首页

《ROMHACK合集教程》，v0.9

资料来源：中国游戏汉化联盟--狼组

目的：希望能让大家方便的在本地看教程。

ps：我实在有事，大家凑合着看，多到狼组提意见！！学习ROMHack要有恒心呦。


狼组Rom hacker教程(一)

作者：Alan Yang 2002/08/10

本文版权属于作者Alan，欢迎访问模拟人(狼组)

简介: 本文讲述如何对SFC游戏ROM进行汉化，并以实例带领读者破解一个Rom，最终实现部分文本对话的汉化。本文面向初学者，阅读本文仅需要预先了解16进制知识。

1. 汉化一个Rom的步骤

a. 找到游戏Rom中的码表(字符对照表);
b. 按照码表将Rom中的文本导出来, 存成一个文件;
c. 翻译这些文本;
d. 找到Rom中的字库, 并用中文字体替换;
e. 为中文字体再重新做一个码表;
f. 按照新码表将译文写回Rom中, 并更新相应的指针表;
g. 测试.
下面我以七龙珠RPG(SF8047)为例, 讲一下具体操作。

1.1 动手之前

在开始动手之前，你要先找到想汉化的游戏的ROM。注意，可不是随便从哪里down一个就行的！你要找到最完美的rom，这样才能保证不会因为Rom有问题，导致你的做了一半的工作白白浪费。具体说，就是要找经过GoodSnes和Romcenter校验过的Rom。
另外需要注意的是，要随时做好备份！原始的日文rom当然要做备份；对修改过的rom，我是每天作一次备份的。备份这个事，强调再多也不为过！
还有一个需要提醒的事，就是有些游戏同时有日文版rom和英文版rom。很多人以为用英文版的rom做汉化会简单一些。这是大错特错的！无论何时，记得一定要用日文版rom做汉化。这样做有以下几个原因：

1. 英文版的字库小，只有52个字母加一些标点；而要汉化一般需要1500－2000汉字，需要对字库做大幅扩容。日文版的rom一般都会带一些汉字，精打细算的话可能够用。
2. 英文版的字模小，一个字母也就8x12，而汉字一般是12x12到16x16大小，为了显示汉字，你又要asm hacking。
3. 英文版的对话远比日文长，为节省空间，一般做了压缩，这大大增加了破解难度。

2. 字库与码表

好，汉化任何游戏的第一步，都是要找到码表。什么是码表？请往下看。

2.0 一个例子

打开你的windows记事本, 往里面写入 "ABCDE你好", 然后保存为abc.txt。
现在的问题是，在这个文本文件里，记录了什么信息？是这几个字在屏幕上的图形吗？

现在，用UltraEdit(或其它你爱用的16进制编辑器)打开abc.txt, 按一下"Ctrl－H"，切换到16进制显示，你看到的应该是下面这样子：

0000000000h: 41 42 43 44 45 C4 E3 BA C3 ; ABCDE你好

左边的000000000h是地址，右边的"ABCDE你好"就是这个文件显示在屏幕上的样子；注意！中间的 41 42 43 44 45 C4 E3 BA C3, 这才是真正记录在文件中的信息！

你可能看出来了，这绝不是这些字显示在屏幕上的图形。就算41-45是ABCDE的图形；“你好”两个字这么多笔划，也绝不可能用C4 E3 BA C3这几个字节就能完全记录下来。你是对的，这个文件里记录的只是字母(和汉字)的代码，并没有任何“怎么画这些字”的信息。那么“画字”的信息存在哪呢？打开你的C:\Windows\fonts目录看看，看见“宋体”、“楷体”等字体了吗？就是这里啊！

这说明了什么？说明PC机把如何显示文字的具体信息存在windows字库里；而我们平常写的文件（不管你是存成Word文件还是txt文件），里面只包含字的编码。

2.1 计算机是如何显示文字的

还是说刚才的abc.txt。你打开这个文件时，写字板程序先取出第一个字节41，然后，它去查一张对照表，这张表是这个样子的：
......
41 - A
42 - B
43 - C
44 - D
45 - E
......
从表中找到41对应字母A。现在计算机知道要在屏幕上显示"A"了，它就会从某个字库中（比如说宋体）去找字母A的字模（通俗的说就是如何画出宋体的A），并把字模显示在屏幕上。然后，取abc.txt的下一个字节42，以此类推。
那么，这张对照表就很重要了。它就是ASCII码表！所有PC上的公共标准。所以，你在自己机器上写的文件abc.txt，不会在朋友机器上显示成“KGTYU”，因为你们都用相同的ASCII码表！

2.2 SFC是怎样显示文字的
原理上基本和计算机一样。假设要显示的文本是"04 78 9A"，码表是
......
04=孙
78=悟
9A=空
......
SFC先取出第一个字节04，查码表，是“孙”，再从字库里找到“孙”字的字模，显示在屏幕上；下一步取出78，查表是“悟”，显示字库里的相应字模，下一步...(算了，没稿费，不多写了，否则我打算写50个“下一步” :))
最后，“孙悟空”三个字就显示在屏幕上了。

2.3 SFC的码表
很遗憾，并不是所有的机器都使用ASCII码表，Machintosh就不用。而大多数游戏机，包括SFC，也都不用ASCII。不仅如此，各游戏开发小组根据自己的喜好和方便，随意安排每个字的编码，造成的结果就是：每一个Rom的码表都不相同！(标准的重要性在这里体现出来，要是有个统一标准，我们就不用费劲去找码表了，这也是为什么Microsoft总是力图让自己的产品成为标准)。
所以，Rom hacker的首要工作，就是找出码表。只有完成这步工作，才能按图索骥，顺利导出对话脚本、菜单、物品名等。

3. 寻找码表

3.1 最简单的情况－单字节码表

尽管每个SNES游戏用的码表都不一样，我们还是有规律可循的。对于英文字母，大家总是习惯按照字母表ABCDEFG....XYZ来排列顺序，SFC程序员也一样，所以假如"A" 的编码是1，那么"B"就是2，"C"就是3，以此类推。

同样，日文假名也是按固定顺序排列的(五十音图)，如下图。

 1 あ a    2 い i    3 う u    4 え e    5 お o
 6 か ka   7 き ki   8 く ku   9 け ke  10 こ ko
11 さ sa  12 し shi 13 す su  14 せ se  15 そ so
16 た ta  17 ち chi 18 つ tsu 19 て te  20 と to
21 な na  22 に ni  23 ぬ nu  24 ね ne  25 の no
26 は ha  27 ひ hi  28 ふ fu  29 へ he  30 ほ ho
31 ま ma  32 み mi  33 む mu  34 め me  35 も mo
36 や ya            37 ゆ yu            38 よ yo
39 ら ra  40 り ri  41 る ru  42 れ re  43 ろ ro
44 わ wa                                45 を o
46 ん n

现在虽然我们不知道他们的编码，我们却知道，"A"和"B"的编码差1，"D"和"H"
的编码差4。如果有个单词mana, 那么字母与字母之间的差值肯定是
m a n a
\ / \ / \/
11 -12 12

如果有工具能搜索差值，我们就能找到Rom中的文本。这种搜索叫“相对搜索”，(relative search)。我推荐的工具就叫Relativeful search，在狼组网站上可以下载。它既能输入英文搜索， 又能输入数字搜索，还允许跳过几个字节输入。

好，进入游戏，找一段文本，如左上图，抄下第一句话，根据上面的表格标上数字：
と つ ぜ ん ゃ っ て き た
20 18 ？ 46 36 ? 19 7

现在打开Relative search: 按"Open File"选择Rom，在"Values"后面填20，因为是十进制所以"Decimal"，按"Add Value"，20会出现在左下的"Values"列表里。如法炮制输入18，下一个值“ぜ”我们不知道，没关系，按一下"Skip value"跳过它。继续输入46和36。有五个就足够了。现在按下"Relativeful Search"按钮，等一下，右下的"Result"中会显示查到的结果。

OK! 找到一个。现在用UltraEdit(或你爱用的十六进制)编辑器，打开DBZrpg.smc。 找到$03C6EC处，如图。这里是不是正确的地址呢？我们随便改几个值，再进游戏看看。

看有没有变化就知道了。把头四个字节"17 15 A8 31"全改成17，存盘，进入游戏。看看最上面的图，怎么样！头四个字全是“と”。说明我们找对了这句话的位置了！

现在我们知道17对应“と”(注意这里是十六进制，换成十进制是23)。由此可以推算出あ是04。好，可以把已知的部分码表做出来了。打开记事本，照这样写：
04=あ
05=い
06=う
07=え
.......
是否写起来很麻烦？这里推荐狼组hdw1978写的Table Maker，如图，选好“あ”和04，一按“生成对照表”马上就成了！是不是很方便。注意它有个小bug：假名"を"被遗漏了，你需要手动把它加入码表。这个工具最简单，狼组网站上还有其他更好的码表工具，大家可以自己试用。

你要是细心的话，应该还能看出，FF就是换行，FF FF是一段话结束。在码表中加一项：
FF=
其他的日文怎么办？太简单了，你只要在$03C6EC处随便乱写些数，再进游戏看看显示是什么不就成了？这些工作就留给你去做了。狼组有完整的龙珠RPG码表下载。看看跟你做的是不是一样？

对于含有大量汉字的rom来说，如果都一个个去试太麻烦了。一般来说，码表的顺序与字库的排列顺序是一致的，所以，如果你能找到游戏的字库，就可以直接把码表按顺序抄下来。

新手可能还有个问题：那些日文汉字都是输入法里找不到的，要怎样输入啊？最简单的就是用南极星里附带的输入法了。可以很快就找到你要输的日文汉字。如图：

3.2 其它Rom及双字节码表
实际上任何Rom都是如此：只要它的文本没有被压缩，从假名入手都能找出码表。对于压缩的
Rom，如果它允许你输入主角姓名，你可以起个名字存盘，再去Search存盘文件！你也可以用在输入
界面不断地变换第一个字母，不要按确认，并配合FPE等进行低级搜索，找到缓冲区的确切地址。
对于有汉字的Rom，会更复杂一些。用一个字节来编码肯定不够，有些Rom就用到了双字节编码，
还有的用到可变长编码。下面说一下火焰之纹章：多拉基亚776的例子：(主要注意它的格式)
我找到第一章一开始两个人的对话，在$0AA493处, 格式如下:

00AA493h: 06 00 3A      78 00      07 00 3A     5B 00
          ----------    ------     --------     ------
          第一个人      第1人头像   第二个人     第2人头像
 06      09 4C  12  48  BB  0F 10    0A 5E 5F    09 29  0A D4
 ------  -----  --  --  --  ------   ----------  -----  -----
第1人说  ど      ぅ   だ  、 等待几秒    王 子     は　   见
09 21  15  5C  1F  15  BE   02    08       07
-----  --  --  --  --  --   --    ----    ------
つ     か  っ  た  お   ?   换行  结束符   第2人说

776的文本很讨厌！不仅有正常的文字编码，还包含大量的控制符（例如切换说话的人物、头像）。
现在说码表，776码表是两个字节表示一个汉字或假名。日文平假名、片假名对应09 xx，汉字对应两个字节，从0A 10到0E 5F共1040个汉字。
双字节的表示方法：连续多个字都以09打头的话可以只写第一个09，省略后面字的09。其他段也一样。
例：
0A10=我
0A12=真
0A6E=棒
那么“我真棒”三个字的编码是：0A 10 12 6E

你可以试试改控制符，改变$0AA49D处的06 为07，进入游戏，如何，原本该第一人说的话，现在由第二人说了吧！这里有两个人，编号06、07。头像也可以改。这种文本中夹杂控制符的rom是很常见的。
总之，要开动你的脑筋，不要拘泥于我写的这几种情况，用各种办法尝试，成功hack出码表的乐趣和成就感足以抵偿你花的时间！
有问题请到论坛提出。

课后作业：阅读本站上的几篇相关文章：
1. SFC_DQ1不完全汉化入门
2. 皇家骑士团2的变态程序员

［下一章］


狼组Rom hacker教程(二)

作者：Alan Yang 2002/08/17

本文版权属于作者Alan，欢迎访问模拟人(狼组)

4. 有关Rom的一些知识

在开始讲下一步之前，让我们先来了解一下ROM的结构，这有助于理解后面的内容。
一个ROM分为两部分：Header(文件头)和主体。Header是一个长$200字节的区域，在ROM的最前头，记录了一些ROM的相关信息。有些ROM甚至没有header。对于汉化，我们完全可以不关心header。而对于主体部分，那是我们的重头了，很遗憾的是，它没法再细分了。这里的内容存放，完全由该游戏的开发人员决定，所以一个rom一个样。
ROM的存储格式有很多，常见的有SMC、FIG、078等。可能你拿到的rom是好几个文件，那么你必须用snestool把他们合并成一个文件，最好是SMC格式的。
ROM的映射格式分为Low rom和High rom两种格式。具体的区别先不用管，你只要知道如何确定一个rom是哪种格式。我们用一个叫ucon的小程序来帮忙，它也可以用来转换rom格式。在DOS命令行下执行
ucon dbzrpg.smc，得到这样的结果：

如图，在HiRom Game一项写的是No，那么这就是Low Rom。至于有没有header可以计算出来：例如龙珠是8M的rom，大小应该是1024×1024＝1048576(注意它是8M bit,相当于1M byte)，但是查看一下文件属性，大小是1049088，多了512字节。这512字节就是header，正好是十六进制的$200。
所以，我们知道了龙珠RPG是有header的，Low rom格式。

5. 指针表与文本块

铛铛铛铛铛铛，继续理论课！不要说我跟唐僧似的，可一定要用心看明白啊！
假如我要开发一个游戏，其中肯定有好多对话。这些对话文本怎么存储呢？ 一种方法是哪里用到一句对话了，就把一句话写在哪里，对话之间插入很多的控制符，甚至程序。这就是“漂浮”的文本。可是这样很麻烦，如果后来对话脚本要修改，句子变长了，就要影响后面的程序。这样的Rom，像火焰纹章系列、浪漫沙加系列都是，汉化起来很麻烦。
更好的方法是，把所有对话(文本)都放在一起，对话与对话之间用一个特定的结束符分开。然后，再做一张表，表中每一项都指向一段话的第一个字。这张表就叫指针表(Pointer Table)，那一大堆文本叫做文本块(Text Block)。举例如下：

指针表：
00 60
0F 60
1D 60
2F 60
3A 60

文本块：
地址 文本
6000-600F: This is a dog.|T
6010-601F: hat's a pen.|Do
6020-602F: you play game?|C
6030-603F: ertainly!|What's

解释：我先说明一下，在汇编及16进制中，两个以上字节的东东，最前面的字节叫高位，后面的叫低位。比如$34AB，34是高位，AB是低位。在Rom里，它是这样存放的：先低位后高位。所以$34AB在Rom里存成“AB 34”。

文本块地址是16进制的，从6000-600F一共有16个字节，即一行有16个字符。“|”是结束符，标志一句话结束。指针表中每一项由两个字节组成。先看第一项，00 60，实际上是指向地址$6000，正是"This is a dog."这句话的开头。下一项0F 60，指向地址$600F，正是"That's a pen."的开头。

采用这种方法存储，最大的优点就是，随时可以改对话文本的长度！只要改完后，把指针表相应的指针指向正确的位置，根本不用改动程序！这样的rom我们汉化起来也比较方便，因为可以写程序来自动导入导出对话脚本。

6. 指针表、文本块、字库与码表的协作关系
先仔细看看这张图，能看明白吗？这是如何显示一个字的完整流程。
如图，假设程序中要显示一句话，这句话是文本块中的第一句。程序会调用显示子程序来显示这句话。开始运行。首先，它检查传给它的参数“1”，这是指要显示文本块中的第一句话。查指针表，第一项指向$6000，OK，从$6000地址取出一个字节$54，查码表，得知$54表示字母T，再从字库中找到字母T的字模，并显示出来。
然后，从$6001取出第二字节$68，重复上述操作。一直到$600E，取出的值是$FF，这是结束符标记，显示子程序返回结束。此时，第一句话已经显示在屏幕上了：“This is a dog.”
明白了？当程序中要显示文本时，只要调用显示子程序，并告诉它要显示哪一句话，显示字程序就会把对应的话显示出来。

7. 寻找指针表

现在我们要找指针表了。还是用龙珠Rom，还记得这个Rom有文件头，是Low-rom吧。上一讲我们找到第一句话的开始地址为$03C6EC，减文件头$200后是$03C4EC，去掉高位03并交换，得到Rom中的存放格式为EC C4。这就是找指针表时的计算方法。
在UltraEdit中打开rom，向上搜索这个值，马上找到一个地址$0394A2。(此处如有疑问请参见看我翻译的“汉化高级教程”一文)

观察这个地址附近的数值，发现是有规律的，每两个字节一组，都指向Cxxx附近。肯定就是这里。把ECC4
改成第二句话的地址03C5(指向$03C703)，进入游戏看看(如图)，哈哈! 拉蒂兹只说后两句话了。

这里就是指针表没错！通过指针表，可以很容易地找到每段文本的开始。指针表很有用处；如果你会写程序，它可以大大方便导出和导入过程。将来所有中文文本都插入后，还要更新指针表，重新指向各段中文文本的首地址。

8. 导出脚本对话 (Dump Script)

现在，你只需要导出文本，交给一个翻译去翻，兴奋吧？我们来看看怎样导出脚本。
DOS下最常用的工具之一是Thingy。早期的英化中，好多老外专门用它，所以它支持的码表格式也就成了标准。当然现在有很多工具已经超过它了，但它仍然不能不提。

执行Thingy，它会问你Rom名字和码表名字，它支持两个码表同时使用，第二个是可选的。进入Thingy后，随便翻翻页，你发现，右侧显示的已经是日文了！ 把光标停在文本块开始处，按D，回车；翻页到文本块结束，再按D，Thingy会提示你给导出的文件起个名字。写上名字后，这一块文本就导出来了。
其实有人专门编了工具来进行导入导出。Script-extractor和Script-Insertor就不错，用起来比Thingy方便多了。导出的文本就像下面：

「それとも じっくりと あそんで
ころそうか？ へへ……いくぜ！」
「おとうさん！ ピッコロさんが…
ピッコロさん しんじゃった-！」
「すまん…… おそかったか…！
ゆるさんぞ！ オメエら--！！」
じめんから カイブツが？
「なうだ!! きさまら こいつらと
1ぴきずつ たたかってみんか!!」
「まさか こんなクズなもに……
こうなったら カカロットの
ガキをみちづれに してやるぜ!!」

再稍微处理一下就很不错了，下面就是翻译的工作了。
狼组汉化专区还有很多工具都可以导入导出，大家可以选自己最方便的使用。

对于有编程能力的rom hacker，最好是自己写程序导出。我习惯是把整个rom都导出来，再手工整理去掉垃圾。下面是我导出776对话用的VB源程序核心部分，其实很简单的，大家可以参考。完整程序在专区。
简单说明：MB(a,b)数组包含当前的所有码表，count是计数器，每3句话显示一次地址。每次从rom里取一个字节放在d里，再根据码表译成文字。hz代表码表第几段，hz＝0时是假名和字母(即09xx)。

For i = start To endp
    Get #2, i, d
    If d = 0 Then
        i = i + 1
        Get #2, i, d
        If d = &H11 Then
            hz = 0
        ElseIf d = &H12 Then
            hz = 1
        ElseIf d = &H13 Then
            hz = 2
        ElseIf d = &H14 Then
            hz = 3
        Else
             Print #1, str
             count = count + 1

             If count = 3 Then
                count =
                0  str= "<$" & Hex(i + 1) &">"
                Print #1, " "
                Print #1, str
                Bar1.Value = CInt((i - start) / 1000)
             End If
             hz = 0
             str = ""
         End If
    Else
        str = str & Mb(hz, d)
    End If

Next

好，今天的课就到这里。学完这课后，大家应该可以导出对话文本，开始找翻译了！顺便澄清一个错误观念：汉化不需要你会日文，因为我们要有分工的嘛。见到有些朋友在论坛说“我先学好日文吧”，其实根本没必要的，像我就完全不懂日文。有问题请到论坛提出。

课后作业：阅读本站上的几篇相关文章：
1. 汉化高级教程
2. 汉化入门跟我做
3. 初学者ROM破解指导
4. 试着导出龙珠的文本。

［上一章］［下一章］


狼组Rom hacker教程(三)完结篇

作者：Alan Yang 2002/08/27

本文版权属于作者Alan，欢迎访问模拟人(狼组)

8. 查找字库

好，现在要做的，是最有意思的部分！
先来个名词解释：TILE(图块)－是指8x8的一小块图。SFC中的所有图形都是用8x8的小块图形拼成的，有点像马赛克。这一个8x8的小图块就叫tile。它是最小的图形单位，不可再分的。对话汉字同样也是用tile拼成的。一般游戏用的文字，从12x12到16x16大小的都有，用4个tile拼成。
怎么能看到tile呢？我们用一类叫精灵编辑器的工具（精灵是指用tile拼好的人物或头像）。其中最好用的是Tile Layer Pro(TLP)，网站上有下载。用TLP打开龙珠的rom，显示是这样的：

选择“View”“Format”，然后选择“Gameboy”模式。像这样：

为什么要选GB模式呢？对于SFC的游戏，字库一般都是2bpp格式（关于1bpp，2bpp的含义，感兴趣的请看站上其他文章）。GB模式恰好就是按2bpp格式显示tile。

好，现在睁大眼睛，按“Page Down”翻页，仔细看有没有像文字的东西！如果找到了，那就是字库。
但是……好像找不到啊～！答对了，龙珠RPG的字库是压缩的！
（打死他，竟敢欺骗读者！......Alan 被读者攻击，HP减少30点）
啊……对不起，事先没仔细看，就选了这个rom，谁知字库是压缩的。这也告诉我们，汉化的第一步不是找码表，而是先看能不能找到字库，再找码表。除非你会asm hack，否则，遇到压缩的字库，你最好就换个rom下手吧。从现在开始，我以776为例讲解字库替换和汉化对话。请回忆一下第一课讲过的776码表。

9. 多拉基亚776的字库

776的码表站上有下载，可以下载回来做为参考。我再简单说明一下：
776采用双字节编码，如0A20=神。在这个编码里，第一字节0A，我们把它叫做段号，第二字节20才是字的实际编号。776共有09xx,0Axx,0Bxx,0Cxx,0Dxx,0Exx 六段。
在rom中，系统会尽量节省空间。对于每个字，如果它的段号和前一个字相同，就可以省略段号，只用一个字节。
如：09 A0 23 E1 0B 45 21 09 61 0A 8B
上面这行对话，黑体字代表段号，后面的是1字节的编码。如头四个字节，实际代表了3个字符：09A0 0923 09E1。其后是0B 45 21，系统将它解释为2个字符0B45和0B21，以此类推。

好，现在用TLP打开776的日文rom，换到GB模式，往下翻吧！很快就可以看到大段汉字了，这就是字库！(左下图)
还没改动过的字库 改动过“王子”两字后的字库
现在我们把字库里的“王子”二字改成“外传”看看：
改字要用到TLP的编辑功能了。先点击要改的tile，在中间编辑框里就可以编辑了。下方是调色板，776里用到了四种颜色：蓝绿白黑。其中蓝色是重要的，绿白二色是阴影。用鼠标慢慢“画”字吧，相信很快你就能掌握的。右上方的图是改完的样子。(画字模可以参考专区其他文章)

改完后存盘，然后我们进游戏看看有什么变化：

左图是原始的样子，右图是改完后的样子，看看：“王子”变成“外传”了。
这就是修改字模！以后，我们会把所有的日文汉字及假名都改成中文字模的。

10. 制作中文对照表

太简单了。先打开一个空的文件，把“王子”二字的编码从日文码表拷贝过来：
0A5E=王
0A5F=子

然后把“王子”二字改成“外传”：
0A5E=外
0A5F=传

好，现在把这个文件存盘，起名叫776c.tbl。这就是我们的中文对照表！以后，随着做汉字字模，随时往里添加新的编码就行了。

11. 将中文译文写回Rom中
（假设你已经把所有字模都做好了）
我们还是以776第一章的第一句话为例：(如果你的ROM有header，地址是00AA69E)
00AA49E: 09 4C 12 48 BB 0F 10 0A 5E 5F 09 29 0A D4 09 21 15 5C 1F 15 BE
どぅだ、王子は见つかったお? 瓦斯曼，发现王子了吗？

根据我们的中文码表，查出每个字的编码，再根据776的特点，把相同的段号省略，结果应该是这样的：
00AA49E: 0B A6 0A 69 0B 85 09 BB 0B 43 44 0A 83 63 47 0B 45 09 BE
用你爱用的16进制编辑器写入rom，存盘，再进入游戏去看看，怎么样！这句话已经汉化了吧！

可是，汉化后比日文文本少了两个字节，怎么办？没关系，汉化下一句话时，紧接着这一句汉化就行了。如果汉化后的字节比日文的多了，也不用怕，按顺序往下汉化就行，只要最后一句话正好在原来的日文编码位置上结束就没问题。
776是不用考虑指针表的问题的；但是有些游戏需要指针表。那么，在汉化完后，也要把指针表对应的指针，改成指向中文对话的起始位置。

12. 加速汉化进程
如果这前面的东西你都看懂了，实际上你已经学会了汉化需要的技能了，祝贺你，找个游戏rom练练手吧！
但是，如果你真的照前面我讲的去做，一定会累死的。工作量实在太大了。怎样能加速汉化进程呢？当然要利用各种工具啦！
A. 首先，画字模很烦，我们完全可以用工具来做！Edison的CharEdit可以选择任意的字体字号，写入rom中，很方便的。但是它只能写入2bpp格式的字模。就算画字模也可以加速，狼组Dark01首先发明的结合Photoshop的画字模方法非常实用。如果你会一点编程，建议你去研究一下1bpp、2bpp等字模存储格式，再看看“任意字体字模生成器”这个程序，然后你就可以自己写一个程序，一次把所有字模都写入rom中啦！狼组现在基本不需要做字模的人了，因为我们对标准大小的字体一律用程序写入。
B. 推荐狼组的“汉化辅助工具”，这是个自动把中文/日文翻译成代码的工具，还可以检查哪些汉字还没做字模。第一次使用时要设置好中日文的对照表。

好，Rom hacker的课程到这里就全部结束了。希望你也能汉化成自己喜欢的游戏！记住：汉化游戏，最最需要的，不是技术，而是耐心和坚持。祝你成功！

课后作业：阅读本站上的几篇相关文章：
1. 汉化专区的全部ROM HACK文档


SFC Dragon Quest 1 不完全汉化入门

Written by:波波 data:12.03.2001

一：前言

我写这篇文章的目的是能够让更多的朋友对romhack感兴趣，并能够加入到romhack的队伍中来。首先声明，我不是专业hacker，我也不懂高级程序语言，我有的只是：16进制的数学知识、电脑、sfc模拟软件zsnes、sfc游戏rom、16位编辑器hexworkshop和耐心、逻辑推理能力加上女友辉辉的支持，在汉化dq1之前我也没有看过任何romhack的资料和介绍，我只是希望能让辉辉看懂我玩的游戏。

好！如果你也希望得到自己动手DIY的成就感，那就接着看下去吧，我会尽量详细的说明我的经历，并配上直观的图片，我所引用的图片完全是自己截取下来的，并未得到rom制作者及模拟器制作者的同意，在此我保证仅将该图片用于本文章并向各位rom制作者及模拟器制作者表示敬意！

二：入门指导

想了半天也不知道如何写第二部分的标题，其实就是言归正传的意思了，我不详细讲述如何得到sfc的rom和如何运行zsnes的步骤了，因为那样又可以写一篇文章了，我假设各位都已经有了模拟器并都有了游戏rom，我从网上下载的文件是sf12069a.078和sf12069b.078，运行snestools,选择gd3 to fig，然后先后选择上述两个文件，回车后提示转换ok，这样你就拿到了一个sf12069.fig文件，如何得到smc文件呢？请出ucon吧，把sf12069.fig文件拷到ucon目录下，再进入ms-dos模式运行“ucon c sf12069.fig”，注意c的前后是空格，最后拿到了sf12069.smc。

为什么呢？因为我就是在sf12069.smc的身上做的汉化工作，如果你的文件格式和我不一样，那下文的一些数据地址的位置就不一样了。

运行游戏看到了这幅画面：

然后我选择进入dq1，这时屏幕上会出现以下画面来让你制作冒险之书（就是纪录了）：

进入本项以后就是输入游戏者姓名的画面了，在这里我选择了第一个假名（日语的音标）四次作为我的姓名（为什么？看下去就明白了。）：

现在选择右下角的结束标志并设定游戏对话速度后进入游戏，你会看到第一页对话内容：

好！现在你可以看见自己选择的名字已经作为对话的一部分出现了，这时退出模拟器（一定要完全退出，见下文）然后把游戏目录下的存盘文件sf12069.srm随便改个名字，如改为sf12069z.srm，现在你需要重新开始游戏并给主角取一个不同的名字，同样是四个字符但前三个不变，第四位改为第二个假名，为什么呢？因为我假设ROM的编码和ASCII码相似，那么假名的编码就应该是顺序排列的，现在明白开始为什么要取四个一样的字符做名字了吧?就是为了能在存盘文件中出现四个一样的编码作为参照物，取完名字后进入游戏第一副画面后退出游戏，这又是为什么呢？因为我在写本文截图时发现似乎zsnes退出时会向存盘文件中随机写入一些数据，所以如果退出的时间不一致，会导致两个存盘文件中变化的数据太多，就失去参照物的意义了。

现在在游戏的目录下应该有sf12069.srm和sf12069z.srm两个存盘文件，如图：

好了，现在需要做的就是用工具来比较两个存盘文件，打开hexworkshop，点这个按钮：

然后分别选择目标文件，然后按ok进行文件比较就可以了，如下图：

这时就会出现两个窗口来显示两个文件的比较结果，其中黑色背景的数据就是两个文件中产生了变化的部分，理论上只应该有四个字符发生变化，但实际上并不如此，会有很多的变化数据看的你眼花缭乱，怎么办呢？耐心！我前后一共做了五个存盘文件，名字是按一定规律起的，然后两个两个按照排列组合的法则进行比较，终于发现了四个名字字符的变化位置，一共有三个地址，估计是对应三个存档的。这一步很重要，需要很高的耐心，因为你不知道客观的位置在哪里，这就像你进入了一间绝对黑暗的房间，你需要光明，并且你知道屋子里有一支蜡烛，那么你就得在黑暗中不断摸索，直到找到蜡烛。

如果上一步取得了成功，那么接下来我要做的事你应该很容易想到：我要全部的假名编码表！具体的说就是不断的重新取名进入游戏，然后退出并观察存盘文件中的编码变化。聪明的读者可能会觉得这样太麻烦了，是否可以直接对存盘文件进行修改呢？不行，因为我发现一旦存盘文件的字符发生变化，游戏马上会警告你文件发生损坏，我想可能是游戏有自己的存盘信息来核对存盘文件的完整性吧，可惜的是，我至今也没找到相关信息的存储地址，所以我用的就是穷举法得到了以下编码表：

0F=あ10=い11=う12=え13=お14=か15=き16=く17=け18=こ19=さ1A=し1B=す1C=せ1D=そ1E=た

1F=ち20=つ21=て22=と23=な24=に25=ぬ26=ね27=の28=は29=ひ2A=ふ2B=へ2C=ほ2D=ま2E=み

2F=む30=め31=も32=や33=ゆ34=よ35=ら36=り37=る38=れ39=ろ3A=わ3B=ん

现在再看这幅画面：

你会说：我可以知道假名的编码了，但是汉字的编码怎么办呢？这确实是让我当时很为难的事情，但后来发现其实很简单，因为我很聪聪的，我在纸上先把已经知道的编码添上去并把汉字和空格及标点符号用%来表示，得到的结果如下：

%%1313%0F0F0F0F%

%%%%27%%%29151A%34%

1D231E%%3727%%%3D21133D1E%

大家能看明白吗？这就像福尔摩斯破译密码一样，从已知来推理未知，我想游戏的对话内容在应该是连续存放，于是打开hexworkshop在sf12069.smc文件中先查找1313，可是得到了上百个位置，我又查找0F0F0F0F，也找到了不少，可是位置很零散，没有规律，这时我想一定是1313在游戏中出现的频率很高，而用到0F0F0F0F的地方要少，于是就在所有0F0F0F0F出现的地方附近观察，因为从上图可以得出结论，1313和0F0F0F0F之间应该距离很近，但遗憾的是，事实并不像我所想象的，而且正相反，找不到两个字符串相邻的地址。

怎么办呢？继续努力！条条大路通罗马，我继续查找1D231E、3727和3D21133D1E，这时会发现得到的结果有多有少，可见这三个单词在游戏中出现的频率有多有寡，接下来呢？记得有位老人家说过，要善于从现象中发现本质，经过对四个搜索结果的反复比较，重点在是否有四个接近的地址，最后我终于发现了下面的一段数据：

大家请仔细观察，是否有三个字符串都同时出现了？除了0F0F0F0F，仔细核对一下代码的位置，我终于发现了蜡烛：00表示空格，D0**表示汉字，那FE表示什么呢？你会怎么办？我当时把FE改为00然后存盘，再运行游戏……哦！原来是表示回车的意思。

知道了这个有什么用呢？很简单，可以用穷举法啊，把D013改为D000，然后D001、D002……按16进制以此类推，我就是这样发现了300个左右的汉字，在此过程中，我又发现了F4表示游戏者名字，F58D表示物品，F0FD表示按键后清除对话框，FFFD之前的应该就是文本指针（还没研究）等等等等。

好了，接下来的工作就是重新改写文本了，先定位游戏中对话的地址，然后改写成新的内容就可以了，大家一定觉得简单，但是很花时间的，我用了将近一个月才汉化了拉达特姆城和拉达特姆镇的所有对话，大家要有心理准备哦！

最后，为什么本文叫不完全汉化指南，就因为我不是高手，只能汉化对话，所以游戏里还有日文出现，我只是希望本文一方面可以更快的帮助汉化爱好者入门，另一方面起到抛砖引玉的作用，期待不久我也可以看到高手写的《****完全汉化指南》！

波波

完成于12.06.2001


皇家骑士团2的变态程序员

关于皇家骑士团2的ROMHACK/TRANSLATION的一点点进展

本文中文翻译版版权属于愤怒的人justjust，如欲转载请直接和他联系。欢迎访问模拟人。

皇家骑士团2的变态程序员
关于皇家骑士团2的ROMHACK/TRANSLATION的一点点进展

今天才发现QUEST的皇家骑士团2程序员实在是有够变态,真不知道他是什么思维方式,就像用Naga无法看见它的字符点阵一样,用Relative_Search在Tactics_Ogre的Rom中也根本找不到任何有关对话文本的踪迹.不过与人斗争其乐无穷,我想到了用低阶扫描的办法,只要固定地址下的数值变化就一o定能找出它,当然在一般的对话文本中我们无法确信它们的地址是不变的,但皇家骑士团2中却提供给我们一个在固定地址下的字符变动的机会,不错,就是_姓名输入_!一般只有当确认输入完成时,输入字符才会被传走,而之前它们,就是你输入的那个稀奇古怪名字中的字符,一定是老老实实呆在缓冲区里的(即使最变态的程序员也应该这么做吧)只要我在同一位置替换不同的字符,那该地址的值也就相应变化,用低阶扫描就一定能找到.呵呵,这下你可要显出原形了吧?
为了验证该方法的准确性,我先找了也能输入姓名的Tales_of_Phantasia(幻想传说SF48001)来做实验,结果相当轻松的找到了和hdw1978同志的hack文档相同的结果(平假a是85h,i是86h...),OK,下一个就拿皇家骑士团2开刀.残念,啊不,恰恰相反,我依然很顺利地找到了它的所有字符代码(平假是从21h~70h,片假从71h~C1h;英文字符大写从01 27h~01 40h,小写从01 41h~01 5Ah;皇家骑士团的假名中包含了小写的a段字符所以比幻想传说的字符集大一些)
可是,这是个可怕的字眼,我用这些字符代码在ROM中精确查找时却根本找不到相应语句(我还刻意避开了固定的人名,地名,只查找包含动词的语句),我又用了相应顺序的相对查找,仍然没有结果.那么只有两种可能:1,在对话中的文本采用了和命名时不同的字符序列代码;2,文本采用了压缩,即使只是行压缩,一般的查找也无法找到地址了.
皇家骑士团2的程序员实在是变态啊,其实该游戏并未用满24Mb的空间(从ROM尾部大段的FFFFFFFFFF...就可以看出),而那位程序员竟然还压缩这压缩那,不是变态是什么!不过这也可能是因为用了Hermit1.3的缘故吧.(就是那个在QUEST标志后出现的那个号称19,000block每秒的图形引擎,嗯,很有现在号称xxxxxxpolygon/sec的3D引擎的气势吧)

当然我也没白忙活这半天,至少其他有命名系统的游戏(理论上)都可以用这种低阶查找的方法找到字符代码,诸如最终幻想,机器人大战,浪漫沙加,前线任务等等.(龙界传说不知道能不能命名,忘了)而如果哪位需要皇家骑士团2的"假定"字符代码集或不明白低阶查找的,就mail我吧.
哦,对了,低阶查找的话,用FPE5.0就够了.

本文中文翻译版版权属于愤怒的人justjust，如欲转载请直接和他联系。欢迎访问模拟人。


ROM hacking 高级技术

如欲转载，请勿删除此行。欢迎访问模拟人。

ROM hacking 高级技术
(本文英文版权属于J2e translation)
作者: Brian Maxwell / Anus Pompous 翻译:Alan Yang

简介:本文适合有一些基本的ROM hacking经验的人阅读。
讲解了指针表，ROM的扩容，移动文本，改变文本长度等技术。
想进阶者必读！(Alan Yang:原文中有些讲解NES的部分，我就
不翻译了，基本上与SNES差不多。废话我也全省略了）

==================================================
目录
==================================================
1- 介绍
2- 文本是如何储存的
3- 文件头 (Header)
4- 指针表 (Pointer Tables)
5- 在Rom中移动文本
6- ROM扩容 (Rom Expansion)
7- 文本超出所在Bank (Expanding text past its bank)
========================================
SECTION 1 介绍
=======================================================

1.0 关于本文
本文用到的hex editor，我推荐 "Hex Workshop"!

==============================================
SECTION 2 文本是如何储存的
============================================

2.0 可变长度文本
它们一般储存在一大堆字符串中，一个紧挨着另一个。两个
字符串之间通常有一个结束符，标志字符串的结尾(不是换行
标志!)。这堆字符串(我叫它们"文本块" "text blocks")
一般不是按照它们在游戏中出现的顺序来排列的。在所有
本文块之前有一个指针表。NES roms一般都是这样的。而
SNES roms, 除此外还有其他的存储方式，例如，文本也
存储在上述文本块中，但没有结束符，而是在字符串之间有
一些代码。它也没有指针表，你可以毫无问题的改变单个
字符串的长度！缺点是，这使得dump text更困难。
浪漫沙加系列就是这样存储的。

2.1 固定长度文本

通常用来存储RPG中的怪物名、物品等。最终幻想系列就是例子。
固定长度文本中，所有字符串都一样长，中间不用结束符分开。
例如，Final Fantasy 4中，物品一律9个字符长，就像这样:

Tent FenixDownPotion BlackSwrdIronArrow

你可以看到，中间没有特殊的16进制字符分开它们。这意味着
你可以利用中间的空格！例如你看到"Tent"后面有5个空格，你
就可以把它的名字写的更长些，如"Camp Tent". 但你不能超过
9个字符。要想增加固定长度，唯一的方法是学习ASM!

2.2 漂浮的文本

有时候在SNES roms中, 文本不在文本块中，而是"漂浮"于Rom中，
要加长长度，唯一方法是把它们移到其它有足够空间的地方。
(详细讲解见 section 5)

2.3 其它你要知道的存贮方式

有时文本块不使用指针表。这样的文本最容易扩充，只要移动结束符就
可以。对漂浮的文本也可采用类似的方法，只要移动字符串间的代码
就行。

=======================================================
SECTION 3 文件头
============================================

3.0 NES Headers(略)

3.1 SNES Headers

SNES文件头比NES大多了，共512字节，或者$200字节(16进制)。
这也是个重要数字，虽不象指针表那么重要，但在移动文本块时
非常重要！

一个SNES文件头的例子:

8000000000000000AABB040000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000

在这里的信息并不重要，但如果你的Rom没有Header的话，
最好加一个。(某些Rom不能运行就是因为没有Header)
可以用Snestool加。

==========================================
SECTION 4 指针表
=============================================
4.0 NES指针表

要找指针表，你必须知道Rom中的文本在哪里，以及结束符是什么。
知道这些你就能找到文本块的开始，而指针表就在文本块的最前面！
他看起来好象有某种格式，找出这个格式就是你的任务。从一行的
开始取一个地址，(例如012D63)，只留后4位:2D63. 指针表用两个
字节存储，先低位后高位，所以2D63拆开正好两个字节:2D和63!
现在要先减去文件头，NES文件头是$10字节，$2D63 - $10 = $2D53
倒过来是532D(在Rom里就是这样存储的!)
这组数中只有53是肯定正确的，2D则可能是别的值。所以你要找
53xx (xx未知). 剩下的就靠你了。

一个NES rom指针表的例子:

019D30: 4D76 00F8 349D 3B9D 419D 449D 499D 4D9D
019D40: 529D 569D E5E3 F6CD 4ADA 70F8 F9FA FBF0
019D50: 704C FE70 4BDD CEF3 705C F64D 704C F6CF
019D60: CE70 F5DD CB70 4BF0 DDF1 F2DD F370 83B7

解释: 你已知"70"是结束符，文本块的开头在$019D44
所以在那之前就是指针表! 你注意到指针表中，9D重复出现。
他就是你要找的"xx"

4.1 SNES指针表

SNES指针表与NES一样，只是你不用减去文件头。但小心，有时候
你会碰到一个讨厌的Rom，必须减掉一个数，但这很少见。
另外记住有些SNES rom不用指针表(Romancing SaGA). 所以如果
你在文本块前面找不到指针表，就做个试验！试着改变结束符的位置，
以改变字符串的长度，然后进游戏看看是否有用! 但如果不起作用，
又找不到指针表，你就必须学习如何在Rom中移动文本! (见section 5)

==============================================
SECTION 5 在Rom中移动文本
==============================================

5.0 在Bank内移动文本

如果你找不到指针表，改动结束符的位置也没用，你就只能用这招了！
如果你要加长"漂浮"的文本，也得用这招。大多数时候，文本要在Bank
内移动。一个Bank是固定长度大小的rom空间。在low-rom中一个bank
长度是32KB($8000字节)，high-rom是64KB ($10000字节). 例如, 在
Low-rom中，地址$000200 - $0081FF是第一个Bank，
$008200 - $0101FF是第二个Bank，ect...
在high-rom中， $000200 - $0101FF是第一个Bank，
$020200 - $0301FF是第二个Bank，ect...
注意到了吗，所有地址都包含了$200文件头！
哦，忘了说明，"$"代表后面的数是16进制。

一般情况下，文本只能在它所在的bank内移动。下面是一个例子：
首先，我们从FF4中找一段单独的文本字符串(不在文本块中)：
开始地址在$09DEC6. 现在减去文件头长度(SNES文件头长$200)：
$09DEC6 - $200 = $09DCC6.
你只能在一个Bank内移动, 即只能在$098000 - $09FFFF内。
(不考虑文件头。同时注意FF4是low-rom).
下一步去掉最高位09, 因为bank内转移指令只用2个字节表示地址。
09DCC6变成了 DCC6. 现在交换重组为：C6DC
在bank内查找C6DC, 你应该能找到这个指针! 改成新地址吧！

另一点注意: 在Low-rom中，你所查找的地址必须永远在8000 - FFFF之间！
如果你要查找 0928B0, 减去文件头，去掉最高位09是26B0. 你要把它加上
$8000, 以保证地址在8000 - FFFF之间, $26B0 + $8000 = $A6B0.
交换为 B0A6，你要查找B0A6!
但在high-rom,地址可以是0000 - FFFF的任意地址，因为
一个bank是64KB，不是32KB.

5.1 移动大文本块(及其它东东)到另一个Banks!

通常大文本块都有3字节指针，允许你把它移到rom内任何地方! 这也是
Rom扩容之所以可行! (见section 6) 你要做的就是把文本移到有足够
空间的地方，然后再加长句子。
首先找到文本块开始位置(你要分别移动文本块和指针表。此法对
一些"漂浮"文本也有用，但它们大多数用2字节的bank内指针.)
移动方法基本上与5.0 讲的一样，不同处是你要算出它所在的bank.
先减掉文件头，下表列出实际地址与SNES能读的地址关系：

low-roms表:

HEX Low-ROM SNES

000000-007FFF = 008000-00FFFF
008000-00FFFF = 018000-01FFFF
010000-017FFF = 028000-02FFFF
018000-01FFFF = 038000-03FFFF
020000-027FFF = 048000-04FFFF
028000-02FFFF = 058000-05FFFF
030000-037FFF = 068000-06FFFF
038000-03FFFF = 078000-07FFFF
040000-047FFF = 088000-08FFFF
048000-04FFFF = 098000-09FFFF
050000-057FFF = 0A8000-0AFFFF
058000-05FFFF = 0B8000-0BFFFF
060000-067FFF = 0C8000-0CFFFF
068000-06FFFF = 0D8000-0DFFFF
070000-077FFF = 0E8000-0EFFFF
078000-07FFFF = 0F8000-0FFFFF
080000-087FFF = 108000-10FFFF
088000-08FFFF = 118000-11FFFF
090000-097FFF = 128000-12FFFF
098000-09FFFF = 138000-13FFFF
0A0000-0A7FFF = 148000-14FFFF
0A8000-0AFFFF = 158000-15FFFF
0B0000-0B7FFF = 168000-16FFFF
0B8000-0BFFFF = 178000-17FFFF
0C0000-0C7FFF = 188000-18FFFF
0C8000-0CFFFF = 198000-19FFFF
0D0000-0D7FFF = 1A8000-1AFFFF
0D8000-0DFFFF = 1B8000-1BFFFF
0E0000-0E7FFF = 1C8000-1CFFFF
0E8000-0EFFFF = 1D8000-1DFFFF
0F0000-0F7FFF = 1E8000-1EFFFF
0F8000-0FFFFF = 1F8000-1FFFFF
100000-107FFF = 208000-20FFFF
108000-10FFFF = 218000-21FFFF
110000-117FFF = 228000-22FFFF
118000-11FFFF = 238000-23FFFF
120000-127FFF = 248000-24FFFF
128000-12FFFF = 258000-25FFFF
130000-137FFF = 268000-26FFFF
138000-13FFFF = 278000-27FFFF
140000-147FFF = 288000-28FFFF
148000-14FFFF = 298000-29FFFF
150000-157FFF = 2A8000-2AFFFF
158000-15FFFF = 2B8000-2BFFFF
160000-167FFF = 2C8000-2CFFFF
168000-16FFFF = 2D8000-2DFFFF
170000-177FFF = 2E8000-2EFFFF
178000-17FFFF = 2F8000-2FFFFF
180000-187FFF = 308000-30FFFF

我相信你已看出些东东了。那么假设说你有个文本块在$080600.
减去文件头是 $080400, 然后在上面的表中查SNES地址。
$080400 对应 $108400. 前后交换是 008410. 你应该在Rom里查
008410。你可能查出多个地址，记住一定要把它们全改成新地址！

Hint: 这些地址前通常都有一个"BF"，对于008410, 看起来
是BF008410.

现在说说high-roms。因为bank是 64KB，snes地址与Low-rom
不一样了。它不必非在8000-FFFF之间不可！它的地址是从
C00000开始，所以你把HEX地址加上$C00000就是High-rom SNES
地址. 例如, hex地址$1AE380, 减文件头是$1AE180，加$C00000
以后是$DAE180. 这就是SNES地址! 你找到你的指针了!

===================================================
SECTION 6 ROM 扩容
===================================================

这很简单! 在SNES rom中, 要使容量大些，你只需要加一些空间
就行! 当然空间必须以32KB的倍数增长, 或者说是$8000字节，
或者说，1 bank.

但是如果你保持Rom大小是4m bits的倍数，它会更稳定。

下面表格是稳定的rom大小:
(表格包含$200 文件头)

MBits HEX字节数 DOS大小

4mbit $080200 0.5MB
8mbit $100200 1.0MB
12mbit $180200 1.5MB
16mbit $200200 2.0MB
20mbit $280200 2.5MB
24mbit $300200 3.0MB
... ... ...

================================================
SECTION 7 扩充文本时，超出所在Bank
=============================================
除非你打算修改ASM代码, 否则别让文本超出它所在的bank.
我的意思是, 如果你加长一个low rom中的文本字串, 别让它
超过所在Bank的32KB边界限制。对high-roms，别超过64KB.
超过的部分不会在屏幕上显示出来。是否在所有的Rom中
都这样我不敢确定，但就我的经验来说，在我做FF4时是有此
限制的。

如欲转载，请勿删除此行。欢迎访问模拟人。


ROM修改说明文档（1）

本文版权属于作者黑龙，欢迎参观狼的模拟器世界 http://mywolf.yeah.net

   呵呵，想学习一下如何进行ROM的修改吗？不管怎么说，看到这篇文档的应该就是有这种想法的吧。呵呵。首先，我来界定一下我所说的ROM修改的定义： 这里所说的ROM修改仅仅是指为了汉化而进行的文字上的修改。这里不会提到任何修改游戏中任何有关编辑人物，等级，场景等等汉化之外的任何东西。（呵呵，这些现在我也不明白。:P）修改的目的也是为了能让好的游戏得到更深层次的理解，没有任何的商业目的和违反版权的意图。 另外要说明的是，这份文档是根据我的理解写出来的：它只提示一种方法；它并不绝对--这里所说到的方法在有些地方适用在其他地方可能就行不通。所以这篇文档不会包含所有ROM修改需要的知识，希望大家能一起来讨论，汉化更多的电视游戏出来。哈哈，先说这么多吧，下面言归正传！

第一课：什么是ROM

呵呵，这看起开是一个十分基本的问题（画外音：它简直太基本了，我要不知道什么是ROM还修改什么？！不就是那个只读存储器吗？啊…………我错了！？）。
这的确是一个十分基本的问题，但是我们一般都要从一个最基本的概念开始。不是吗？如果象刚才那位说的，岂不是理解上就有偏差了？:D 一个ROM，在这里提到的，是一个由"dumper"或者"copier"从游戏板卡上拆离出来的板载数据的镜像。它是一个由游戏代码，图形数据，音乐数据等等混合组织在一起的镜像文件。由于无法进行数据的写入操作，ROM代表了只读的内存。
（“呵呵我还是没有错！”一个细小的声音响了起来！）

第二课：什么是ROM的修改？

不知道你有没有看过《黑客帝国》这部电影。呵呵，ROM修改这个词就是指修改ROM文件中的任何一种数据信息。比如修改游戏中的字体（font hacking）；修改游戏中的人物等级（level hacking）；修改游戏中的文本（text hacking），或者是修改一些游戏的代码（ASM hacking）等等。 游戏的翻译，至少到目前为止似乎是一个十分流行的ROM修改形式。简单来说就是将ROM中的日文文本替换成其他语言的文本，比如中文，英文等。与这个翻译相关的就是"指针修改"，这可以为修改提供更多的文本，图形或者字库空间。通常最终涉及到的就是复杂的压缩（有一些ROM中的数据是压缩存储的，用来减少空间的需求）以及修改游戏本身的引擎来做一些诸如让窗口小一些的修改。

第三课：一个简单的练习

实际上，如果我们从一个法文、德文、或是西班牙文，呵呵，英文的ROM开始我们的练习的话，会觉得简单不少，也可以比较轻易的说明白。 好吧，现在，我们来第一次尝试进行ROM的修改。这次练习的目的就是要试着修改英文版的《时空狙击者》--Chrono Trigger (SNES)的一些文本内容。这里，我们先跳过如何制作一个文字对照表的内容（这部分，我将单独讲述）。好吧，用你喜欢的SNES模拟器工具打开ROM文件，让我们来找一些什么有意思的文本来修改。

哈，这里看起来是一个不错的地方。呵呵，好吧，现在，你需要作两件东西：一个16进制编辑器和一个文字对照表文件。你可以在我的主页上找到这样的编辑器，以及文字对照表文件。同样的，我假设你已经知道了如何使用这些工具了。好吧，我在这里使用的Thingy98。启动Thingy98并加载ROM文件和对照表文件之后，滚动窗口到这样的地址370EB0h。此时的窗口看起来是这样的：

看起来不错吧？好的，让我们开始做一点小小的修改。注意修改的范围在这个字符串之中--在这个程序中字符串的结束使用"/"来进行标记的。保存修改完的文件，重新启动模拟器加载ROM，哈哈，是不是很惊讶于你的修改天才？哈哈哈！要注意的是窗口大小的限制--在一定数量的字符之后就使用一个换行。在上面的例子里面，在写完7到8个单词之后就使用一个换行符，这样你的修改总是安全的！

呵呵，这是我的修改结果，你的可能和这个不一样

祝贺你！！现在你已经修改了《时空狙击者》的文本了！ 呵呵。上面的这个说明是一个十分简单的说明，还有很多的技术细节没有涉及到。这里只能够提示一个大体的修改概念，在下面的文档中我将进一步详细的介绍。

此文档由黑龙完成，欢迎转载，但请保持文档完整。不要对内容进行修改。此文档只可用于游戏的汉化使用，请勿用于其他用途或任何其他违反当地法律法规的用途。 游戏版权属于开发公司所有。请使用正版的卡带。

本文版权属于作者黑龙 ，欢迎参观狼的模拟器世界 http://mywolf.yeah.net


初学者ROM破解指导
----Taichou-Maiku
----翻译: hdw1978

本文中文翻译版版权属于hdw1978，如欲转载请直接和hdw1978联系。欢迎访问狼的模拟器世界。

这篇文章是写给那些不知该怎么去做的人看的，尤其是那些对于编程知之甚少甚至毫无经验的人。就让我来教你们一些ROM破解的入门基础，当然主要还是如何去翻译。请继续往下看，我还会向你们演示一些你们今后必将使用到的程序，以下是程序列表：

Hex Workshop
强有力的十六进制编辑器（译者：我更推荐Ultra-edit）
http://www.bpsoft.com/

Thingy98
一个较简单的十六进制编辑器，可以自己标记字节值所代表的意义。如：字节值为01代表字符'A'。

Naga
使用很方便的精灵编辑器，所谓的精灵就是游戏中出现的小块图画，这里就是字形的图画（译者：要用汉字的话就要将电脑字库中的字形转换为精灵图画的格式）

Search Relative
该工具可以帮助你从Rom中找出英语文字并给出正确的十六进制值。需要Vesa2图形驱动，如果你的显卡实在太老，可以到http://www.scitechsoft.com/去下载Scitech Display Doctor

Tile Layer
这实在是一个非常酷的精灵编辑器，还带有剪贴版，可以直接从其它ROM中获取字型。

snestools1.12
绝对酷的程序，可以轻松的给任何系统打补丁。

现在我已经介绍完工具了，你可以根据个人兴趣来挑选。我的选择是：Hex Workshop, Searchr, 和Naga，你可以使用其它的十六进制编辑器和精灵编辑器。

我所用来做演示的ROM是“火炎之纹章 3”（以下简称FE3），因为我对这个最熟悉，而且确实很容易让你理解。这是一个日文ROM，看了这篇文章可以使你懂得破解ROM的一般方法，而不仅仅是翻译而已。

你可以到这里获得ROM

下面进行讲解：

首先用Naga或是Tile Layer打开ROM

这就是你用Naga打开FE3后看到的屏幕，你可以改变ROM的“Layer”来发现隐藏的图片。

Layer 2

Layer 3

要改变Layer，请按1、2、3，他们分别对应着每个层（Layer）。大多数人以为能看到漂亮的精灵图案，这是错误的，你所在精灵编辑器里看到的就是这样的，不太好看，但你得学会忍耐。

现在，让我们来试着找出ROM的字形，也就是在游戏中出现的文字。通常从Naga的图片中你就能找到字形所在的位置。用上下箭头来滚动屏幕，用PageDown和PageUp来快速翻动。注意'Pos: 0'这很重要，但我们以后再讨论这个问题。现在我们的目标是找到FE3的主要字形，一直滚啊滚，很快你就会看到屏幕变成这样：

注意这是在Layer 2上，（按2）。这看起来就像一堆垃圾，但他的确是日文字分割后的样子。这就是说我们现在要把他转换成英语。

按一下5，这能使精灵们合并起来，要使他们分开的话请按4（译者：精灵在ROM中并不是连续存放的，而是先放奇数行，再放偶数行，所以看起来就是变成这样，一定要合并才能看出真面目）。现在你已经看到字形了，要修改他们也许要画上一段时间，不过却简单的很。这里有一个做好的样子：

看出这是什么了么？这是英文字母啊！你要做的就是用他们来替换英文字母。图中蓝色的点表示阴影，但大多数游戏都没有使用。字的颜色和大小在游戏中都是可变的。

如果你还不知道如何编辑字形，这很简单，你所要做的只是在要编辑的地方按一下

然后就出现了这样的屏幕，你可以改变每个点的颜色，进行翻转等等。现在试着把那些日文字变成英文字母吧。

做完后，运行一下游戏看看。你会发现你所改的字已经原封不动的出现了，但是日文和英文混在了一起。

这是因为你没有替换掉所有的字型。不过这不要紧，只要确保所有的英文字母都能很完美的显示就完全没有必要这么做，不然的话你得再去修改字形。

现在我们要使用Search Relative，使用我们已经创造的英文字母，我们可以找到他们在ROM中的地址。尽管我无法给你们看实际图片，但我会尽量说清楚的。首先，在游戏中找到连续出现你先前创造的英文字形的地方。把字母组合记下来，比如上面那张图你就要寻找ZiHP。下面开始搜索，用Search打开ROM，输入要查找的字，这里就是ZiHP。如果你试一下，你会发现有很多结果。一旦你找到了最匹配的（如果实在太难找，那么用再长一点的字母组合试试），就马上记下地址（在左边），退出。

打开Hex Wordshop，选择File|Open，打开你要编辑的ROM。再选择Edit|Goto，你会看到：

在offset的框内输入你刚才得到的地址，如果忘了，那么回去再找。现在我们输入好了，画面跳到该地址所在的地方。我现在输入的是71830，也就是FE3第一章的开始文字所在的地址，你所看到的屏幕是这样的：

现在你已经得到那些文字的位置了，回到Search，再一次查找你所选择的文字。这里仍然以ZiHP为例。注意这里是关键：看看窗口的右边，显示着'ZiHP',对不对？但他同时也对应着左边的十六进制值。为了找到他们的对应关系，我们得运用逻辑思维。

从屏幕上可以看出，Z对应的值是1A，要得到其他的字母，我们只要前后推一下就行了。十六进制是从10~19，再接下来是1A~1F，然后回到20，如此循环。所以，由于Z等于1A，那么我们可以导出Y=19，X=18。最后得到完整的列表：

| - 大写字母 - |
|01 - A 02 - B 03 - C 04 - D 05 - E 06 - F |
|07 - G 08 - H 09 - I 0A - J 0B - K 0C - L |
|0D - M 0E - N 0F - O 10 - P 11 - Q 12 - R |
|13 - S 14 - T 15 - U 16 - V 17 - W 18 - X |
|19 - Y 1A - Z |
| - 小写字母 - |
|1C - a 1D - b 1E - c 1F - d 20 - e 21 - f |
|22 - g 23 - h 24 - i 25 - j 26 - k 27 - l |
|28 - m 29 - n 2A - o 2B - p 2C - q 2D - r |
|2E - s 2F - t 30 - u 31 - v 32 - w 33 - x |
|34 - y 35 - z|
| - 数字 - |
|
|40 - 0 41 - 1 42 - 2 43 - 3 44 - 4 45 - 5 |
|46 - 6 47 - 7 48 - 8 49 - 9 |
|
| - 符号 - |
|
|36 - . 37 - , 38 - ! 39 - ? 3A - ( 3B - ) |
|3C - - 3D - ' 3E - 3F - " & - 4B |

现在，你已经知道了文字所处的位置并且知道每个字母所对应的十六进制值。破解一个ROM是很简单的！

这里是一个例子：

你可以选择Edit|Search进行十六进制搜索。我搜索单词'Mars'或者0D-1C-2D-2E作为开始的字符。

要替换只要在十六进制区域直接输入就行了。现在你可以试着发现自己的更好的方法，但我最先学会的方法就是这个了。

现在你已经成功了，你最后要做的是做一个补丁。完成这道工序的办法是：准备一个没有改过的ROM和一个已经修改完成的ROM；运行Senstool，选择Make IPS Patch,先选择原始的ROM，再是改过的ROM。这样就轻松完成了IPS补丁。

好了，现在你已经知道了破解ROM的方法（希望如此）。如果你有什么问题请写信给romhacker@hotmail.com

请记住，这只是一篇入门材料。

另外，如果你发现在某些ROM中找不到字形，那么说明他们被压缩过了。对此情况你需要更多的知识。你需要学习G65SC816的机器码，并且懂得汇编语言。http://www.classicgaming.com/epr/是一个学习汇编的好地方。

本文中文翻译版版权属于hdw1978，如欲转载请直接和hdw1978联系。欢迎访问狼的模拟器世界。


狼组Asm hacker教程(一)

作者：Alan Yang 2002/11/03

本文版权属于作者Alan，欢迎访问模拟人(狼组)，转载请注明出处

简介: 本文讲述如何对SFC游戏进行asm破解，并以实例带领读者扩充多拉基亚776的大字库，以及进行菜单汉化。本文面向具有初级汉化知识的rom hacker，阅读本文最好有一定的编程知识。

1. ASM hack是汉化一个Rom的关键

这个问题应该不用多说。很多人在找到码表，导出了文本后，都会卡在字库容量不够这一点上。要扩容，就必需应用asm hack。如果要汉化的rom是文本压缩的，或是编码不规则的，也必须要asm破解。

2. 工具

进行asm hack，最主要的工具是一个能单步跟踪程序的debugger。在SFC汉化中，zsnes DOS版提供了这一功能。尽管功能比较简单，也已经足够汉化了。注意，请到狼组下载区去找ZSNES debugger版，这是一个比较完善的版本，其它的版本有些有bug，不能dump出显存。
怎样开始跟踪呢？把zsnes.exe和要跟踪的rom放在同一目录下，进入dos窗口，打
ZSNES -d -dd [rom名]
例如，我要跟踪776，rom名是776.smc，就输入zsnes -d -dd 776.smc
这样进入跟踪状态，但是游戏的声音被关闭了，这是因为大多数声卡不支持DOS模式。如果你的声卡是ISA的，或者你想试试能不能在DOS下出声，也可以用ZSNES -d [rom名] 试试看。
进入后，你看到的应该是这样子的界面：

3. 指令介绍

以下讲一下在debug界面下可以使用的指令：

1 : 打开/关闭声音处理器的指令显示
因为我们不关心声音部分的程序，所以一进来就可以关闭它。按一下“1”键关闭，再按则打开。
要是你不关闭，显示会很混乱的
回车: 单步执行一条指令
最常用的指令
T : 连续执行n条指令
按下“T”键后，还要输入一个十进制数字，比如1000，然后程序会自动执行1000条指令后停下来。
B : 设置断点
按下“B”键后，还要输入一个16进制地址，比如863E4A，然后程序会自动执行，直到当前地址等于863E4A时才停下来。注意：如果你输入的地址永远也不会被执行到，程序就会一直运行下去。
C : 右上角的指令计数器清零
 这个还是很有用的，在每次取出存盘进度时，都要清零一下
M : 修改内存中的数值
按下“M”键后，输入一个16进制地址回车，会显示这个地址里的当前数值。你还可以修改它。按ESC则不修改。
F1 : 切换游戏界面和debug界面
按一下F1，游戏会正常运行，就像你平时玩游戏一样。再按下F1，游戏会暂停，切换回debug界面
F2 : 即时存盘
F4 : 取出即时存盘的记录
 这两个跟正常游戏时的功能完全一样
ESC : 退出
退出ZSNES，回到DOS窗口

以下指令是和我们关系不大的，可以不计：
S : 设置声音处理器断点。和我们无关
A : 修改声音处理器内存的数值。和我们无关
D : dump出声音处理器的指令/数据。和我们无关
W : Break at signal (Used only by the programmers)
2 : 打开/关闭65816的指令显示。这是我们要看的东西，可别关闭!

4. 了解界面

注意看下面的图，debugger界面主要元素：第一列是地址，表示当前执行的指令的地址；第二列是指令；后面的一堆是各寄存器的值。因为一上来忘了关闭声音处理器，有两行很不协调的指令，就是声音处理器的指令。按“1”把它关上就可以。

5. 实际操作

光说不练是不行di，我们来实际操练一把。
首先，进入DOS窗口，按一下“汉”字，让窗口不显示中文乱字符。找到zsnes的目录，输入
zsnes -d -dd 776.smc，按两次回车。（你也可以跟踪其它的rom）
现在我们在debugger模式下了。按几次回车看看，每次按回车，程序就自动执行一行。以前曾有人问我，为什么按了几百次回车，还没到显示子程序呢？要知道，正常运行的SFC游戏，每秒可以执行几十万条程序！所以从开头单步跟踪游戏是不可能的。
那我们怎么办？别忘了F1。按下F1，你会发现游戏正常运行了，只是屏幕下方多了四个数值。运行游戏一直到你打算跟踪的地方，再次按下F1。
现在我们又回到了debugger界面。你可以单步跟踪了。不过，最好先按F2存个盘，再按C清零计数器。随便跟几步试试吧。
按一下T，再输入500，回车。你会看见计数器的值增加了500，表示程序已经执行了500条指令。
按ESC退出debugger。关闭DOS窗口。你成功地跟踪了一次776的程序！(虽然什么也没得到)
等等！我们不是什么也没得到。打开“我的电脑”，找到zsnes的目录，你会发现一个叫"VRAM.DAT"的文件，大小65536字节。用TLP打开它看看，原来它就是刚才退出游戏那一刻的显存！利用这个方法，我们可以得到任何压缩的字模、图象，因为在显存中的图象都是已经解压完毕的。

6. 获得程序执行的记录

我前面让大家使用ZSNES 1.1版，是因为只有这个版可以在退出时保存当前的显存内容。然而最新版的 ZSNES除了丧失了这个功能，还是有很多改进的。首先它可以显示指令会影响的地址，这样就不需要我们自己去计算了；其次，它有一个新增的指令：
L : Dump出执行指令的顺序
可以把程序运行时的指令顺序、各寄存器的值写进一个debug.log文件，方便我们分析。按ESC键停止记录。
这个指令还是很有用的，因为目前的各种反汇编工具多少都有点bug，而这个指令所产生的程序，是肯定正确的。另外，它产生的是指令执行的顺序记录，就同我们在debugger里看到的一样，有助于分析。但是要注意一点，在我的机器上，3秒钟左右就生成了100M大的log文件！所以按完“L”后立刻按“ESC”，否则你的硬盘就全贡献给log了。
这里是生成的log文件的例子，注意方括号里已经给出了实际的地址：
82A579 DEX                    A:0010 X:0004 Y:0010 S:1F9E DB:80 D:0000 P:31 e
82A57A DEX                    A:0010 X:0003 Y:0010 S:1F9E DB:80 D:0000 P:31 e
82A57B BPL $A54C     [82A54C] A:0010 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:31 e
82A54C ASL $0B       [00000B] A:0010 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:31 e
82A54E BCC $A574     [82A574] A:0010 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:B1 e
82A550 LDA $0CE5,X   [800CE7] A:0010 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:B1 e
82A553 STA $4302,Y   [804312] A:000C X:0002 Y:0010 S:1F9E DB:80 D:0000 P:31 e
82A556 LDA $0CE6,X   [800CE8] A:000C X:0002 Y:0010 S:1F9E DB:80 D:0000 P:31 e
82A559 STA $4303,Y   [804313] A:00F6 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:B1 e
82A55C LDA $0CF5,X   [800CF7] A:00F6 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:B1 e
82A55F STA $4304,Y   [804314] A:00B9 X:0002 Y:0010 S:1F9E DB:80 D:0000 P:B1 e

应该说这两个版本的ZSNES各有利弊。我建议两个版本都保留，根据使用需要而决定用哪个。

请大家练习使用zsnes的debugger，这是我们以后最常用的工具，一定要把指令使用熟练。

课后作业：阅读本站上的几篇相关文章：
1. 汇编语言简介
2. ASM Hacking for Dummies(选读) (网)

［ 下一章 ］


狼组Asm hacker教程(二)

作者：Alan Yang 2002/11/10

本文版权属于作者Alan，欢迎访问 模拟人(狼组)，转载请注明出处

简介: 这一讲我们抛开操作，具体学习SFC的汇编语言本身。纯理论，有点枯燥哦，但是请一定仔细看明白，因为没有这个基础，后面的工作全都没法开展。

1. 什么是汇编语言(Assembler)

要了解汇编语言，先要知道机器语言。机器语言是你的电脑唯一认识的语言。虽然有很多种高级语言，象BASIC、C、PASCAL、C++、Java等等，但是用它们写的程序最后都要编译成机器语言来执行。机器语言是什么样子的？用UltraEdit打开任何一个exe文件看看，就是那样子的－－一堆乱七八糟的16进制数字。
象这样的语言，普通人自然没法读。所以就出现了汇编语言来减轻程序员的负担。不过，汇编语言与机器语言的差别不很大，基本上就是用简单的单词来表示指令。
汇编语言是与对应的CPU直接相关的，每种CPU都有自己的一套汇编指令。所以学会了8088的汇编，你只能在PC机上写汇编程序；而SFC的汇编语言叫65816，和PC机的8088汇编完全不同。
我们现在就对比C语言来讲解SFC的汇编语言。注意 对于寄存器和寻址这两部分，我只是简单讲述，你必须去仔细地读xade翻译的《65816寻址方式详解》！xade翻译得很细致了，我不想把所有话再重复一遍。而对于指令部分，我会尽量详细一些的，因为目前还没有较系统的指令翻译。
一些约定俗称的东西：本文涉及的数字，如果是16进制的，一律在前面加个“$”标志。

2. 寄存器

寄存器是CPU上的硬件单元，你可以把它看成C语言里的变量。不过这种变量可不一般：它的读写速度飞快，可以忽略不计！因为它是直接做在CPU上的。我们常用的寄存器有这么几个：

A--数字寄存器( Accumulator )
X，Y--变址寄存器( index Register )
D--直接页面寄存器( Direct Page Register )
S--栈寄存器( Stack Register )
PBR--代码段寄存器( Program Bank Register )
DBR--数据段寄存器( Data Bank Register )
P--标志寄存器( Flag Register )

A寄存器也叫累加器，因为它可以直接跟内存数据打交道，还可以进行各种加减运算和比较。X寄存器、Y寄存器和A完全一样，所以用A可以做的它们也能做，除此以外，它们还能配合某些指令，作出特殊的寻址操作。A、X、Y是使用最频繁的三个寄存器。
由于SFC的CPU是16位CPU，A、X、Y这三个寄存器都是16位寄存器。它们既可以工作在8位模式下，也可以工作在16位模式下。以A寄存器为例，在8位模式下，它的低8位起作用，参与运算；而高8位仅相当于一个静止的存储空间。此时的A相当于两个变量：低8位和高8位。这里所说的“位”是指bit，所以8位的数字的大小是0－255，即一个字节；而16位就是两个字节，大小为0－65535。这三个寄存器可以分别工作在不同模式下，比如A寄存器是16位模式，而同时X寄存器是8位模式。如何设置它们工作在什么模式下呢，这需要P寄存器了。
P寄存器是一个8位寄存器，它一共有8位，我们知道，每一位的取值只能是1或0。它也叫“状态寄存器”，因为它的每一位都代表着当前的某种状态。比如说，它的第5位为1时，表示A寄存器工作在8位模式。下面是P寄存器的各位功能图：

| 1 B E |_________|
| N V M X D I Z C |
| b7b6b5b4b3b2b1b0|

注意，我们把一个8位的16进制数的八位各起了一个名字，最高位叫b7，下一位叫b6，以此类推，最后一位叫b0。下面是各位的含义：
1 如果E=1，那么就永远为1
B Break 0 on Stack after interupt if E=1 (不太明白，反正用不上)
E 模拟标志位 0= 本机模式, 1= 模拟6502模式
注意：上面这3个标志位是与下面的头三个标志位重合的，也就是说它们共用b7/b6/b5位。不过大多数时候，我们可以不考虑，就当1 B E这三个标志位不存在。
N 负数标志位 1= 负数
V 溢出标志位 1= 溢出
M A寄存器和内存工作状态标志位 1= 8位, 0= 16位
X X/Y寄存器工作状态标志位 1= 8位, 0= 16位
D 十进制模式位 1= 十进制模式
I 禁止中断位 1= 禁止
Z 结果为零标志位 1= 结果为零
C 进位标志位 1= 有进位/无借位
注意：M和X标志位用于控制数字寄存器和索引寄存器(A，X，Y)为8比特还是16比特。这很常用。

其余寄存器请参看《65816寻址方式详解》。

3. 寻址方式

汇编语言的语句格式是这样的：
指令 操作数
即每个指令要对一个数进行操作，这个数可能在寄存器里，也可能在内存里。“寻址方式”就是讲这个指令如何去找到要操作的数的。
SFC的地址总线是24位的，也就是说，它的地址范围是从00：0000到 FF:FFFF，头两位叫段地址。把它换算成位(bit), 是32M位，所以SFC的Rom最大容量就是32M的，再大就超出地址总线的能力，也就是找不到了。那么48M的Rom是怎么来的呢？那是通过特殊的芯片，把两个24M的Rom合在一起而成的。

我们就以LDA指令为例，讲讲寻址。LDA指令的执行结果是把操作数放入A寄存器。
LDA #$A900 这个是最简单的情况，＃号表示根本不需要寻址，后面这个数就是操作数。所以LDA指令直接把$A900这个数放入寄存器。当然，假设目前A寄存器工作在16位模式。
从下面开始，其它寻址方式大都会用到D寄存器。事实上，绝大多数Rom里的D寄存器永远是0，根本不需要我们操心。
LDA $20 把内存单元00：0020里的数放入A寄存器。
LDA $890C
LDA $FF9090
LDA ($0354),Y
LDA ($C0)
LDA [$10],Y
LDA ($90,X)
LDA $A000, Y
以上都是不同的寻址方式。请具体参考65816汇编手册。xade说：“实际上新手可以一边调试代码，一边看参考，很快就熟悉的。用不着硬要把所有的寻址模式背下来，那将比边调试边看参考还痛苦。”

4. 指令

指令这部分是我讲述的重点，但这不表示寄存器和寻址方式不重要！切记要先看完xade的文档再继续。
SFC的指令可以分成几大类：
5.1 赋值和存储
5.2 数学运算
5.3 逻辑运算
5.4 分支和子程序
5.5 设置状态寄存器
5.6 寄存器之间的数据交换
5.7 堆栈操作
5.8 其它

4.1 赋值和存储

●LDA
这个指令是直接把一个数字放入A寄存器，或者从内存中取一个数字放入A寄存器，依据寻址方式的不同。它们类似于C里面的a=5这样的赋值语句。
例：A寄存器原有值为$2390，执行LDA指令后，A寄存器的内容变成$FFFF。
A: 2390 LDA #$FFFF
A: FFFF
●LDX
和LDA很相似，唯一区别是数字要放入X寄存器，而不是A寄存器。
●LDY
和LDA很相似，唯一区别是数字要放入Y寄存器，而不是A寄存器。
●STA
既然有把内存单元读入寄存器的指令，那当然也要有把寄存器内容写回内存的指令。STA指令就是把A寄存器的内容写入指定的内存单元，具体要写入哪个内存单元依据寻址方式的不同而不同。
例：A寄存器现在的值为$000F，执行STA指令后，这个值被写入内存单元$2100。
A: 000F STA $2100
●STX
和STA很相似，唯一区别是把X寄存器的内容写入指定的内存单元，而不是A寄存器。
●STY
和STA很相似，唯一区别是把Y寄存器的内容写入指定的内存单元，而不是A寄存器。
●STZ
这个指令是把指定的内存单元置零，即写入"00"到指定单元。它等同于执行
LDA #$0000
STA [指定内存单元]

4.2 数学运算

●ADC 加法指令
这个指令是直接把一个数与A寄存器中的数相加，或者从内存中取一个数与A寄存器相加，依据寻址方式的不同。相加结果放回A寄存器。它类似于C里面的 a = a + num 这样的赋值语句。但是，ADC这个指令一般叫做“带进位加法”，因为如果状态寄存器P中的“进位”位的值是1, 那么要在结果上再加1，放回A寄存器。也就是说，ADC执行的是3个数的相加：A寄存器 + 操作数 + P寄存器的进位位
例：A寄存器原有值为$2390，P寄存器的进位位是1。执行下列指令后，A寄存器的内容变成$23E1。
A: 2390 ADC #$0050
A: 23E1

●SBC 减法指令
这个指令是从A寄存器中减去一个直接数，或减去一个内存中的数，依据寻址方式的不同。结果放回A寄存器。它类似于C里面的 a = a - num 这样的赋值语句。但是，SBC这个指令一般叫做“带借位减法”，因为如果状态寄存器P中的“进位”位的值是1, 那么要在结果上再减1，放回A寄存器。也就是说，SBC执行的是3个数的相减：A寄存器 - 操作数 - P寄存器的进位位
例：A寄存器原有值为$0060，P寄存器的进位位是1。执行下列指令后，A寄存器的内容变成$000F。
A: 0060 SBC #$0050
A: 000F

●DEC
本指令很简单，就是把A寄存器或指定地址的数值减一。类似于C语言中的--操作，即a--。
例：
A: 0001 DEC A;
A: 0000
●DEX
把X寄存器的数值减一。类似DEC
●DEY
把Y寄存器的数值减一。类似DEC

●INC
本指令很简单，就是把A寄存器或指定地址的数值加一。类似于C语言中的++操作，即a++。
例：
A: 0001 INC A;
A: 0002
●INX
把X寄存器的数值加一。类似INC
●INY
把Y寄存器的数值加一。类似INC

●CMP 比较指令
这个指令执行 A寄存器 减去操作数的运算，但是结果不保存，也就是说A寄存器和操作数都不受影响，唯一受影响的是状态寄存器P。
例如：A寄存器值为$0020，执行CMP #$20后，A寄存器的值还是$0020不变；而P寄存器的Z标志位被置为"1"
A: 0020 P: --mx-i-- CMP #$20
A: 0020 P: --mx-iz- BEQ next
这个指令一般是配合条件转移语句使用，上面那两条指令的效果类似于C语言中的
if (a==20) goto next
●CPX
与CMP类似，但是是比较X寄存器 与操作数，寻址方式也只有3种。
●CPY
与CMP类似，但是是比较Y寄存器 与操作数，寻址方式也只有3种。

●ASL 算术左移
把操作数的全部二进制位，每一位向左移一位，最后一位补零。实际相当于乘以2。这个指令常被用来做简单乘法。对比想想看，对我们熟悉的一个十进制数，例如345，左移一位并补零，是3450，相当于乘以10。
例：执行下列指令后，A寄存器的值翻了一倍
A: 0010 ASL A
A: 0020
●LSR 算术右移
把操作数的全部二进制位，每一位向右移一位，最高位补零。实际相当于除以2。这个指令常被用来做简单除法。C语言中类似的指令是整除操作符a%2

●ROL 逻辑左移
跟ASL有点类似，把操作数的全部二进制位，每一位向左移一位，最高位移到P寄存器的“进位”位，而最后一位填上“进位”位的原值。
例：A寄存器值为$8000，P寄存器进位位是1，那么执行下列指令后，A的值为$0001
A: 8000 ROL A
A: 0001
●ROR 逻辑右移
类似于上面的ROL，只是方向相反。

（待续）

课后作业：阅读本站上的几篇相关文章：
1. 65816寻址方式详解xade版
3. 上面两篇文档的英文原版，英语好的朋友可以对照读
4. 宣云翻译整理的65816指令和寻址大全(选读)

［ 上一章 ］ ［ 下一章 ］


狼组Asm hacker教程(三)

作者：Alan Yang 2002/12/8

本文版权属于作者Alan，欢迎访问模拟人(狼组)，转载请注明出处

简介: 这一讲继续讲剩下的所有汇编指令，请耐心点，因为这是构建asm hack技术的坚实基础。

4. 指令(续)

4.3 逻辑运算

●AND
把A寄存器和操作数逐位进行“与”操作。与操作的公式是 1 and 1 =1 ; 1 and 0 = 0; 0 and 0 = 0
例：
A: 0010 AND #$80 ; $80 与 A寄存器 做与操作
A: 0000
●EOR
把A寄存器和操作数逐位进行“异或”操作。异或操作的公式是 1 xor 1 =0 ; 1 xor 0 = 1; 0 xor 0 = 0
异或操作有个很有意思的特点，就是可以再次对同一个数做异或操作，从而恢复最初的值。例如，A的初值是$35B8, 与$453A做异或后，值为$7082。再次与$453A做异或后，值又恢复成最初的$35B8了。利用这个特点，可以做简单的加密。
●ORA
把A寄存器和操作数逐位进行“或”操作。或操作的公式是 1 or 1 =1 ; 1 or 0 = 1; 0 or 0 = 0
●BIT
与AND指令一样，这个指令执行A寄存器与操作数的AND运算，但是结果不保存，也就是说A寄存器和操作数都不受影响，唯一受影响的是状态寄存器P。这与CMP指令类似。
例：执行下列指令后，因为结果为零，P寄存器的Z标志位置1。
A: 0010 P: ---x-i-- BIT #$0080 ;
A: 0010 P: ---x-iz-
●TRB
执行BIT指令的操作，并设置(受影响的)位。(说实话，不太明白...)
●TSB
执行BIT指令的操作，并清除(受影响的)位。(说实话，不太明白...)

4.4 分支和子程序

下列前八条语句很像C语言里的 if .... else ....语句，所以是条件分支语句。
●BEQ
如果P寄存器的Z标志位为1，则跳转到指定的地址继续执行，否则继续执行下一条。状态寄存器P的Z标志位为1时，表示前一条指令的运算结果为0。所以，本指令可以用C语言的形式表示为：if (结果==0) goto 指定地址。
例：下列程序执行后，因为符合结果为零的条件，将跳向地址$813780继续执行。
81:3750 A9 60 LDA #$60 A: 0060   往A寄存器里放入数字$60
81:3752 C9 60 CMP #$60 A: 0060   A寄存器减去$60，结果为零
81:3754 F0 24 BEQ $3780 A: 0060   因为结果为零，所以跳转至$813800继续执行
81:3756 .....
顺便再说一下指令前面的16进制数：以第一行LDA #$60为例，A9就是LDA的代码，后面的60是操作数。注意：BEQ这一行，F0是BEQ的指令代码，后面的24是什么？应该是表示跳转地址$3780吧？正确。这里提到的8条条件分支语句和后面的BRA/BRL，它们一律采用相对地址 的表示法，也就是用当前的地址加上操作数，得出要跳转的地址。在执行BEQ $3780时，当前地址已经指向了下一条指令的开始，即$3756，我们用$3756加上操作数$24，正好等于$3780，这就是要跳转的地址。这是对于向后跳转，那么如果要跳转到前面的地址呢？我们来看这个跳转指令：
9A:C3B5 F0 F7 BEQ ??
当前地址是$9AC3B7，加上操作数$F7得$9AC4AE。但是这样算是不正确的！实际上，CPU只作8位加法，即用$B7+$F7= $1AE，因为是8位加法，最前面的进位被丢弃，实际结果是$AE，再加上段地址$9AC300，得$9AC3AE。这就是向前跳转，指令实际上是：BEQ $C3AE。
因此，我们可看出，这些条件跳转只能跳到 当前地址＋$80或当前地址－$80 这样一个小范围内。
●BNE
也叫“非零跳转”。如果P寄存器的Z标志位为0，则跳转到指定的地址继续执行，否则继续执行下一条。用C语言的形式表示为：if (结果!=0) goto 指定地址。
●BCC
如果P寄存器的C标志位(进位)为0，则跳转到指定的地址继续执行，否则继续执行下一条。
●BCS
如果P寄存器的C标志位(进位)为1，则跳转到指定的地址继续执行，否则继续执行下一条。
●BMI
如果P寄存器的N标志位(负数)为1，则跳转到指定的地址继续执行，否则继续执行下一条。用C语言的形式表示为：if (结果<0) goto 指定地址。
●BPL
如果P寄存器的N标志位(负数)为0，则跳转到指定的地址继续执行，否则继续执行下一条。用C语言的形式表示为：if (结果>=0) goto 指定地址。
●BVC
如果P寄存器的溢出标志位为0，则跳转到指定的地址继续执行，否则继续执行下一条。
●BVS
如果P寄存器的溢出标志位为1，则跳转到指定的地址继续执行，否则继续执行下一条。
●BRA
无条件地跳转到指定的地址继续执行。类似C语言中的goto语句
●BRL
无条件地跳转到指定的地址继续执行。类似C语言中的goto语句，但是，它与上面的BRA有区别，在于BRA后面只能跟一个8位操作数，所以只能跳转到 当前地址＋$80或当前地址－$80 这样一个小范围内。
而BRL后面要求跟上两个8位操作数，所以能跳转到的地址范围比较大，是：当前地址＋$8000或当前地址－$8000
●JMP
直接跳转到指定地址继续执行。等同于C语言中的goto指令。JMP后面跟2个字节的地址，也叫短跳转。JMP和下面的JML与前面介绍的BRA、BRL的区别是：JMP后面的操作数是绝对地址；而BRA则是相对地址。举例：
81:3754 BRA $0120
则跳转到81:3876处，而
81:3754 JMP $0120
则跳转到81:0120处。
●JML
直接跳转到指定地址继续执行。JML后面跟3个字节的地址，也叫长跳转。
●JSR
转子程序。类似C语言中的调用其它函数。
●JSL
转子程序。与JSR的区别是后面跟3个字节的地址。
●RTS
从子程序返回。对应JSR调用。
●RTL
从子程序返回。对应JSL调用。
●RTI
仅用于从中断处理程序返回。

4.5 设置状态寄存器
状态寄存器P是很重要的，原因很简单，有很多条件分支语句要依赖于它的值进行判断；还有A、X、Y三个寄存器是工作在8位还是16位，也要靠它的某几位来判断。以下语句就是用来更改P寄存器的状态。
●SEC
把进位(carry)位置1。如下图，进位位是P寄存器的最低位(b0位)。
－－－－－－－c
●CLC
清除进位位，把进位(carry)位置0。
●SED
把十进制(decimal)位置1。如下图，十进制位是P寄存器的b3位。
－－－－d－－－
●CLD
把十进制(decimal)位置0。
●SEI
把中断(interrupt)位置1。如下图，中断位是P寄存器的b2位。
－－－－－i－－
●CLI
清除中断位，把中断位置0。
●CLV
清除溢出(overflow)位，把它置0。如下图，溢出位是P寄存器的b6位。
－v －－－－－－
●SEP
SEP指令不像上面几条指令，它可以一次把几个标志位置1。程序里常用它来切换到8位工作模式。例：
P: －－－－－i－－ SEP #$30 ;设置内存访问、A寄存器和X、Y寄存器都工作在8位模式
因为操作数$30，分解成二进制时，是00110000，所以会把P寄存器的b5、b4位都置为1。而b5、b4这两位正是控制A寄存器和X、Y寄存器的工作模式。b5为1时，表示内存访问、A寄存器都工作在8位模式；b4为1时，表示X和Y寄存器工作在8位模式下。(参考上一章的P寄存器说明)
执行后，P寄存器的结果是：
P: －－m x －i－－
●REP
REP与SEP类似，可以同时把几个标志位置0。程序里常用它来切换到16位工作模式。
例：
P: --mx-i-- REP #$30 ; 设置内存访问、A寄存器和X、Y寄存器都工作在16位模式
P: -----i--

4.6 寄存器之间的数据交换
CPU内部的几个寄存器并不是“老死不相往来”的，实际上它们之间常常要交换数据。因为都在CPU内部，不需要访问地址总线，寄存器间交换数据的速度相当快。
●TAX
把A寄存器的值写入X寄存器，类似C语言中的赋值语句 x=a。
例：
A: 6E30 X: 0050 TAX
A: 6E30 X: 6E30
●TAY
把A寄存器的值写入Y寄存器，类似C语言中的赋值语句 y=a。
●TCD
把A寄存器的值写入DP寄存器，即直接地址页寄存器(Direct Page)
●TCS
把A寄存器的值写入堆栈寄存器(Stack)
●TDC
把DP(直接地址页)寄存器的值写入A寄存器
●TSC
把堆栈寄存器的值写入A寄存器
●TSX
把堆栈寄存器的值写入X寄存器
●TXA
把X寄存器的值写入A寄存器，类似C语言中的赋值语句 a=x。
●TXS
把X寄存器的值写入堆栈寄存器
●TXY
把X寄存器的值写入Y寄存器，类似 y=x
●TYA
把Y寄存器的值写入A寄存器，类似C语言中的赋值语句 a=y。
●TYX
把Y寄存器的值写入X寄存器，类似C语言中的赋值语句 x=y。
●TXY
把X寄存器的值写入Y寄存器，类似C语言中的赋值语句 y=x。
●XBA
把A寄存器的高8位与低8位互换。A寄存器是16位寄存器，如下面例子里，A寄存器的值是5690，56就是高8位；90就是低8位。
例：
A: 5690 XBA
A: 9056
●XCE
把P寄存器的进位位(carry)与模拟位(emulation)互换。用来切换本机模式与模拟模式。65816 CPU是16位CPU，它是6502 CPU的升级，所以也可以模拟6502 CPU，此时它工作在8位模式，我们叫“模拟模式”。相对的，“本机模式”就是说现在我们工作在16位模式下，发挥全部65816 CPU的能力。进位位是b0，模拟位是b5。下面是P寄存器构成，可以看出：模拟位与16位、8位访问标志位共用b5。
| 1 B E |_________|
| N V M X D I Z C |
| b7b6b5b4b3b2b1b0|

4.7 堆栈操作
想必学过数据结构的朋友都知道“堆栈”这种存储机制。SFC也有这样一个堆栈，并且有一个S寄存器作为指针，指向堆栈的顶部。这个堆栈很重要，记录着每次调用子程序时的地址，等子程序执行结束，再根据堆栈里记录的地址，回到调用处继续执行。下面的指令都是操纵堆栈的。
●PEA
把一个16进制的地址压进栈，栈指针－2。例：
PEA $6000 ; 把值$6000压入栈
注意这个指令很容易误导：以上面为例，压入栈中的是“6000”这个立即数，而不是地址$6000中的数值。
●PEI
按照间接寻址，把操作数压进栈。例：
PEI ($00) ；把地址$00开始的16位数值压入栈
●PER
把后面地址中的数值压进栈。例：
PER $8000
上面指令把地址$8000中的数(假设是$0050)压进栈。则压入栈中的是#$0050。
●PHA
把A寄存器的值压入栈。
●PLA
从栈顶弹出一个16位数放入A寄存器。它和上面的PHA是一对指令。PHA把数进栈，PLA把数出栈。
●PHB
把地址段寄存器(Data Bank)的值压入栈。
●PLB
从栈顶弹出一个8位数放入地址段寄存器。
●PHD
把直接页寄存器(Direct Page)的值压入栈。
●PLD
从栈顶弹出一个8位数放入DP寄存器。
●PHK
把程序段寄存器(Program Bank)的值压入栈。
●PHP
把状态寄存器P的值压入栈。
●PLP
从栈顶弹出一个8位数放入状态寄存器。
●PHX
把X寄存器的值压入栈。
●PLX
从栈顶弹出一个16位数放入X寄存器。
●PHY
把Y寄存器的值压入栈。
●PLY
从栈顶弹出一个16位数放入Y寄存器。

4.8 其它

这里介绍的指令是无法归类的指令，好在一般没有用。了解一下就行了。
●BRK
中断程序的执行。效果类似于死机。
●COP
协处理器产生一个软件中断。
●MVN
●MVP
块移动。这个东东很不错，可以快速拷贝大量数据。不过，没有DMA方式好用。但是你还是得弄明白它，以防以后遇到。
MVN $<8-bitexp>,$<8-bit exp>
MVP $<8-bitexp>,$<8-bit exp>
这是我到目前为止看到的最怪异的操作结构。他的基本功能是将一块数据从内存的一个地方移到另一个地方。第一个8位表达式指明目的地的段地址，第二个8位表达式指明数据源的段地址。X寄存器中保存数据源的16位地址，Y寄存器中保存目的地的16位地址，A寄存器中存放要移动的字节数。
例如：
REP #$30 ; 设定数字寄存器和索引寄存器工作在16位模式
LDY #$8000 ; 写立即数8000到Y寄存器
LDX #$9000 ; 写立即数9000到X寄存器
LDA #$0005 ; 写立即数5到A寄存器
MVP $80, $A0; 开始块移动
功能：从$80:8000移动5个字节到$A0:9000
●NOP
什么也不做，让CPU空转一个时钟周期。调试时，可以用这个指令来屏蔽某条指令，观察运行效果。
●STP
停止时钟。
●WAI
等待下一次中断。

5. 下一步
好，到此为止SFC 65816的全部指令集都讲完了。配合xade的寻址和寄存器部分，你应该可以看懂程序了。当然，现在你如果读程序的话，速度会很慢；几乎每一条指令你都要查说明才能弄明白它的功能。别急，任何高手都是这样一点点熟练起来的。耐心地试试读一小段程序吧。下一讲我将带领大家读懂一段程序，并进一步介绍SFC的硬件。

推荐一下几个很有用的文档：
1. 65816指令速查手册
这个手册是英文的，但是很好懂；可以立即查出指令的用法，功能，甚至还有对应的16进制码。我在写程序时向来是手工查这个手册，把程序“编译”成16进制的可执行码的。
2. 65816寄存器、寻址速查手册(网)
这是我刚开始学65816时做的速查表，有全部寻址方式、寄存器介绍和一些指令。我觉得还算方便，就自卖自夸一回。
（待续）

课后作业：
随便找个rom跟踪一下，试着读懂一段程序。读不懂没关系；但是至少每个单个的指令都应该能看懂。实在不行的话，也可以等到看完下一讲再说。

［ 上一章 ］ ［ 下一章 ］


狼组Asm hacker教程(四)

作者：Alan Yang 2003/01/11

本文版权属于作者Alan，欢迎访问模拟人(狼组)，转载请注明出处

简介: 前面讲述了SFC的全部汇编指令，现在我们来试着读一个小程序看看。

1. 试着看懂第一个程序

好，了解了全部SFC汇编指令，你至少能看懂一些简单的程序。我们先来读一个小程序，顺便了解一下汇编语言的运行思路。

829000	REP #30		;设置内存访问、A寄存器和X、Y寄存器都工作在16位模式
829002	LDA $50		;取出内存单元$50, $51的值，放入A寄存器
829004	PHA		;把A寄存器的值存入堆栈中
829005	LDA $51		;取出内存单元$51, $52的值，放入A寄存器
829007	PHA		;把A寄存器的值存入堆栈中
829008	LDA #$3A00		;
82900B	STA $50		;值$00放入$50单元，把值$3A放入$51单元
82900D	LDA #$7E3A		;
829010	STA $51		;把值$3A放入$51单元，值$7E放入$52单元
829012	LDY #$0100		;把初值$0100放入Y寄存器，这是循环初值
829015	LDA #$0000		;A寄存器清零
829018	CLC		;状态寄存器的"进位"位清零
829019	ADC [$50],Y	;*关键的一句：先取出$50 - $52单元的值，是$7E3A00；加上Y寄存器的值$100，
            是$7E3B00。这是计算出了真正的操作地址，不明白的，请参考xade翻译的寻址方式
            那部分。下一步是把地址$7E3B00的值加上A寄存器的值再写入A寄存器。
82901B	DEY		;Y寄存器的值减一
82901C	BNE $9019		;如果Y寄存器的值不是0，就跳转到$829019处继续执行
82901E	TAX		;把A寄存器的值存入X寄存器
82901F	PLA		;从堆栈中退处一个数
829020	STA $51		;存入$51内存单元
829022	PLA		;从堆栈中退处一个数
829023	STA $50		;存入$50内存单元
上面这个程序，其实用C语言写出来，就是这个样子的：(用M数组代表内存单元)
push(M(50));	//push函数用来把一个值压进栈
push(M(51));
M(50)=00; M(51)=0x3A; M(52)=0x7E;
Y=0x100;
A=0;
do {
    A = A + M( 0x7E3A00 + Y );
    Y--;
} while (Y>0);
X=A;
pop(M(51));	//pop函数用来把一个值弹出栈，并存入给定的变量中
pop(M(50));
也就是说，这段程序的作用是把内存单元$7E3A01到$7E3B00,一共255个内存数值加起来。
下面我们来仔细分析一下程序：
829000 REP #30 ;设置内存访问、A寄存器和X、Y寄存器都工作在16位模式
；所以，下面的所有语句，都是16位的操作，例如下面的LDA $50, 其实是把$50,$51两个单元的数都取出来，而不是只取出$50单元！因为一个内存单元只是一个8位数
829002 LDA $50 ;取出内存单元$50, $51的值，放入A寄存器
829004 PHA ;把A寄存器的值存入堆栈中
829005 LDA $51 ;取出内存单元$51, $52的值，放入A寄存器
829007 PHA ;把A寄存器的值存入堆栈中
;上面4行，其实就是把$50,$51,$52三个单元的值放入堆栈中保存起来，因为我们要借用用$50-$52单元，但是万一原先的$50-$52单元存储着重要的数据怎么办？所以我们把它们原来的值先放到堆栈里保存；等使用完了，再从堆栈里恢复。
829008 LDA #$3A00 ;
82900B STA $50 ;
82900D LDA #$7E3A ;
829010 STA $51 ;
上面4行，是把7E3A00写入$50-$52单元，注意“先低位后高位”的储存格式，即最高位的7E要存入$52单元；最低位的00要存入$50单元
829012 LDY #$0100 ;Y寄存器赋初值$100
829015 LDA #$0000 ;A寄存器充当累加器，先清零
829018 CLC ;状态寄存器的"进位"位清零，以防上次计算有进位，影响了我们的计算
829019 ADC [$50],Y
*这一句是关键。注意它使用的寻址方式，要经过两次计算得出真正的操作地址：
第一次：[$50],方括号表示取出$50开始的3个连续单元内的地址，即7E3A00;
第二步，把上一步取出的地址(7E3A00)加上Y寄存器的值(100).
最后得出真正的操作地址7E3B00. 注意，下一次执行到这里时，[$50]的值不变，而Y寄存器的值变化了，从而得出的操作地址也会变化。
82901B DEY ;Y寄存器减一。这样下次计算出的操作地址将是7E3AFF,再下次是7E3AFE....
82901C BNE $9019 ;Y寄存器不得零的话，转回829019去执行。
829019－82901C这三句构成了一个循环，它会循环$FF(255)次，每次给A寄存器加上一个数，相当于
7E3B00 + 7E3AFF + 7E3AFE + ....+ 7E3A02 + 7E3A01
82901E TAX ;把最后的计算结果存入X寄存器 ；因为A寄存器我们马上要用来恢复$50-52的数值了
82901F PLA ;
829020 STA $51 ;
829022 PLA ;
829023 STA $50 ;
；上面4行用来恢复$50-$52的原先的值。注意堆栈的使用原则是“后进先出”，所以恢复的顺序应当是反着的，先恢复$51,$52, 再恢复$50,$51

请大家一定要彻底理解明白这个小程序。如果连这个小东西还不能完全理解的话，是没办法做下面的破解工作的。

2. 读程序的一般方法
  • 要掌握常见的寻址方式。象上面用到的ADC [$50],Y，读懂这句话的核心就是理解这种寻址方法。对不确定的，或不熟悉的寻址方法，一定要勤翻手册，了解明白。另外，用ZSNES边跟踪边阅读时，也可以利用ZSNES列出的各寄存器的值，来印证你的理解对不对。
  • 对不认识的指令，查阅前两讲的内容。随着你读的程序越来越多，很多指令就已经记在心中了，读程序的速度也会越来越快。但是在刚开始的时候，读一条语句就翻一次手册的事儿很常见的。
  • 当你准备读一个真正的游戏时，你是不可能把它从头读到尾的，因为有几乎海量的程序。所以，我们只针对我们汉化有用的地方去读。具体的做法是：
    zsnes -d 776.smc (这里以找776对话为例）首先进入debug模式；
    按F1，此时游戏正常执行。当执行到有人说话时，按F1切换回debug画面。
    这时再开始试着读程序。

课后作业：试着读一些程序，不用管他是负责什么功能的，只要把每一句话都弄明白就可以。

［ 上一章 ］ ［ 下一章 ］


metore的rom_hack心得

作者：metore 2003/02/05

本文版权属于作者metore，欢迎访问模拟人(狼组)，转载请注明出处

嗯，从开始破解皇2到现在也有近半年了，其间也多多少少破解了几个游戏，趁现在比较清闲，准备写一点自己的心得体会，希望能对有志于加入我们汉化行列的朋友们提供一些帮助！^^

先说一下我进行rom_hack时的基本步骤：
1、找到手柄输入子程序
2、找到并破解写屏子程序
3、根据破解写屏子程序得到的字模在内存中的地址，找到字模读取、转换子程序
4、根据字模读取子程序找到rom中字模的储存地址（也就是字库）
5、根据字模转换子程序做出码表
6、根据字模转换子程序得到的rom文本的地址，找到并破解文本读入子程序
7、根据文本读入子程序自己写一个文本导出程序，导出规范格式的文本
接下来就是翻译及字库扩容、破解菜单等asm_hack的工作了。

ps：对于那种利用tlp找字库，用相对搜索找文本的方法，老实说我并不是很赞成，虽然开始的时候会很快，但到后期字库扩容、文本扩容的时候就很困难了，而且还很可能出现大量的bug。并且一旦遇到有压缩的字库或是文本，这种方法就一筹莫展了。还是老老实实的进行rom_hack比较保险（这个只是我个人的看法，大家千万不要砸我啊^^）

呀，一不小心说了这么多废话~~打住^^b
咳咳，下面进入正题：
首先是找手柄输入子程序
这个子程序可以说是所有游戏的最基本也是最重要的子程序了，当然也是我们破解时所要最先破解的程序，其他的字模读入、写屏等子程序都需要通过它来找到入口。

嗯～今天我们以星海为例子吧，用－d命令打开rom，按F1进入游戏，然后一阵狂按跳过开头，直到对话框出现。好，现在大家应该都知道只要按下A键，下面一句对话就会出现了吧，先按F2保存游戏，再按F1进入debug模式，按L，等3秒种（具体时间要根据你机器的速度来定）再按ESC，这时会生成一个debug.log文件，里面就是dump出来的游戏程序了，当然手柄输入子程序也在里面啰！

当然，我们不需要把这里面的程序都读懂，只要简单的搜索一下就行了。在65816芯片中有专门用来存放手柄状态的寄存器$4218、$4219，所有的跟手柄操作有关的程序都要用到它的。在debug.log文件中搜索$4218...好，找到了！

源码:--------------------------------------------------------------------------------
C21235 LDA $4218 [004218] A:0080 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:83 e
C21238 STA $00 [000000] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
C2123A EOR $C1 [0000C1] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
C2123C AND $00 [000000] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
C2123E STA $C3 [0000C3] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
C21240 LDA $00 [000000] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
C21242 CMP $C1 [0000C1] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
C21244 STA $C1 [0000C1] A:0000 X:80C0 Y:0040 S:01F9 DB:00 D:0000 P:03 e
--------------------------------------------------------------------------------

这段程序就是把手柄寄存器的状态读出，再保存在内存中（$4218、$4219是只读寄存器，我们只能修改它们在内存中的映象），那么$00、$C1、$C3到底那个才是有用的呢，这个只要简单的试一下就知道了（当然，如果你能把这段程序读懂那是最好了^^），按M分别把它们的值改为80h（手柄的键值可以参看yoshi写的寄存器资料，80h代表A键），再按F1继续游戏，发现当C3的值为80时，下面一句对话出现了！这就是说在此游戏中手柄的输入储存在$C3、$C4中（$4218对应$C3，$4219对应$C4），赶快记录下来吧^^

接着是写屏子程序：
写屏子程序嘛就是把内存ram中的数据传到显存vram中的那部分程序，我们在游戏中看到的所有东西，包括字模、图形都是通过这个程序显示出来的。一般说来都是用DMA把ram中某个地址段的数据传输到vram中一个指定的地址段，举个简单的例子吧：

源码:--------------------------------------------------------------------------------
LDX #$E000
    STX $4302 源地址的段内偏移地址
    LDA #$7E
  STA $4304 源地址的段地址
    LDX #$200
    STX $4305 需要传输的字节数
    LDA #$18
    STA $4301 这个是告诉DMA把数据写入显存vram
    LDA #$01
    STA $4300 每次写入2个字节
LDX #$6000
STX $2116 这个是vram中的目标地址
    LDA #$01
    STA $420B 开始DMA传输--------------------------------------------------------

上面这段小程序的作用就是把ram中从7EE000h到7EE200h这512个字节通过DMA传输到vram中从6000开始的地址段处（关于DMA的资料还有很多，我这里就不多说了，请大家查阅相关资料~~），我在这里想说的就是$2116这个目标地址寄存器！它是所有的写屏子程序都会用到的，我们只要死死地盯住它就行了^^[

但是还有一个小问题大家要注意的，就是通过搜索$2116找到的不光是字模的写屏子程序，还有可能是图形的！虽然我们是在对话的时候跟踪的，但有的游戏在对话的同时人物也会动...对于这种情况，建议大家用bgmapper、tlp等工具打开及时存档文件，凭着直接观察法来找出文本数据在vram中的地址。还是以星海为例吧，用tlp打开so.zst文件，把format设为gameboy，goto：20C13h（vram的范围20C13h～21C12h），然后就往下找吧~~很快就发现了（附图）!地址为2BE93h，用hex2sens转换成sens的地址就是5940，这就是说只有当$2116的值为5940时，才是真正的字模的写屏子程序。

OK！接着上次的进度，把$C3的值改成80h，按L导出程序...然后搜索$2116

源码:--------------------------------------------------------------------------------C20D2E LDX #$6000 A:0601 X:E000 Y:0040 S:01F9 DB:00 D:0000 P:A0 e
C20D31 STX $2116 [002116] A:0601 X:6000 Y:0040 S:01F9 DB:00 D:0000 P:20 e <--这个不是
......
C20DFF LDX #$3E00 A:06FE X:0400 Y:0040 S:01F9 DB:00 D:0000 P:21 e
C20E02 STX $2116 [002116] A:06FE X:3E00 Y:0040 S:01F9 DB:00 D:0000 P:21 e <--这个也不是
......
C20DBE LDX #$3C00 A:06FC X:0400 Y:0040 S:01F9 DB:00 D:0000 P:21 e
C20DC1 STX $2116 [002116] A:06FC X:3C00 Y:0040 S:01F9 DB:00 D:0000 P:21 e <--还是不对
......
......
......
C21089 LDX $03EB [0003EB] A:007E X:0340 Y:0040 S:01F7 DB:00 D:0000 P:20 e
C2108C STX $2116 [002116] A:007E X:5940 Y:0040 S:01F7 DB:00 D:0000 P:20 e <--终于找到了T_T--------------------------------------------------------------------------------

这个就是文本写屏子程序

源码:--------------------------------------------------------------------------------C20F75 JSR ($1063,X) [C21077] A:0002 X:0002 Y:0040 S:01F9 DB:00 D:0000 P:20 e <--C21077就是子程序入口，记录下来吧！
C21077 LDX $03E7 [0003E7] A:0002 X:0002 Y:0040 S:01F7 DB:00 D:0000 P:20 e 地址03E7里面存放的就是字模数据在ram的地址了
C2107A STX $4302 [004302] A:0002 X:A353 Y:0040 S:01F7 DB:00 D:0000 P:A0 e
C2107D LDA $03E9 [0003E9] A:0002 X:A353 Y:0040 S:01F7 DB:00 D:0000 P:A0 e
C21080 STA $4304 [004304] A:007E X:A353 Y:0040 S:01F7 DB:00 D:0000 P:20 e 字模数据的地址7EA353
C21083 LDX #$0340 A:007E X:A353 Y:0040 S:01F7 DB:00 D:0000 P:20 e
C21086 STX $4305 [004305] A:007E X:0340 Y:0040 S:01F7 DB:00 D:0000 P:20 e 字模数据的总长度是340h个字节
C21089 LDX $03EB [0003EB] A:007E X:0340 Y:0040 S:01F7 DB:00 D:0000 P:20 e
C2108C STX $2116 [002116] A:007E X:5940 Y:0040 S:01F7 DB:00 D:0000 P:20 e 目标地址是存放在03EB中的
C2108F LDA #$01 A:007E X:5940 Y:0040 S:01F7 DB:00 D:0000 P:20 e
C21091 STA $420B [00420B] A:0001 X:5940 Y:0040 S:01F7 DB:00 D:0000 P:20 e 开始传输
C21094 RTS A:0001 X:5940 Y:0040 S:01F7 DB:00 D:0000 P:20 e 子程序结束--------------------------------------------------------------------------------

到这里，我们就找到了字模数据在内存ram中的地址7EA353h，接着就可以破解字模转换子程序了！
（补充说明：实际上在星海的及时存档中，我们可以用tlp直接找到字模数据在ram中的地址的，但这只是偶然因素,有些游戏根本就找不到，只能通过$2116下手，所以我还是把这种方法写下来了。当然，大家实际破解的时候可以灵活掌握嘛^_^）

字模转换子程序：
这个子程序的作用就是根据字模代码找到rom中对应字模数据的地址，其工作原理为：通过一定的算法对字模代码进行转换，得到一个地址，然后根据地址从rom中读取相应的数据。举个简单的例子：假设有码表“0101=八”，其字模长度为32个字节，字库首地址为2E0000h，码表范围0000～4000，采用最简单的算法；那么对于字模代码“0101”有：0101h×32＝2020h（这是这个字模在字库中的偏移地址），然后加上字库首地址：2020h＋2E0000h＝2E2020h，就是说从2E2020h～2E203Fh这32个字节的数据就是“八”的字模数据。最后把这32个字节写到显存中，我们就可以看见“八”这个字了。

（ps：关于字库的扩容，首先当然必须找到其字模转换的算法啦，然后对其算法进行一定的修改。根据我的经验，对于采用hi-rom的游戏的字库扩容要简单一些，因为其数据段长度为65536个字节（0000h～FFFFh），就算对于比较大的32字节的字模也可以装下2048个，这已经基本上够用了，对于这种字库的扩容，我们只需另找一个空白数据段装入新的字库，然后把字模转换子程序中的字库首地址改成新字库的首地址就可以了。但是SFC的大多数游戏还是用的low-rom格式，其一个数据段的长度只有32768个字节（0000h～7FFFh），这时我们就必须要用到2～3个数据段了，也就是说我们必须要把新字库拆成2～3个小的新字库才行。对于这种字库，我们可以在其字模转换子程序中加入一些判断、分支，如代码在0000～03FF之间的其字库首地址设为2E0000h，代码在0400～7FFF之间的其字库首地址设成2E8000h等等，虽然麻烦一点，但也是可以搞定的。字库扩容最重要的还是在于找到字模转换的算法，并且完全读懂它，这样才能方便修改、不会出现bug。）

好，基本原理掌握了之后，我们就可以开始找子程序代码了，基本的思路就是盯着字模数据在内存ram中的地址啦（像星海就是我们上次找到的地址7EA353h），然后跟踪看它的内容什么时候被改变，那个改变其内容的程序就是我们要找的字模转换子程序了。跟踪时可以采用的方法就很多了，你可以不断的用M指令检查它的内容（注意字模数据的范围，像星海就是从7EA353h～7EA692h，里面有些地址可能永远都是00h或FFh，一定要找一个会改变的地址来跟踪），也可以把程序导出之后搜索地址（我最常用的就是这种方法，没办法~太懒了^^b 不过搜索时也是需要一定的技巧的，如当7EA353搜索不到，那么可以试试A353，7EA354等比较接近的地址~~反正自己多想想啦，一定会有办法的^^）

还是来看实际的例子吧，通过不断的搜索加跟踪，我们在18万条指令之后找到了如下代码：

源码:--------------------------------------------------------------------------------C808BC LDX $03E7 [0003E7] A:5900 X:8943 Y:63D0 S:01EE DB:00 D:0000 P:22 e
C808BF STX $2181 [002181] A:5900 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:A0 e //这里用到了wram
C808C2 STZ $2183 [002183] A:5900 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808C5 LDA #$08 A:5900 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808C7 STA $4300 [004300] A:5908 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:20 e
C808CA LDA #$80 A:5908 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:20 e
C808CC STA $4301 [004301] A:5980 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808CF LDX #$8000 A:5980 X:A353 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808D2 STX $4302 [004302] A:5980 X:8000 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808D5 LDA #$C0 A:5980 X:8000 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808D7 STA $4304 [004304] A:59C0 X:8000 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808DA LDX #$0340 A:59C0 X:8000 Y:63D0 S:01EE DB:00 D:0000 P:A0 e
C808DD STX $4305 [004305] A:59C0 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:20 e
C808E0 LDA #$40 A:59C0 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:20 e
C808E2 SEI A:5940 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:20 e
C808E3 BIT $4212 [004212] A:5940 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:24 e
C808E6 BEQ $08E3 [C808E3] A:5940 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:26 e
......
C808E3 BIT $4212 [004212] A:5940 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:26 e
C808E6 BEQ $08E3 [C808E3] A:5940 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:64 e
C808E8 LDA #$01 A:5940 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:64 e
C808EA STA $420B [00420B] A:5901 X:0340 Y:63D0 S:01EE DB:00 D:0000 P:64 e //这段程序我还没有完全读懂，只能确定其效果是把对话清空--------------------------------------------------------------------------------

但是...这个程序的作用是把从7EA353开始的340h个字节的数据全部清空（就是全部置为00h），然后再通过我们上次找到的写屏程序写入显存，这就是说其实这只是一个清屏程序！...T_T
但是不要灰心，一般来说字模数据的存放位置都是固定的，接下来再向这个地址段写入数据的程序就应该是我们要找的字模转换程序了！接着往下找......
终于～～我们在139万条指令之后找到了真正的字模转换程序！（T_T...大概2万条指令dump出来就有1M）

源码:--------------------------------------------------------------------------------C26562 LDY #$0000 A:0010 X:0000 Y:FFFF S:01EF DB:7E D:0300 P:01 e
C26565 LDA $0361,Y [7E0361] A:0010 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C26568 ORA $A353,X [7EA353] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C2656B STA $A353,X [7EA353] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C2656E LDA $0381,Y [7E0381] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C26571 ORA $A373,X [7EA373] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C26574 STA $A373,X [7EA373] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C26577 LDA $03A1,Y [7E03A1] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C2657A ORA $A393,X [7EA393] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e
C2657D STA $A393,X [7EA393] A:0000 X:0000 Y:0000 S:01EF DB:7E D:0300 P:03 e--------------------------------------------------------------------------------

从上面可以看出7EA353的数据是由7E0361转换来的，我们只要抓住这个数据流向前面顺藤摸瓜就行了。实际上字模数据在前面的程序中经过了很多次转换，包括从1BPP转换成2BPP、格式对齐等等，不过我们可以不用管这些东西，我们只要知道它是根据什么代码从那里把字模数据读出来的！下面就是我找到的程序：

源码:--------------------------------------------------------------------------------C239E1 BMI $39E6 [C239E6] A:0081 X:051A Y:0000 S:01F6 DB:7E D:0000 P:A0 e //C239E6就是字模转换程序的入口了
C239E6 REP #$20 A:0081 X:051A Y:0000 S:01F6 DB:7E D:0000 P:A0 e
C239E8 LDA $0000,X [7E051A] A:0081 X:051A Y:0000 S:01F6 DB:7E D:0000 P:80 e //读入文本中的字模代码（注意！这个地址就是文本数据在rom中的地址了，以后我们还会用的到它的）
C239EB INX A:697D X:051A Y:0000 S:01F6 DB:7E D:0000 P:00 e
C239EC CMP #$FFFF A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e //从这里就开始进行字模代码转换了
C239EF BEQ $3A1F [C23A1F] A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e
C239F1 CMP #$FFFE A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e //先判断是不是FFFFh和FFFEh这两个特殊代码
C239F4 BEQ $3A10 [C23A10] A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e
C239F6 BIT #$0080 A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e //如果低位小于80h，那么字模代码就是一个字节的（如果大于80h，就是2个字节）
C239F9 BEQ $3A04 [C23A04] A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:02 e
C23A04 AND #$00FF A:697D X:051B Y:0000 S:01F6 DB:7E D:0000 P:02 e //取出一个字节
C23A07 STX $03D5 [7E03D5] A:007D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e
C23A0A JSR $3A4D [C23A4D] A:007D X:051B Y:0000 S:01F6 DB:7E D:0000 P:00 e
C23A4D CMP #$0005 A:007D X:051B Y:0000 S:01F4 DB:7E D:0000 P:00 e //如果大于05h，就是字模代码，小于05h的是控制代码（目前知道的有01h：结束符，02h：开始符）
C23A50 BCC $3A97 [C23A97] A:007D X:051B Y:0000 S:01F4 DB:7E D:0000 P:01 e
C23A52 CMP #$4001 A:007D X:051B Y:0000 S:01F4 DB:7E D:0000 P:01 e
C23A55 BCS $3A97 [C23A97] A:007D X:051B Y:0000 S:01F4 DB:7E D:0000 P:80 e
C23A57 SEC A:007D X:051B Y:0000 S:01F4 DB:7E D:0000 P:80 e
C23A58 SBC #$0005 A:007D X:051B Y:0000 S:01F4 DB:7E D:0000 P:81 e //这里减去05h，得到字模在字库中的序号（字库中可没有控制符^^）
C23A5B PHA A:0078 X:051B Y:0000 S:01F4 DB:7E D:0000 P:01 e
C23A5C LDA $048B [7E048B] A:0078 X:051B Y:0000 S:01F2 DB:7E D:0000 P:01 e
C23A5F CMP #$0009 A:0000 X:051B Y:0000 S:01F2 DB:7E D:0000 P:03 e
C23A62 BEQ $3A6A [C23A6A] A:0000 X:051B Y:0000 S:01F2 DB:7E D:0000 P:80 e
C23A64 PLA A:0000 X:051B Y:0000 S:01F2 DB:7E D:0000 P:80 e
C23A65 JSR $63A0 [C263A0] A:0078 X:051B Y:0000 S:01F4 DB:7E D:0000 P:00 e //开始计算字模数据在字库中的偏移地址
C263A0 ASL A A:0078 X:051B Y:0000 S:01F2 DB:7E D:0000 P:00 e
C263A1 ASL A A:00F0 X:051B Y:0000 S:01F2 DB:7E D:0000 P:00 e
C263A2 ASL A A:01E0 X:051B Y:0000 S:01F2 DB:7E D:0000 P:00 e
C263A3 ASL A A:03C0 X:051B Y:0000 S:01F2 DB:7E D:0000 P:00 e
C263A4 ASL A A:0780 X:051B Y:0000 S:01F2 DB:7E D:0000 P:00 e //由于字库中一个字模数据占32个字节，所以把字模的序号乘以32，
C263A5 TAY A:0F00 X:051B Y:0000 S:01F2 DB:7E D:0000 P:00 e //F00h就是字模数据的偏移地址了
C263A6 PHD A:0F00 X:051B Y:0F00 S:01F2 DB:7E D:0000 P:00 e
C263A7 LDA #$0300 A:0F00 X:051B Y:0F00 S:01F0 DB:7E D:0000 P:00 e
C263AA TCD A:0300 X:051B Y:0F00 S:01F0 DB:7E D:0000 P:00 e
C263AB SEP #$20 A:0300 X:051B Y:0F00 S:01F0 DB:7E D:0300 P:00 e
C263AD PHB A:0300 X:051B Y:0F00 S:01F0 DB:7E D:0300 P:20 e
C263AE LDA #$FF A:0300 X:051B Y:0F00 S:01EF DB:7E D:0300 P:20 e //这里把字库所在的数据段设为当前段（这就是说字库首地址为FF0000h，转换成hex地址就是3F0200h）
C263B0 PHA A:03FF X:051B Y:0F00 S:01EF DB:7E D:0300 P:A0 e
C263B1 PLB A:03FF X:051B Y:0F00 S:01EE DB:7E D:0300 P:A0 e
C263B2 REP #$20 A:03FF X:051B Y:0F00 S:01EF DB:FF D:0300 P:A0 e
C263B4 LDA $0000,Y [FF0F00] A:03FF X:051B Y:0F00 S:01EF DB:FF D:0300 P:80 e //开始读入字模数据了，接下来就是一系列的变形，这里就不一一列出了）
C263B7 STA $31 [000331] A:0000 X:051B Y:0F00 S:01EF DB:FF D:0300 P:02 e
C263B9 LDA $0002,Y [FF0F02] A:0000 X:051B Y:0F00 S:01EF DB:FF D:0300 P:02 e
C263BC STA $33 [000333] A:1F00 X:051B Y:0F00 S:01EF DB:FF D:0300 P:00 e
......--------------------------------------------------------------------------------

看到这里，相信大家已经知道程序是怎么通过字模代码读出字模数据了吧，其实字模代码实际上就是字模在字库中的排列顺序，只要知道字库的首地址、单个字模数据的长度再加上字模代码，就可以很简单的通过一个乘法运算得到字模数据的地址了。码表（就是全部字模代码的集合）也可以根据这个原理很简单的就做出来：先找到字库和第一个字模的代码，剩下的就按顺序一个一个的排下去就是了^^

文本读入子程序
所谓的文本读入就是把组成文本的字模代码从rom中读入到ram中，一般来说按rom中文本数据的存放方式分为两大类：
1、集中存放型。
采用这种方式的rom，其文本都在一个区域内集中存放，一般都有个文本指针表，通过文本指针来找到文本数据的地址，并读入ram中（实际上文本指针表＋文本数据和字模代码＋字模数据的工作原理都是一样的，只是名称不同而已）
2、分散存放型
采用这种方式的rom，其文本是和游戏的控制代码放在一起的，一般没有文本指针，但一定会有标志文本数据开始和结束的控制代码。例如：如果00h代表文本数据开始，FFh代表文本数据结束，那么，在00h和FFh之间的数据就是文本数据了。

对于我们汉化者来说，最方便的当然就是集中存放型文本了，只要简单的改一下指针表就可以了，而且可以把文本添加在rom的最后，这样就没有翻译字数的限制了。至于分散存放型文本，由于其夹杂着控制代码，文本数据的长度是固定的，这样翻译起来就有些不方便了（尤其是那种分散型＋压缩型的rom，我现在还没找到有什么好办法可以完美的解决~~-_-b）

如果按数据存放格式来说也分为两大类：压缩型和非压缩型。非压缩型这里就不多说了，对于压缩型的rom，首先最好是熟悉一下各种基本压缩算法（如字典、LZ77、Huffman等等），然后最重要的就是跟踪程序了。每个游戏采用的压缩法一般都不同的，而且也不会直接用那些基本压缩算法，很多都是根据自己的rom做了些改进的，所以要找到正确的压缩算法只有跟踪并破解它的文本读入子程序。

跟踪的基本方法也很简单，跟找字模转换程序一样就是盯住文本数据在ram中的存放区域，看程序什么时候改变其中的数据。闲话少说，让我们看一下实际的例子吧。

还记得上次我们找到的文本数据的首地址是7E051A吧，好，盯住这个地址......ok，找到了

源码:--------------------------------------------------------------------------------C23F54 LDA $0000,Y [7E154F] A:154F X:051A Y:154F S:01E0 DB:7E D:0000 P:00 e //这个是压缩文本在ram中的地址
C23F57 AND #$00FF A:6074 X:051A Y:154F S:01E0 DB:7E D:0000 P:00 e //取低位的一个字节，得到文本的压缩代码
C23F5A CMP #$00FC A:0074 X:051A Y:154F S:01E0 DB:7E D:0000 P:00 e
C23F5D BEQ $3F8E [C23F8E] A:0074 X:051A Y:154F S:01E0 DB:7E D:0000 P:80 e
C23F5F CMP #$00FF A:0074 X:051A Y:154F S:01E0 DB:7E D:0000 P:80 e
C23F62 BEQ $3F99 [C23F99] A:0074 X:051A Y:154F S:01E0 DB:7E D:0000 P:80 e
C23F64 PHX A:0074 X:051A Y:154F S:01E0 DB:7E D:0000 P:80 e
C23F65 TAX A:0074 X:051A Y:154F S:01DE DB:7E D:0000 P:80 e
C23F66 LDA $C04B60,X [C04BD4] A:0074 X:0074 Y:154F S:01DE DB:7E D:0000 P:00 e //C04B60是字典的首地址，这里根据压缩代码从字典中取出真正的文本数据
C23F6A AND #$00FF A:7978 X:0074 Y:154F S:01DE DB:7E D:0000 P:00 e
C23F6D CLC A:0078 X:0074 Y:154F S:01DE DB:7E D:0000 P:00 e //这个就是真正的文本数据
C23F6E ADC #$0005 A:0078 X:0074 Y:154F S:01DE DB:7E D:0000 P:00 e //大家还记得字模转换的时候会减去05h吧^^
C23F71 PLX A:007D X:0074 Y:154F S:01DE DB:7E D:0000 P:00 e
C23F72 BIT #$0080 A:007D X:051A Y:154F S:01E0 DB:7E D:0000 P:00 e
C23F75 BEQ $3F81 [C23F81] A:007D X:051A Y:154F S:01E0 DB:7E D:0000 P:02 e //判断在加了05h之后是否大于80h，如果大于的话就把它变成双字节的代码（具体的程序大家可以自己去看看^^）
C23F81 SEP #$20 A:007D X:051A Y:154F S:01E0 DB:7E D:0000 P:02 e
C23F83 STA $0000,X [7E051A] A:007D X:051A Y:154F S:01E0 DB:7E D:0000 P:22 e //把解压之后的文本数据放入文本数据区--------------------------------------------------------------------------------

以上就是一个简单的字典压缩算法的解压程序。不过大家是不是看出问题来了？它的压缩文本只有一个字节啊～～对，其实这只是人物名的解压程序（星海中的人物名都是日文假名，所以一个字节的空间就可以放的下了...>_<~~），那个7E154F中放的就是主角的名字（这个是开始游戏时就放进去了的），真正的文本在什么地方呢...往上找吧：

源码:--------------------------------------------------------------------------------C23F16 LDA $C23F21,X [C23F21] A:0000 X:0000 Y:0000 S:01E2 DB:7E D:0000 P:02 e //名字的压缩文本的偏移地址就放在这儿（其实就是压缩文本的指针啦），X里面放的是代号（现在是第一主角的名字，当然是No.1了^^）
C23F1A TAY A:154F X:0000 Y:0000 S:01E2 DB:7E D:0000 P:00 e //154F是要显示的名字的偏移地址（通过查找，好像只有几个主角的名字有固定存放位置，就是常驻内存啦，其他的小喽罗就没这么好运了^^b）
C23F1B PLX A:154F X:0000 Y:154F S:01E2 DB:7E D:0000 P:00 e
C23F1C JSR $3F51 [C23F51] A:154F X:051A Y:154F S:01E4 DB:7E D:0000 P:00 e
C23F51 PHY A:154F X:051A Y:154F S:01E2 DB:7E D:0000 P:00 e
C23F52 STZ $20 [000020] A:154F X:051A Y:154F S:01E0 DB:7E D:0000 P:00 e[/cdoe]
嗯嗯，那么是怎么知道要显示那个名字的呢（就是X的值是哪儿来的~~），还得继续往上找啊...
由于这前面还有很多复杂的运算，所以这里就不一一列举了，知道了基本的原理，离破解就只剩时间的问题了。还有，这个只是人物名部分，真正的对话文本部分是用的另外一种压缩算法，大家可以照着上面的方法自己跟踪一下试试看^^（对话文本的字模数据区是从B2A6h，也就是7EA693开始的）

只要找到了文本读入程序，那么文本数据的首地址、指针、相关控制符等东西也就一清二楚了，接下来就自己写个简单的小程序导出吧！

呼～～终于写完了！同时也感谢一下能有耐心看到最后的朋友!^^b 在这篇心得里我只是把汉化时需要的最基本的东西说明了一下，至于更深入的技巧就要通过不断的实践才能体会到了。所以～～大家一起加油吧，衷心希望我们的汉化队伍会越来越壮大！^_^

附录1：
一些游戏中常用的压缩算法：

1、字典压缩法
字典压缩法就是把一些常用的词语或者短句放在一起，并给其一个固定的代码，然后当要在文本中用到时，就直接调用其代码。

例子：如果00h＝我们，01＝汉化，02＝队伍，那么“这就是我们的汉化队伍”就可以写成“这就是00的0102”

2、LZ77压缩算法
简单的说就是可以调用前面已有的文本数据。例如在一段对话中出现了很多相同的人名，那么除了第一个，后面的地方都可以调用前面的那个文本数据。另外一般来说每段的前面都有一个控制符，它表明了后面的文本数据那些是直接读入的，那些是需要调用其它地方的数据的。（呵呵，其实这个只是一般游戏中采用的LZ77算法的变形，真正的LZ77算法还要复杂一些，有兴趣的朋友可以参考狼组上的相关文章）

例子：像这段代码“EE 11 05 FF 08 FF 18 FF 08 1C 01 FF 00 FF 3C FF 42”，EEh就是控制符，它表明了后面的8组数据的读入方式（要先转换成2进制“11101110”，2进制的每一位从低到高都和后面的一组数据相对应），0代表跳转读入，1代表直接读入。那么上面的第1组数据“11 05”就是跳转读入了，而第2、3、4组都是直接读入。表示跳转读入的那组数据里面一般都有跳转的偏移地址和需要跳转读入的字节数，不过到底那几位是表示地址，那几位是表示字节数，每个程序都不相同的，要自己去找了^^

附录2：
魔神转生2的字库扩容实例（之所以选这个是因为这个程序最简单，而且是我进行的第一个扩容工作^^b）

下面是我在空白空间添加的新代码
[code]C0F050（这段程序是在读入了字模代码之后插入的，Y中放的是字模代码在直接页面中的偏移地址）

LDA $0000,Y B9 00 00 //读入字模代码
REP $80 C2 80 //把进位标志清空，因为马上要进行数据对比了
CMP $0400 C9 00 04 //把字模代码跟400h对比（我是把新字库分为两部分，以400h为分界点）
SEP $20 E2 20
BMI J0 30 04
LDA #$F0 A9 F0 //如果大于或等于400h就把F0读入A（F0是第2个字库所在数据段）
BRA J1 80 02
J0： LDA #$CF A9 CF //如果小于400h就把CF读入A（CF是第1个字库所在数据段）
J1： STA $FF 85 FF //把段地址写入内存空间$FF、$EF（这个空间是我找的程序没用到的空间）
STA $EF 85 EF
REP $20 C2 20
RST 60

C0F0D0（这段程序是在算出了字模偏移地址之后插入的，A中放的就是字模的偏移地址）

STA $FD 85 FD //把字模偏移地址写入内存$FD、$FE（此时A寄存器是16位的）
ADC #$0010 69 00 10
STA $ED 85 ED //把字模下半部分的偏移地址写入$ED、$EE（此游戏字模数据是分成两部分存放的）
RST 60

C0F100（这就是新的字模读入部分）

LDA [$FD] A7 FD //读入字模上半部分
STA $5000,Y //$5000是ram中的字模数据区的首地址，Y中是偏移地址
LDA [$ED] A7 ED //读入字模下半部分
STA $5100,Y--------------------------------------------------------------------------------


微处理器的模拟

微处理器的介绍

这篇文章是为了能让读者能了解一些关于微处理器的背景资料，从而能明白到是如何模拟一颗微处理器的。本文并不会告诉你一些诸如它们如何工作，为什么那样工作的细节。要是想知道那方面资料的话，请参考其它相关材料。

要了解微处理器，第一件事就是至少要在概念上知道它里头到底有什么东西。微处理器里的第一样东西是储存器。储存器是非常小，也非常快的内存。在6502（红白机所使用的处理器）里面的储存器能贮存1个字节（8位）的数据。6502有3个数据储存器，分别叫作A，X和Y（A有时候被称作Acc或者Accumulator）。第二样在微处理器里的是一个能执行指令的“魔术盒”。我们需要知道是当我们给它发出指令时，它就会执行相应的操作，别的就不须深究了。最后一样是数据总线。它能让微处理器能向主内存读出和写入数据。程序就储存在主内存里。

如果你曾经编写过程序，你就会明白微处理器是怎样工作的了。当你编程的时候，你知道你的程序会被从头到尾，逐个逐个指令地执行（我们暂时先忽略掉有分支时的情况）。这就是微处理器工作的方式。你首先告诉它你的程序在哪儿，然后让它执行。然后它就会将所找到的程序永远地执行下去，一直到它被强行终止。你的程序则是以该处理器的汇编语言所写成。很多人对“汇编语言”这个名称不理解是因为它并不仅仅指一种的语言。举个例子来说，C语言是一种单一的计算机语言，它并不因为你编程对象的改变而改变：无论你是为了Macintosh还是PC编程，C的指令都是一样的。这是因为C有着硬件无关性。汇编就不一样了，它与硬件有关：因为PC与Mac有着不同的硬件，所以他们各自的汇编语言也不一样。这看起来很糟糕，要知道世界上有着数百种不同的处理器……不过就算表达式不一样，因为基本操作都是一样的，所以情况还不坏。就象你学过C再去学JAVA一样，你要学的只是一些新的表达式而已。

OK，我在前面的章节里已经给了你不少信息了。要消化这些信息最好的方法就是通过一个例子。
我会用6502汇编语言，因为红白机里用的就是它。

LDA #05 ; 将5装入Acc

ADC #01 ; 向Acc里的值加1

STA #00 ; 将Acc里的值存入地址0

上面这小程序印证了汇编语言中的一些重要概念，现在让我解释它是如何工作的。第一条指令(LDA)将数字5装入Acc内。注意井字符，它表明了该数字是一个直接值。直接值在opcode的右边。opcode的含义我会在下一段解释。下一个指令将指定的值加入Acc。在执行了第二条指令后，Acc里的值是5+1=6。最后一条指令让主内存中地址0的8位记忆体设成Acc中的值，6。这就是汇编语言。当你理解了它是如何工作的以后，你就能以汇编语言写出任何你想要的程序（当然，一本好的指令全集是必不可少的）。

上面是在你源程序文件里显示出来的程序，跟微处理器读的可不一样。下面的是同一个程序，不过以6502能理解的方式写出来。

A9 05 69 01 89 00

它看起来就象是一堆垃圾，不是吗？它叫机器语言。要理解它你必须先要理解Opcode的含义。Opcode是Operation Code（执行代码）的简称。它是一个代表着某条指令的代码。设计6502的Rockwell工程师们决定A9代表着LDA，所以当6502从内存读出A9时，它就知道要向Acc里装进点什么东西了。但它装什么东西进去呢？装进的是下一个字节，05。与其相似，代表着ADC的opcode是69，而STA是89。你能轻而易举地从网上或者是从制造商所提供的技术手册里找到任何一款处理器的opcode列表。那么我们为什么如此关心处理器是怎样识别指令的呢？答案是因为游戏程序乃是以机器语言的形式储存在ROM里，为了能模拟这游戏，模拟器必须象处理器一样工作，读取机器语言并决定应该怎么做。

我想这对于了解模拟微处理器的基本原理已经够了。假如某位读到本文的大虾认为有更好的方法去解释文中的某些问题，或者在文中发现错漏时，请不吝赐教。本文只是一个很基本的介绍而已，您的无私帮助将使大家都从中受益，先谢了！：）

时间:十月 31, 1999


65816 ASM 教程
原文名：ASM Tutorial
原作者：Jay 翻译：xade

目录
--------------------
1) 前言
2) CPU 寄存器 / 标志寄存器
3) 寻址模式
4) 指令参考
5) FAQ

--------------------------------------------------------------------------------
1) 前言
--------------------
这是一篇用于 SNES 的 65816 汇编的教程，即使刚入门的人也能看懂（可以说是这样）。为了满足你的好奇心，我可以告诉你我并不用这种语言来编程（xade：晕~~~那你就直说是搞 ROM HACK 了嘛^^），所以这篇教程中可能会有一些错误。如果你发现其中真的有错误之处的话，你可以发 e-mail 到 tennj@yahoo.com 告诉我，或者发发牢骚。学习汇编语言并不是一件容易的事（xade：兄弟们，千万不要被这句话吓到了，据我的经验，汇编也没什么可怕的。不过相信有胆子来看这篇文的就不会没那个胆量吧^^）。如果你已经懂得了一种高级编程语言的话，这个过程将会轻松许多。如果你已经懂得了一种低级编程语言的话，你就可以撕掉这篇教程了，因为你也许不会需要它（这是为刚入门的人准备的，记得吗？）。这篇教程的目的是要教会你一些 65816 汇编的基础知识。

2) CPU 寄存器 / 标志寄存器
--------------------
SNES 的 65816 CPU 有一套通用寄存器和标志寄存器。那么寄存器又到底是个什么冬冬？你可以把它想像成一种存储器空间，就像一个变量（xade：实际上把它理解为交换信息或是进行某些运算并暂时保存结果的地方要恰当一些）。每一个寄存器都有它自己的作用。这些标准的寄存器是
A，X，Y，D，S，PBR，DBR，PC 和 P。现在我将解释解释各个寄存器的作用。

A - 累加寄存器（Accumulator）：
    （xade：相当于 80x86（就是 PC）中的累加寄存器 AX（EAX））

这是个用于一般目的的数学寄存器。换句话是说，我们可以在里面存进任何我们喜欢的冬冬然后对它进行各种数学运算。比如，你要想把 $20 这个数存到 A 里面，那么你可以这么做：
LDA #$20 （LDA = Load Accumulator）
（xade：就是“将数值装入累加寄存器 A”）
然后如果你想要把它加上 5（假设标志寄存器的进位标志位没有被置位（关于这个后面将会讲到）），你可以这么做：
ADC #$05 （ADC = Add to Accumulator with Carry）
（xade：加到累加寄存器 A 并允许进位）
那么现在在累加寄存器 A 中的值就变成 25 了。

累加寄存器 A 可以当作 8 位或 16 位的来用。也就是说，当它被作为 8 位的寄存器来用的时候，可以存储一个在 0 - 255 范围内的数；而当它被作为 16 位的寄存器来用的时候，就可以存储一个在 0 - 65535 范围内的数。除了检测标志寄存器 P（同样在后面会讲到）的第 5 位是否被置位之外，没有其它方法可以判断累加寄存器 A 是被当作 8 位还是 16 位来用的。如果标志寄存器 P 的第 5 位被置位的话（xade：某一个位被置位就是指这个位上的值是 1），则累加寄存器 A 当前是被当作 8 位的寄存器来用的，否则就是被当作 16 位的寄存器来用的。

X，Y - 变址寄存器（Index Registers）：
        （xade：相当于 80x86 中的源变址寄存器 SI（ESI）和目的变址寄存器 DI（EDI））

变址寄存器 X，Y 和累加寄存器 A 非常相似，你可以在里面存放任何数值并且对它们进行各种数学运算。但是它们还有另外一个作用，就是用来寻址（xade：或叫索引，就是定位）内存中的位置。变址寄存器 X，Y 和累加寄存器 A 一样可以当作 8 位或 16 位的来用，只不过是由标志寄存器 P 的第 4 位是否被置位来决定的。

D - 直接页面寄存器（Direct Page Register）：
    （xade：相当于 80x86 中的基址寄存器 BX（EBX））

直接页面寄存器 D 内是一个指向第一个 64k 内存区域的指针（xade：实际上这种说法是不准确的，应该说是一个介于 $0000 到 $FFFF 之间的数，因为这个数并不是只能用来寻址第一个 64k 内存区域的）。这个寄存器中的值在各种直接寻址模式中被用来形成有效地址以对内存进行访问。在直接寻址方式（xade：“模式”是各种“方式”的统称，一种“方式”是“模式”的一种具体实现形式。汗~~~~~好绕口^^b）下，一个 8 位的值（0 - 255 或 $00 - $FF）将和直接页面寄存器 D 内的值相加形成一个有效地址。例如，直接页面寄存器 D 内的值是 $5000，那么指令：
STA $10 （STA = Store Accumulator to Address）
（xade：将累加寄存器 A 中的值存到后面的操作数指定的地址中）
将会把累加寄存器 A 中的值存到内存地址 $5000 + $10 = $5010 处。

S - 堆栈指针寄存器（Stack Register）：
    （xade：相当于 80x86 中的堆栈指针寄存器 SP（ESP））

堆栈指针寄存器 S 内是一个指向堆栈栈顶的指针。那么堆栈又是个什么冬冬呢？你可以这样来想像它：现在你有一张桌子，当你把一本书放到桌上的时候，就说你 push（xade：翻译成中文就叫做“压栈”或“入栈”）了一本书到堆栈里。然后，假设你又 push 了另一本书到堆栈里（xade：你要把这本书放到刚才那本书的上面压着它！至于为什么，后面我再讲^^），现在桌上就有两本书了。接下来，你拿走了一本书（xade：现在你拿走的是刚才放在面上的书！），就说你从堆栈里 pop（xade：翻译成中文就叫做“退栈”或“出栈”）了一本书。那么到底我说的这个又 push 又 pop 的究竟是些什么冬冬？就是说，我们可以把一些值存入到堆栈里，稍后再把它们取出来。每次我们向堆栈里压入一个数的时候，这个数就被存到了当前堆栈指针寄存器 S 所指向的内存空间中，而同时堆栈指针寄存器 S 内的值就会减少（xade：减少多少，取决于压栈的数据的长度。这样堆栈指针寄存器 S 内的值就指向到了堆栈中的下一个可用空间，为下一次压栈操作做好准备）。而当我们从堆栈里取出一个数的时候，这个数就被从堆栈里取出并存到目的地址的内存空间中（xade：这个目的地址由出栈指令的操作数（就是参数）指定），并且将堆栈指针寄存器 S 内的值做相应的增加（xade：实际上出栈和入栈是完全相反的操作，包括顺序。入栈时是先存值，然后减少堆栈指针寄存器 S 内的值；而出栈时就反过来，先取值，然后增加堆栈指针寄存器 S 内的值）。举个例子来说，假设现在我们的堆栈位于 $1FFF 处（xade：此时堆栈指针寄存器 S 内的值是 $1FFF），下面的指令：
PEA $1000 （PEA = Push Effective Address）
（xade：将有效地址压栈）
将 $1000 压入到了堆栈里（xade：在内存 $1FFE 和 $1FFF 处，现在的值就是 $00 和 $10 了。因为压栈时是按先高位后低位的顺序进行的，所以先是在 $1FFF 处存入 $10，然后再在 $1FFE 处存入 $00），现在堆栈指针寄存器 S 内的值就变成 $1FFD 了，因为 $1000 占了两个字节的长度。然后指令：
PLA （PLA = Pop and Load to Accumulator）
（xade：出栈一个数，并将它存到累加寄存器 A 中）
将从堆栈中取出刚才压入的 $1000 并将它存到累加寄存器 A 中去，现在堆栈指针寄存器 S 内的值就又恢复成 $1FFF 了。

PBR - 程序段寄存器（Program Bank Register）：
    （xade：相当于 80x86 中的代码段寄存器 SS）

程序段寄存器 PBR 中保存着当前代码所在的段的段地址（xade：“段”是程序的一部分。因为一些老的处理器芯片（CPU）的寻址能力的限制，必须将程序分为一段一段地来处理。虽然现在的 CPU 在能力上这个问题已经不明显了，但是为了与老的程序保持良好的兼容性，在某些情况下会保留这种执行方式）。通常，程序段寄存器 PBR 中的值用来和指令 JMP（Jump，跳转）指令或 JSR（Jump to Sub Routine，跳转到子程序）指令中的操作数一起形成一个 24 位的有效地址。例如：
PBR：$80 （当前程序段寄存器 PBR 中的值是 $80）
指令：
JMP $1C00
将会使程序跳转到 $80:1C00 处继续执行（xade：这时，指令指针寄存器 PC（见后面）的值就变成 $1C00 了）。

DBR - 数据段寄存器（Data Bank Register）：
    （xade：相当于 80x86 中的数据段寄存器 DS）
    （xade：这里再提一下，这个寄存器在很多地方只写作 DB，而原文中一直
用 DBR 表示，为尊重原作者，我翻译时也用 DBR 表示。如果你在其它
文章或程序里看到 DB 而不是 DBR，那只是缩写记号不同，但是指的都是
这个数据段寄存器。上面的 PBR 可能也有类似情况，请大家自己注意）

和程序段寄存器 PBR 差不多，只不过其中的值是当前进行存放数据操作所在的段的段地址。
例如：
DBR：$90 （当前数据段寄存器 DBR 中的值是 $90）
指令：
LDA $8000 （把操作数指定的地址中的数装入到累加寄存器 A 中）
（xade：与前面的用法不同，详细见“寻址模式”一节）
将会把位于 $90:8000 处的数装入到累加寄存器 A 中。

PC - 指令指针寄存器（Program Counter）
（xade：相当于 80x86 中的指令指针寄存器 IP（EIP）。这个寄存器的名字如果直接照字面翻译的话，应该是“程序计数器”，但是这个名称不太贴切，所以借用了 80x86 中有相同作用的指令指针寄存器 IP（Instruction Pointer）的中文译名）

指令指针寄存器 PC 中保存着当前指令的地址（xade：相对于当前程序段的）。它和程序段寄存器 PBR 一起形成了当前指令的有效地址：PBR:PC。

P - 标志寄存器（Flag Register）：
    （xade：相当于 80x86 中的状态字寄存器 PSW（EPSW），或叫做标志寄位存器 FLAGS（EFLAGS））

标志寄存器 P 是一个 8 位的寄存器，其中保存 CPU 的运行状态等信息。通过检测它也可以知道累加寄存器 A 和变址寄存器 X，Y 是被用作 8 位的还是 16 位的。标志寄存器 P 各个位的定义如下：
位７　６　５　４　３　２　１　０
┏━┳━┳━┳━┳━┳━┳━┳━┓
┃n ┃v ┃m┃x ┃d ┃i ┃z ┃c ┃
┗━┻━┻━┻━┻━┻━┻━┻━┛
位７　　n：Negative（xade：负数标志位）
位６　　v：Overflow（xade：溢出标志位）
位５　　m：Memory/Accumulator Select（xade：累加器工作状态标志位）
位４　　x：Index Register select（xade：变址器工作状态标志位）
位３　　d：Decimal（xade：十进制模式标志位）
位２　　i：Interrupt（xade：禁用中断标志位）
位１　　z：Zero（xade：结果为零标志位）
位０　　c：Carry（xade：进位标志位）

上面的每一个框表示标志寄存器 P 中的一个位（xade：左边是最高位）。你必须把标志寄存器 P 中的数转换成二进制，然后通过比较来逐个检查各个位是否被置位。进位标志位 c 通常在发生错误或无符号溢出（xade：即执行加法时在最高位上产生了进位）时被置位。当进位标志位 c 被置位时，加法指令 ADC 将在完成加法后，再向累加寄存器 A 中加 1。所以要进行普通的加法时，必须先将进位标志位 c 清零。例如：
CLC （Clear Carry flag）（xade：清进位标志位）
ADC #$20
现在你搞清楚了我之前说到的关于进位标志位的事了吧。累加器状态位 m 和 变址器状态位 x 决定了累加寄存器 A 和变址寄存器 X，Y 当前是被用作 8 位的还是 16 位的。当累加器状态位 m 被置位时，表明累加寄存器 A 当前是被用作 8 位寄存器的（xade：变址器状态位 x 相同）。大多数汇编器不能检测出你是将累加寄存器 A 用作 8 位的还是 16 位的，所以这就得由你的程序自己来做。如果你不这么做的话，将会给将来的调试工作带来极大的困难（xade：虽然说我们大多数情况下不会用到这个来编程，但是搞 ROM HACK 时必须注意这一点）。我们可以用 SEP（Set P flag，设置标志寄存器 P）和 REP（Reset P flag，复位标志寄存器 P）来设置和清零标志寄存器 P 中的指定位（xade：当然是通过一个 8 位的操作数实现）。要想把累加寄存器 A 用作 16 位寄存器，我们可以把二进制数 00010000 转换成十六进制的 $20 或十进制的 #32，然后执行指令：
REP #$20
这样累加寄存器 A 就被设置成 16 位的了。

3) 寻址模式
--------------------
寻址模式就是指处理器解释指令的方式（xade：准确地说，应该是解释指令的操作对象的方式）。换句话说，我们不能认为指令：
LDA #$20
和下面的指令：
LDA $20
是一样的。第一条指令是将数值 $20 直接存入累加寄存器 A，而第二条指令则是将内存中位于当前页面起始处偏移 $20 的地方的一个字节的值存入累加寄存器 A。因此我们必须学习一些关于寻址模式的知识。（xade：实际上新手可以一边调试代码，一边看参考，用不着硬要把所有的寻址模式背下来，那将比边调试边看参考还痛苦^^）上面的例子中，均假设累加寄存器 A 和变址寄存器 X，Y 工作在 8 位的状态下。

现在我们先约定一些记号，在下面的语法描述中我将用到它们。

<exp> ：表达式
<8-bit exp> ：8 位表达式。一个 8 位的表达式就是介于 $00 - $FF 之间的一个数
<16-bit exp> ：16 位表达式。介于 $0000 - $FFFF 之间的一个数
<24-bit exp> ：24 位表达式。就像你猜测的那样，$0000 - $FFFF 之间的一个数
<dp exp> ：直接页面表达式。实际上就是一个 <8-bit exp>，但是它描述的是直接页面值，并 且总是写成两位的十六进制数的形式。实际的直接页面值总是由 <dp exp> + D 形成的
<abs exp> ：绝对地址表达式。一个 <16-bit exp>，并且总是写成四位的十六进制数的形式
<long exp> ：长地址表达式。一个 <24-bit exp>，并且总是写成六位的十六进制数的形式

（01）立即寻址方式（Immediate）：

opcode #$<8/16-bit exp>

立即寻址方式就是直接指定操作数。
例：
LDA #$FF ;将数 $FF 装入累加寄存器 A
SEP #$30 ;将标志寄存器 P 设为 $30

（02）直接寻址方式（Direct）：

opcode $<8-bit exp>

操作数的目标地址是 <8-bit exp> 和直接页面寄存器 D 内的值相加形成的有效地址。
例：
LDA $20 ;将 $20 + D 处的值载入累加寄存器 A（xade：$00:20 + D）
LDA $90 ;将 $90 + D 处的值载入累加寄存器 A（xade：$00:90 + D）
如果 D = $1000，这两条指令将会从地址 $1020（xade：$00:1020）和 $1090（xade：$00:1090）处读一个字节（如果累加寄存器 A 工作在 8 位模式的话）到累加寄存器 A 中。

（03）绝对寻址方式（Absolute）：

opcode $<16-bit exp>

有效地址通过 DBR:<16-bit exp> 形成。
例：
DBR：$88

LDA $901C ;将 $88:901C 处的值载入累加寄存器 A

（04）绝对长地址寻址方式（Absolute Long）：

opcode $<24-bit exp>

有效地址就是表达式本身。
例：
LDA $808000 ;将 $80:8000 处的值载入累加寄存器 A
LDA $FF9090 ;将 $FF:9090 处的值载入累加寄存器 A

（05）累加器寻址方式（Accumulator）：

opcode

操作对象就是累加寄存器 A 。
例：
INC ;累加寄存器 A 中的值增加 1

（06）隐含寻址方式（Implied）：

opcode

根据指令决定操作对象。
例：
CLC ;清标志寄存器 P 的进位标志位
INX ;变址寄存器 X 中的值增加 1

（07）直接页面间接变址寻址方式（Direct Indirect Indexed）：

opcode ($<dp exp>), Y

这种寻址方式中有较多的寄存器参与。首先 <dp exp> 与直接页面寄存器 D 内的值相加产生一个直接页面地址，然后从这个地址处取出两个字节和数据段寄存器 DBR 内的值组合形成一个基地址，最后变址寄存器 Y 内的值加到这个基地址上形成一个绝对地址。
例：
DBR：$80， D：$0020， Y：$0001
内存镜像：
00:0030： 30 40 23 22 F4 22 23 1C
00:0038： 23 2D DD F4 FF FF FF FF

LDA ($10), Y
首先我们要计算 DP 地址（xade：直接页面地址，direct page address）：
$10 + D = $0030
然后从 $0030 处取出两个字节，$30 和 $40（因为低位在前高位在后的 LSB 顺序，这两个要交换位置）后得到地址 $4030。这个地址和数据段寄存器 DBR 内的值组合得到一个基地址：
DBR:$4030 -> $80:4030
最后变址寄存器 Y 中的值加到这个基地址上形成有效地址：
基地址 + Y = $80:4030 + $0001 = $80:4031

使用这种寻址方式的指令基本上都是从 $80:4031（xade：当然不是都从这里^^）处读取一个字节到累加寄存器 A 中去。通常，这种寻址方式被用在一个循环中，每一次 Y 中的值都会增加，就可以从内存中的某个地方取得一连串的数据。
例 2：
DBR：$80， D：$0020， Y：$0001
内存镜像：
00:0030： 30 40 23 22 F4 22 23 1C
00:0038： 23 2D DD F4 FF FF FF FF

LDA ($15), Y

DP 地址 = $0020 + $15 = $0035
基地址 = DBR:<$0035 处的两个字节> = $80:2322
有效地址 = 基地址 + Y = $80:2323

P.S. 也许我把这些解释得太复杂了点，大多数时候直接页面寄存器 D 内的值都是 0，因此并不 需要花那么多的功夫来计算这些冬冬。

（08）直接页面间接变址长寻址方式（Direct Indirect Indexed Long）：

opcode [$<dp exp>], Y

这种寻址方式和前面的差不多，不同的是从直接页面地址处取出三个字节而不是两个，这三个字节直接形成有效地址。
例：（和前面相同的情况）
DBR：$80， D：$0020， Y：$0001
内存镜像：
00:0030： 30 40 23 22 F4 22 23 1C
00:0038： 23 2D DD F4 FF FF FF FF

LDA [$10], Y

DP 地址 = $0020 + $10 = $0030
基地址 = <$0030 处的三个字节> = $23:4030
有效地址 = 基地址 + Y = $23:4031

（09）直接页面变址间接寻址方式（Direct Indexed Indirect）：

opcode ($<dp exp>, X)

DP 地址被计算出来后加上 X 产生一个新的 DP 地址，然后从新的 DP 地址处取出两个字节和 DBR 组合形成有效地址。
例：
DBR：$80， D：$0020， Y：$0004
内存镜像：
00:0020： FF 00 FF 09 33 33 09 88
00:0028： 08 76 66 36 D7 23 99 00

LDA ($02, X)

DP 地址 = $0020 + $02 = $0022
新的 DP 地址 = $0020 + X = $0026
有效地址 = DBR:<$0026 处的两个字节> = $80:8809

于是 $80:8809 处的一个字节将被装入累加寄存器 A。

（10）直接X变址寻址方式（Direct Indexed by X）：

opcode $<dp exp>, X

DP 地址被计算出来后加上 X 形成有效地址。这个有效地址始终在 bank 0（xade：就是第一个 64k 段） 内。
例：
D：$0020， X：$0004

LDA $30, X

DP 地址 = $0020 + $30 = $0050
有效地址 = DP 地址 + X = $00:0054

（11）直接Y变址寻址方式（Direct Indexed by Y）：

opcode $<dp exp>, Y

和上面的一样，只不过把 X 换成了 Y。

（12）绝对X变址寻址方式（Absolute Indexed by X）：

opcode $<abs exp>, X

<abs exp> 和 X 相加后和 DBR 组合形成有效地址。
例：
DBR：$80， X：$0001

LDA $8000, X
有效地址 = DBR:($8000 + X) = $80:8001

LDA $6988, X
有效地址 = DBR:($6988 + X) = $80:6989

（13）绝对Y变址寻址方式（Absolute Indexed by Y）：

opcode $<abs exp>, Y

和上面的一样，只不过把 X 换成了 Y。

（14）长绝对X变址寻址方式（Absolute Long Indexed by X）：

opcode $<long exp>, X

<long exp> 和 X 相加形成有效地址。
例：
X：$0001

LDA $808000, X
从 $80:8001 处读取一个字节到累加寄存器 A。

LDA $589112, X
从 $58:9112 处读取一个字节到累加寄存器 A。

（15）指令指针相对寻址方式（Program Counter Relative）：

opcode $<8-bit signed exp>

这种寻址方式只用在程序分支指令中。<8-bit signed exp> 加到指令指针寄存器形成一个新的跳转地址（xade：就是下一条要执行的指令的地址）。<8-bit signed exp> 是一个 8 位带符号（xade：指正负号）的表达式，介于 -128 到 127 之间的数。（xade：学过一点 C 的人应该知道的^^）大多数汇编器允许你输入一个 <abs exp>，然后在编译时自动计算跳转点的相对地址。
例：
BRA $8005 ;只要 $8005 在 -128 到 127 的相对地址范围内就跳转过去
;（xade：“只要”好像是多余的，若编译时 $8005 不在 -128 到
;127 的相对地址范围内应该是会报错的。但是我也没用过 65816 的
;汇编器，所以不敢肯定，希望高手指正^^）

（16）指令指针长相对寻址方式（Program Counter Relative Long）：

opcode $<16-bit signed exp>

和上面的差不多，只不过 <16-bit signed exp> 的范围扩大到了 0 到 65535。这种寻址方式只有 BRL 和 PER 两条指令用到。（xade：这里有一个明显的错误，<16-bit signed exp> 的范围应该是 -32768 到 32767，而不是 0 到 65535。而且根据其它文章的说法来看，这种寻址方式在两条指令的情况下并不是完全一样的，对 BRL 指令来说，操作数是一个有符号的 16 位表达式，其范围是 -32768 到 32767；而对 PER 指令来说，操作数是一个无符号的 16 位表达式，其范围是 0 到 65535。）

（17）绝对间接寻址方式（Absolute Indirect）：

opcode ($<abs exp>)

从内存 <abs exp> 处取出两个字节形成有效地址。（xade：准确地说是从 PBR:<abs exp> 处取出两个字节形成有效地址）
例：
内存镜像：
0000： 90 77 78 00 43 00 00 00
0008： 33 32 12 33 11 11 FF FF

JMP ($0008)
这条指令将先从 $0008 处取出两个字节 $33 和 $32 形成有效地址 $3233，然后跳转到 $3233 处继续执行。

（18）绝对变址间接寻址方式（Absolute Indexed Indirect）：

opcode ($<abs exp>, X)

<abs exp> 和 X 相加后产生一个新的 <abs exp>，再从内存中这个新的 <abs exp> 处取出两个字节形成有效地址。（xade：同样也是从 PBR:<abs exp> 处取）
例：
X：$0001
内存镜像：
0000： 90 77 78 00 43 00 00 00
0008： 33 32 12 33 11 11 FF FF

JMP ($0008, X)

新的绝对地址 = $0008 + X = $0009
从 $0009 处取出两个字节 $32 和 $12 形成有效地址 $1232，然后跳转到 $1232 处继续执行。

（19）直接页面间接寻址方式（Direct Indirect）：

opcode ($<dp exp>)

从 <dp exp> 处（xade：00:<dp exp> 处）取出两个字节产生一个 16 位的地址，和 DBR 组合后形成一个 24 位的有效地址。
例：
D：$0000， DBR：$80
内存镜像：
0040： 50 00 81 00 22 23 33 44
0048： 60 21 21 21 22 55 55 66

LDA ($40)

DP 地址 = $40 + D = $0040
有效地址 = DBR:<$0040 处的两个字节> = $80:0050

（20）直接页面长地址间接寻址方式（Direct Indirect Long）：

opcode [$<dp exp>]

从 <dp exp> 处（xade：00:<dp exp> 处）取出三个字节直接产生一个 24 位的有效地址。
例：
D：$0000， DBR：$80
内存镜像：
0040： 50 00 81 00 22 23 33 44
0048： 60 21 21 21 22 55 55 66

LDA [$40]

有效地址 = DBR:<$0040 处的三个字节> = $81:0050

（21）堆栈寻址方式（Stack）：

opcode

和隐含寻址方式差不多，但是数据在堆栈中进行操作。
例：
PHA ;将累加寄存器 A 中的值压栈
PLA ;出栈并装入累加寄存器 A

（22）堆栈相对寻址方式（Stack Relative）：

opcode $<8-bit exp>, S

<8-bit exp> 和 S 相加形直接成有效地址。
例：
S：$1FF0

LDA $01, S
从 $01 + S = $1FF1 处读一个字节存入累加寄存器 A 中。

（23）堆栈相对间接变址寻址方式（Stack Relative Indirect Indexed）：

opcode ($<8-bit exp>, S), Y

<8-bit exp> 和 S 相加后和 DBR 组合产生一个基地址，然后加上 Y 形成有效地址。
例：
S：$1FF0， DBR：$80， Y：$0001

LDA ($01, S), Y

基地址 = DBR:($1FF0 + 1) = DBR:$1FF1 = $80:1FF1
有效地址 = $80:1FF1 + Y = $80:1FF2

（24）块寻址方式（Block Move）：

MVN $<8-bit exp>,$<8-bit exp>
MVP $<8-bit exp>,$<8-bit exp>

这是迄今为止我见过的最怪异的指令了。它将成块的数据从一个地方移到另一个地方。第一个 <8-bit exp> 是目标地址所在段（xade：就是 bank）的段地址，第二个则是源地址所在段的段地址。X 中事先存放着一个 16 位的源地址的偏移，Y 中的则是目标地址的偏移，A 中存放着要移动的字节数。
例：

X：???? Y：???? A：???? REP #$30 ;使 A 和 XY 工作在 16 位模式下
X：???? Y：???? A：???? LDY #$8000 ;将 $8000 装入 Y
X：???? Y：$8000 A：???? LDX #$9000 ;将 $9000 装入 X
X：$9000 Y：$8000 A：???? LDA #$0005 ;将 $0005 装入 A
X：$9000 Y：$8000 A：$0005 MVP $80, $A0 ;开始正向移动数据块
;（xade：关于“正向”见指令参考）
从 $80:8000 开始的 5 个字节将会被传递到 $A0:9000 处。

4) 指令参考
--------------------
以下给出一个简明的指令参考。
（xade：由于以下的指令参考不是很全面，很多指令都没有，而且解释也不是很清楚，所以我就懒得翻译出来了。懂英语的自己就可以搞定，不懂的嘛，嘿嘿嘿，等吧~~~~~~~~~~~ ^^）

(01) ADC - Add with Carry

The operand is added to the accumulator. 1 is added in addition if carry flag is set.
eg.

A: 0010 adc #$50 ; adds $50 to accumulator or 51 if carry is set
A: 0060 ; result

(02) AND - Perform AND to accumulator

The operand is "AND"ed to the accumulator.
eg.
A: 0010 and #$80 ; "AND"s $80 to accumulator
A: 0000

(03) ASL - Left shifts Accumulator, Memory

Performs a shift left.
eg.
A: 0010 asl #$01 ; Left shifts accumulator with 1
A: 0020

(04) BCC - Branch if carry clear

Jump to a new location within the -128 to 128 range if the carry flag is clear. Useful for comparing 2 numbers, and branches if it is greater than.
eg.
P: --mxdi-- bcc next ; since the carry flag is clear, it'll jump to next
lda #$00 ; this will not be executed
next: lda #$40

(05) BCS - Branch if carry set

Like BCC, but only when carry is set. Good for branching "if less than" in comparison.

(06) BEQ - Branch if equal

Branches if zero flag is set. This is useful for comparing numbers.
eg.
cpx #$50 ; if X is = $50, the zero flag is set
beq next ; branches to next if X = $50
lda #$44
next: lda #$00

(07) BIT - Bit Test

Performs AND except only the flags are modified.
eg.
A: 0010 bit #$80 ; "AND"s $80 to accumulator but result not stored
A: 0010

(08) BMI - Branch if Minus

Branches if negative flag is set.

(09) BNE - Branch if not equal

Branches if zero flag clear. Good when used with comparison. You can branch if the number it not equal to.

(10) BPL - Branch if plus

Branches if negative flag clear.

(11) BRA - Branch always

Hmm…..

(12) BRK - Break to instruction

Causes a software break. The PC is loaded from a vector table from somewhere around $FFE6.

(13) BRL - Branch Always Long

Like BRA but with longer range (0 - 65535).

(14) BVC - Branch if Overflow Clear

Branches if overflow flag is clear.

(15) BVS - Branch if Overflow Set

Opposite of BVC.

(16) CLC - Clear Carry Flag

Clears the carry flag.

(17) CLD - Clear Decimal Flag

Clears the decimal flag.

(18) CLI - Clear Interrupt Flag

Clears the interrupt Flag.

(19) CLV - Clear Overflow Flag

Clears the Overflow flag.

(20) CMP - Compare Accumulator with memory
(21) CPX - Compare X with memory
(22) CPY - Compare Y with memory
Compares accumulator, X, or Y with an operand. The n-----zc flags are affected by the comparison. If the result is negative, the n flag is set. If the result is zero (or equal), the z flag is set. Carry is set usually when borrow is required.
eg.
A: 0020 P: --mx-i-- cmp #$20 ; compare accumulator with $20
; if equal, z flag is set
A: 0020 P: --mx-iz- beq next ; branch if equal

(23) COP - Coprocessor Empowerment

Causes a software interrupt using a vector.

(24) DEC - Decrement Accumulator
(25) DEX - Decrement X
(26) DEY - Decrement Y

Subtracts 1 from A, X, or Y.
eg.
Y: 0001 dey
Y: 0000

(27) EOR - Exclusive OR accumulator

Performs XOR (I think) to the accumulator.
eg.
A: FFFF eor #$DDDD
A: 2222

(28) INC - Increment Accumulator
(29) INX - Increment X
(30) INY - Increment Y

Adds 1 to A, X, or Y.
eg.
A: 0000 inc
A: 0001

(31) JMP - Jump to location
(32) JML - Jump long

The JMP command will jump to a location within the bank. The JML will jump to places out of the current bank.
eg.
PBR: $80 jmp $5500 ; jumps to $80:5500
PBR: $80 jml $908000 ; jumps to $90:8000
PBR: $90 ; the PBR is updated in long jump

(33) JSR - Jump Subroutine
(34) JSL - Jump Subroutine Long

If you already know a programming language, this is basically calling a function. This performs the same as JMP except the address of the current program counter is saved. In subroutines, the RTS and RTL are used to return back to the saved address.

(35) LDA - Load Accumulator with memory
(36) LDX - Load X with memory
(37) LDY - Load Y with memory

Loads the accumulator, X, Y with a value.

(38) LSR - Shift Right Accumulator, Memory

Performs right shift.
eg.
A: 0080 lsr #$01 ; shift right by 1
A: 0040

(39) MVN - Block move negative
(40) MVP - Block move positive

*See Block Move Addressing mode.
The difference between the positive and negative block move is that with negative block move, the source address is greater than the destination.

(41) NOP - No operation

Does nothing but take up 2 cycles.

(42) ORA - "OR" Accumulator with memory

Performs "OR" to accumulator.
eg.
A: 005F ora #$7F
A: 007F

(43) PEA - Push Effective Address

Pushes a 16-bit operand onto the stack. The instruction is very misleading because you are really pushing an immediate onto the stack.
eg.
S: 1FFF pea $FFFF ; Pushes $FFFF onto the stack
S: 1FFD ; stack decrements by 2

(44) PEI - Push Effective Indirect Address

Pushes a 16-bit value from the indirect address of the operand.
eg.
Memory Dump:
0000: 23 33 45 22 DD C7 FF 8D
0001: 99 99 90 88 DD FF CC 67

S: 1FFF pei ($01) ; push $4533 on the stack
S: 1FFD

(45) PER - Push Program Counter Relative

Pushes a 16-bit from that address taken by the current PC added to the operand. The range must be within (0 - 65535).

(46) PHA - Push Accumulator
(47) PHD - Push Direct Page Register
(48) PHK - Push PBR
(49) PHP - Push Flags
(50) PHX - Push X
(51) PHY - Push Y

Pushes the operand onto the stack.

(52) PLA - Pull Accumulator
(53) PLD - Pull Direct Page Register
(54) PLP - Pull Flags
(55) PLX - Pull X
(56) PLY - Pull Y

Pops a value off the stack and stores it in the operand.

(57) REP - Reset Flag

Clears the bits specified in the operands of the flag.
eg.
rep #$30 ; Clears bit 4 & 5 to make A, X, Y 16-bits

(58) ROL - Rotate Bit Left

Much like shift left except any bits moved off the most significant bit are restored to the right.
eg.
A: 8000 rol #$0001 ;rotate 1 left
A: 0001

(59) ROR - Rotate Bit Right

Much like shift right except any bits moved off the least significant but are restored to the left.
eg.
A: 0001 ror #$0001 ; rotate 1 right
A: 8000

(60) RTI - Return from Interrupt

Used to return from a interrupt handler.

(61) RTS - Return from Subroutine
(62) RTL - Return from Subroutine Long

Return the PC to the saved address caused by the JSR and JSL command.
eg.
jsl sub ; jump to subroutine at label "sub"
.
.
.
sub:
lda #$0000 ; some code
rtl ; return

(63) SBC - Subtract with Carry

Subtracts Accumulator with memory, and subtract an additional 1 if carry is set.

(64) SEC - Set Carry Flag

Sets the carry flag.

(65) SED - Set Decimal Flag

Sets the decimal flag.

(66) SEI - Set Interrupt flag

Sets the interrupt flag.

(67) SEP - Set Flag

Sets certain bits of the flag depending on the operand.
eg.
sep #$20 ; set bit 5 of P to make A 8-bits

(68) STA - Store Accumulator to memory
(69) STX - Store X to memory
(70) STY - Store Y to memory

Stores the accumulator, X, or Y to a memory location.
eg.
A: 000F sep #$20 ; 8-bit A
A: 000F sta $2100 ; Stores $0F to $2100

(71) STP - Stop the clock

Don't know how this works.
(xade:STP shuts the processor down until a hardware reset. It is used in some
systems to put the processor to sleep and reduce power consumption. There
is a RESet B pin on some 65816 processors that allow for the usage of this
instruction.)

(72) STZ - Store zero to memory

Stores zero to a memory location.
eg.
stz $2101 ; store 0 to $2101

(73) TAX - Transfer Accumulator to X
(74) TAY - Transfer Accumulator to Y
(75) TCD - Transfer Accumulator to Direct Page
(76) TCS - Transfer Accumulator to Stack
(77) TDC - Transfer Direct Page to Accumulator
(78) TSC - Transfer Stack to Accumulator
(79) TSX - Transfer stack to X
(80) TXA - Transfer X to Accumulator
(81) TXS - Transfer X to Stack
(82) TXY - Transfer X to Y
(83) TYA - Transfer Y to Accumulator
(84) TYX - Transfer Y to X

Copies the content of one register to another.

eg.
A: 0099 X: 1FFF Y:5656 D:0020 S: FFFF rep #$30
A: 0099 X: 1FFF Y:5656 D:0020 S: FFFF lda #$0000 ; load A with 0
A: 0000 X: 1FFF Y:5656 D:0020 S: FFFF tcd ; transfer A -> D
A: 0000 X: 1FFF Y:5656 D:0000 S: FFFF txa ; X -> A
A: 1FFF X: 1FFF Y:5656 D:0000 S: FFFF tcs ; A -> S
A: 1FFF X: 1FFF Y:5656 D:0000 S: 1FFF

You get the idea.

(85) TRB - Test and Reset Bit

Tests the bit similarly to "AND", and clears it, while affecting the flags.

(86) TSB - Test and Set Bit

Tests the bit similarly to "AND", and sets it, while affecting the flags.

(87) WAI - Wait for Interrupt

Waits until a hardware interrupt is triggered.

(88) XBA - Exchanges B with A

Due to the fact that the accumulator was either 8 or 16 bit, the low and high ends of the accumulator was given a name A and B (and C is the whole 16-bit Accumulator). This command swaps the low and high of the accumulator.
eg.
A: 5690 xba
A: 9056

(89) XCE - Exchange Carry with Emulation

Well, the 65816 has actually 2 modes. Native and (6502) emulation mode. This tutorial deals with native only. The emulation bit shares the same bit as the carry flag so to put ourselves in native mode, we can only set the emulation flag via the carry and exchanging it. For native mode, the emulation flag must be off. To switch to native mode, we must clear the carry and exchange it.
eg.
clc
xce

5) FAQ's
--------------------
Q: 既然累加器有 8 位和 16 位两种模式，那么反汇编程序是如何知道什么时候累加器是 8 位或 16 位的呢？
A: 实际上它无法知道。Tracer（xade：一个 65816 的反汇编程序）提供一个开关 -f 来尝试检测累加器和变址器的工作状态。

Q: 有时汇编器会把我写的 JMP 指令 编译成 JML 指令，这怎么办？
A: 试试用 jmp.w

Q: 现在我会一些基本的 ASM 了，这表示我已经能像那些破解小组一样搞 SNES ROM 的 ASM HACK 了吗？
A: 不，你还必须学学关于硬件的知识，我并没有讲到这些。

（xade：…………^^b）
Q: Are you the coolest?
A: Yes.

Q: What about Tiger Claw? Ain't he cool too?
A: Of course.


65816入门书

 宣云 翻译/修订

目 录

1.00 介绍
2.00 新的65816指令
3.00 65816原本模式设计模型
3.01 原本模式处理器的状态寄存器 (P)
3.10 原本模式寄存器
3.11 累加器
3.12 X，Y变址寄存器
3.13 直接页面寄存器 (D)
3.14 堆栈指针 (S)
3.15 程序存储寄存器 (PBR)
3.16 数据存储寄存器 (DBR)
3.20 状态寄存器
3.21 模拟位 E: 隐藏位
3.22 十六位用户寄存器
3.23 变址寄存器选择
3.24 累加器/内存选择
3.30 设定状态寄存器位
4.00 65816模拟模式设计模型
4.01 模拟模式处理器的状态寄存器 (P)
4.10 模拟模式寄存器
5.00 重新定位堆栈和直接页面
6.00 寻址方式
6.10 新的65816特殊寻址方式
6.20 寻址方式描述
7.00 中断
7.10 硬件向量

附录 A: 65816指令集
附录 B: 指令一览表
附录 C: 集成电路引脚

1.00 介绍

欢迎来到65816的编程世界。65816是经典的6502中央处理器的一种升级产品。 它主要的新特征包括可对高达16MB内存进行直接访问的完全24位寻址；提供了可完全模拟6502的模拟模式，新的寻址方式，完全的16位用户寄存器和几十个新的指令。

如果你是一个资深的6502程序员,你将会很快地了解65816的优点。无论处理器的操作速度如何，藉着16位用户寄存器的优势，65816总是操作得要快一些。 由于在操作（比如加、减）中使用了16位寄存器，65816为程序员提供了速度的实质性提高。

接下来的文档不打算写成一个65816的编程指南，但是可以作为那些熟悉6502并且希望找到新的65816资料的人的一本入门书。

65816提供两种主要的操作模式，原本的65816模式和6502模拟模式。处理器的通电后默认状态是在6502模拟模式。除了修正了6502中的一些BUG，模拟模式下的65816无论在哪方面看都像是一个6502。

在65816的模拟模式和最初的6502有一些值得注意的不同之处。一些6502没有使用的操作码，现在已经作为65816的附加操作而可以被使用了，如此它们将不会得到和在旧的6502处理器上获得的相同结果。无论是在模拟模式还是在原本模式下，65816都使用了所有可能采用的256个操作码。

6502和模拟模式之间还有一个差异就是那间接的跳转指令JMP($XXXX)的BUG，当低位$XX被固定的时候它会使处理器取回错误的数据。

在模拟模式下，65816的直接零页面寄存器设置为零。堆栈指针的高位设置为一(和6502一样)。程序和数据存储寄存器的初值设置为零。所有指令的占用时间是一样的。

在原本模式下，通过24位的寻址，处理器有完全访问16MB地址空间的能力。 原本模式也使得一些新的和扩展的寄存器变为可用。65816有能力将堆栈和零页面重新定位到开始的64K内存空间内的任何位置。对于Commodore 128的程序员，这听起来会让人怀疑它也像128的内存管理单元那样重新定位零页和堆栈－－因此它从一开始就是落伍的。(说句题外话，在CMD的超级处理器128上编程是很有趣的－－它会给你一个可重新定位的零页面和堆栈。呵呵;))

原本模式也打开了16位用户寄存器的世界。累加器可能是8位或16位的。XY变址寄存器也可能是8位或16位的。累加器也许可以看成两个8位的寄存器（一个可到达而另一个是隐藏的），或者看成一个完整的16位寄存器。当累加器被设置成16位，内存也被用16位（2字节）的方式处理。

另外两个新的寄存器是数据存储寄存器和程序存储寄存器。程序存储寄存器在功能上扩展了程序记数器，使它可以超过24位，而数据存储寄存器则允许代码流程的转移到或者跳转到6502的64K寻址空间之外的地址。

最后,在原本模式下，65816的状态寄存器包括了一些新的位。由于65816有BRK硬件向量，旧的BRK位已经不再需要。BRK位现在被用于选择8位或者16位变址寄存器的X位所取代，然而在模拟模式中,BRK位仍然存在。过去状态寄存器中保留的位5则被16/8位的累加器/内存选择位所取代。还有一个隐藏的模拟位，只有在将它和进位标志交换之后才可以被访问到。

下面是一些新的有趣的东西:
  • XYA寄存器可能是16或8位的。
  • 哇，两个真正的内存区块移动指令。
  • 新的压栈/出栈指令phx，plx，phy，ply。
  • 新的xfer指令tyx，txy。
  • 不载入累加器而设置或者重置任何的内存位。
  • INC（递增）和DEC（递减）累加器。
  • 间接的JSR，Branch Always，或者Branch Long到64K。
  • 零页面被重新命名为直接页面。像许多奇怪的事物一样，这将会开始引起真正的困惑。你能做的唯一事情是，"忘掉它然后继续用它"。
回到顶部

2.00 新的65816指令

有一些新的指令值得简短介绍:

新的移动指令包括:
TXY，TYX 在X和Y之间的传值。
TCD 在累加器和直接页面指针(D)之间传值。
TCS，TSC 在累加器和堆栈指针(S)之间传值。
XBA 交换累加器的低8位和高8位。
XCE 交换模拟位(E)和进位标志(C)的内容。

新的压栈和出栈指令:
PHX，PHY，PLX，PLY XY变址寄存器压栈和出栈。
PHB，PLB 数据存储寄存器压栈/出栈。
PHK 程序存储寄存器压栈(当前内容不出栈)
PHD，PLD 直接页面寄存器压栈/出栈。
PEA 有效绝对地址压栈。
PEI 有效间接地址压栈。
PER 有效相对地址压栈。

其他:
MVN 负方向移动区段。
MVP 正方向移动区段。
STZ 将任何位置内容置零。
BRA 无条件转移。
BRL 在存储段0(64k)中转移到任何的地址。
RTL 返回长整数，多于一个字节出栈。

如你所见，有很多新内容需要消化。这份文档的剩余部分会假定你具有6502的知识，让我们开始吧。
回到顶部

3.00 65816原本模式设计模型

位：
23
15
7
0
累加器(B)
(A或C)
累加器(A)
数据存储寄存器(DBR)
X变址寄存器
Y变址寄存器
0 0 0 0 0 0 0 0
直接页面指针(D)
0 0 0 0 0 0 0 0
堆栈指针(S)
程序存储寄存器(PBR)
程序指针(PC)

3.01 原本模式处理器的状态寄存器 (P)
位：
7
6
5
4
3
2
1
0
e
模拟位，为0表示原本模式
n
v
m
x
d
i
z
c
进位标志，为1表示进位
零，为1表示结果为0
IRQ禁用，为1表示IRQ禁用
十进制，为1表示十进制，为0表示二进制
变址寄存器选择，为1表示8位，为0表示16位
内存/累加器选择，为1表示8位，为0表示16位
溢出，为1表示溢出
负号，为1表示负数

回到顶部

3.10 原本模式寄存器

3.11 累加器
虽然作为一个16位寄存器出现，但它是16位还是8位取决于状态寄存器的位5(也就是M位，内存/累加器选择)的状态。
在8位模式(M=1)下可访问的低8位被指定为A，隐藏但是可交换的高8位被指定为B，当处于完全的16位累加器模态(M=0)，累加器被指定为C。

3.12 X，Y 变址寄存器
X和Y变址寄存器是8位或16位是可选择的。当状态寄存器的位4（就是位X）被设成1，则选择成8位寄存器；当被设成0，则选择成16位寄存器。

3.13 直接页面寄存器 (D)
这个寄存器过去被称为零页面。直接页面指针说明了64K直接页面（零页面）中第一个存储段（存储段0）的位置，直接页面可能被搬到存储段0内的任何的位置。存储段字节(位16-23)用虚线显示，表示直接页面总是在存储段0内部。

3.14 堆栈指针:(S)
像直接(零)页面指针，现在堆栈可以在内存的存储段0(第一个64k)内部完全重新定位。存储段字节用虚线显示，以表示堆栈总是在存储段0的内部。在原本模式下，堆栈没有256字节的长度限制。在模拟模式(e=1)下，堆栈位于第一 页。

3.15 程序存储寄存器:(PBR)
很像下面提到的DBR，程序存储寄存器用来描述超过6502的64K上限的地址。PBR被称为存储段字节或程序指针的最高的8位。流程控制指令如 JSR和JMP，可能跳转到全部的23位地址。PBR用来描述有效地址的最高的8位。当然，用相对寻址的转移是不会转移出当前的存储段的，转移指令在超过$FFFF后又会回到当前存储段内。同样的，程序段也不可能越过存储段的边界－－如果发生这种情况，程序指针会从$FFFF变成$0000。

3.16 数据存储寄存器:(DBR)
一些寻址方式利用了65816可寻址高达16MB的寻址能力，这些模式直接在16MB内存上访问和保存数据。24位地址用DBR作为有效地址的最高的8位。DBR也被称为数据存储寄存器。使用了存储段这个术语，这样高字节仍然表示为指定位置或寄存器的8－15位。
当变址寻址方式的转移越过了64K存储段边界的时候，DBR是临时增量。
回到顶部

3.20 状态寄存器

状态寄存器的位7，6，3，2，1，0(nvdizc)功能和6502的状态寄存器位相同。
不再需要用B暂停位来检测BRK了，取而代之的是用一个新的硬件向量来像IRQ的方法那样指示代码流程到操作系统的ROM处理器。然而，在模拟模式(E=1)下BRK和B就像在6502中一样工作。

3.21 模拟位E:隐藏位
模拟状态位E表示处理器是在6502模拟模式或原本的65816模式，设置成1表示在模拟模式下。处理器在通电的时候就默认在6502模拟模式下。 在6502模拟模式下，处理器在功能上和6502完全一致。除了6502没有使用的操作码之外，其他操作码都和在真正的6502上一样的执行。在模拟模式下，堆栈被定义为页面1，直接页面被定义成零页面，而且数据存储段和程序存储段字节被设成零。在模拟模式下65816附加的操作码也是可用的。
模拟状态位是一个隐藏的或者虚幻的位，是不能被直接设置，检测或清除的。因此，一个新的指令被用于交换进位标志位和模拟位(XCE:交换进位标志位和模拟位)的值。在交换后，检测进位标志就可以指定E位的原先状态。
切换到原本模式使用下列指令:
clc ;进位标志清零。
xce ;交换进位标志位和模拟位。
要返回到模拟模式使用下面指令:
sec ;进位标志设为1。
xce ;交换进位标志位和模拟位。

3.22 十六位用户寄存器
和6502相比，65816的主要优点是累加器和XY变址寄存器宽度可以在8位和16位之间选择。累加器和XY寄存器的宽度可以独立地进行选择。这样你可能选择以下的任何一种组合:
16位累加器 m=0 16位XY寄存器x=0
16位累加器 m=0 8位XY寄存器x=1
8位累加器 m=1 16位XY寄存器x=0
8位累加器 m=1 8位XY寄存器x=1
无论累加器从16位切换到8位还是从8位切换到16位，累加器的高端字节总是保留的。
当XY寄存器从16位切换到8位时，高端字节(8-15位)会丢失。当XY寄存器切换到16位的时候，高端字节会变成零。

3.23 状态位4(X): 变址寄存器选择
状态寄存器位4选择8位16位的XY变址寄存器操作。当x＝1(8位)，XY寄存器以和6502的变址寄存器一样的方式工作。
当x＝0(16位)，X和Y寄存器都变成16位宽。所有X和Y寄存器参与的操作，都在变址寄存器的16位中进行。
当变址寄存器从8位到16位或者从16位到8位切换时，X或Y的高端字节将会被置零。
在模拟模式(E=1)下，位4是B，BRK标志位。

3.24 状态位5: 累加器/内存选择
位5表示是将累加器当做一个8位还是16位寄存器。当在16位模式(m=0)下，所有累加器参与的操作都会在数据的16位上进行。例如，在16位模式下，一个标准的读入操作(lda$1000)会将($1000)读入累加器的低8位而将($1001)读入累加器的高8位。
当累加器从16位切换到8位或从8位切换16位，高端字节完整地被保留。 在8位模式下，累加器的高端字节(B)可以用XBA指令来和低端字节交换。
在模拟模式下，位5不使用。
回到顶部

3.30 设定状态寄存器位

65816原本模式中增加了两个新的指令用于设置和复位状态寄存器的位。这两个指令是SEP(设置处理器状态位)和REP(复位处理器状态位) 他们都使用单一的操作数来指定哪些位将要被设置或复位。举例来说，要设定状态寄存器的位4为1，以获得8位变址寄存器:
SEP #%00010000 ;设置位4
或清除位4来使用16位的XY变址寄存器:
REP #%00010000 ;复位(清除)位4
你可以一次设置或者复位多于一个的位。举例来说，要同时使用16位累加器/内存和16位的XY寄存器使用下列语句:
REP#%00110000 ;设置16位累加器/XY寄存器
回到顶部

4.00 65816模拟模式设计模型

位：
23
15
7
0
累加器(B)
(C)
累加器(A)
数据存储寄存器(DBR)
X变址寄存器
Y变址寄存器
0 0 0 0 0 0 0 0
直接页面指针(D)
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
堆栈指针(S)
程序存储寄存器(PBR)
程序指针(PC)

4.01 模拟模式下处理器的状态寄存器 (P)
位：
7
6
5
4
3
2
1
0
e
模拟位，为1表示模拟模式
n
v
b
d
i
z
c
进位标志，为1表示进位
零，为1表示结果为0
IRQ禁用，为1表示IRQ禁用
十进制，为1表示十进制，为0表示二进制
Break指令，为1表示BRK指令会引起IRQ
溢出，为1表示溢出
负号，为1表示负数
回到顶部

4.10 模拟模式寄存器

上面的6502模拟模式设计模型显示了65816一些有趣的特征。即使在不能使用16位变址寄存器的模拟模式下，你仍然能做下面的事情：
  • 重新定位直接页面。
  • 使用堆栈寻址方式。
  • 交换A累加器和隐藏的B累加器。
  • 程序和数据存储寄存器可以被改变。
  • 使用新增的指令。
    从原本模式向模拟模式转变时会损失或者改变的东西有：
  • 使用16位用户寄存器的能力。状态寄存器的M位和X位都回到它们在6502中的形式。
  • 区块移动指令的效用。区块移动指令使用变址寄存器来指定源区块和目的区块，在模拟模式中只有8位地址可用，你只能在零页面内移动数据，因为高端字节总是零。
  • 零页面在模拟模式下寻址越界会折回，而在原本模式下就会进入下个页面。
  • 堆栈指针总是在页面1上。
    从模拟模式切换到原本模式时，处理器用65816的M和X标志代替B位（Break标志）和位5，并且将它们设为1。这样变址寄存器和累加器/内存仍然是8位模式(和在模拟模式下相同)。其余的状态寄存器位没有变化。堆栈指针仍然在页面1。
    从原本模式切换到模拟模式时，M和X状态寄存器位消失，将累加器和变址寄存器设置为8位。X和Y的低端字节被保留，但是高端字节就失去了。累加器的低端和高端字节都被保留(当然高端字节是隐藏的但可通过XBA指令访问)。堆栈指针则变回八位，高端字节被强行置1，高端字节也失去了。
    我想，当你用过65816的模拟模式后，你会认识到模拟模式并没有使你在原本模式上失去什么，而是让你在6502上可以获得很多。事实上，所有新增加的操作码以及指令在模拟模式中仍然可以使用，它们是为甚至没有16位寄存器的强大处理器而设计的。
    回到顶部

    5.00 重新定位堆栈和直接页面:

    通电后，堆栈被放到页面1，直接页面(零页面)被放到页面0。在模拟模式(E=1)下，堆栈被初试化到页面1，零页面被初试化到页面0，以模拟6502的默认状态。
    重新定位直接页面(以前称为零页面)是通过使用PLD：直接页面出栈指令来实现的。
    LDA #$5900 ;累加器直接读入16位数据
    PHA ;累加器入16位栈。
    PLD ;把数据出栈到直接页面寄存器。
    要时刻记住PLD从栈里面取出了16位(2字节)。你也可以使用TCD：传送C寄存器的数值到直接页面寄存器。(C寄存器是16位的累加器)
    LDA #$5900 ;累加器直接读入16位数据
    TCD ;传送累加器的数值到直接页面寄存器。
    尽管通常你都会想让直接页面在一个页面的边界处(低端字节为零)开始，你仍然能指定地址的低端字节为1－255之间的任何值。然而，如果直接页面寄存器的低端字节不是零的话，所有的直接页面(零页面)寻址方式都将会增加一个执行时钟周期。
    在模拟模式下，直接页面寻址方式会将超出直接页面的寻址变址折回到直接页面的开始处继续寻址，就像6502那样。举例来说：
    LDX #$2C
    LDA ($E0,X)
    该例子会产生一个有效的地址$0C而不是$10C。
    在原本模式下，直接页面寻址方式会在寻址变址超过直接页面时进入下一个内存页面。使用上面的例子会产生预期的$10C有效地址。
    对于那些资深的Commodore 128程序员来说，重新定位直接页面的观念完全不是新的－－“我们已在MMU上和它打交道达数年之久”。然而，在有良好的编程风格的支持下，重新定位零页面到除了页面边界之外的地方具有很大的意义。
    重新定位直接页面的优点，与6502的零页面寻址是一样的。你能通过使用直接页面（零页面）寻址来保存数据同时获得更快的执行速度。找到最佳的方法来为重新定位的直接页面编程是需要花一些时间的，但一旦你掌握了它，你就会很习惯的使用它。
    回到顶部

    6.00 寻址方式

    65816的原本模式支持所有的6502和65C02的寻址方式和操作码。在原本模式和模拟模式下都支持九个其他的新的寻址方式。然而，在65816原本模式和6502模拟模式在寻址上有一些值得注意的不同。
    在模拟模式下，使用零页面变址寻址时，对超过了$FF的(基地址+变址)进行寻址，将不会有页面的折回循环出现。而在65816原本模式下变址可以是16位，因此如果(基地址+变址)超过了本页面，也会生成正确的有效地址。
    在使用16位变址寄存器时，直接页面寻址方式在遇到超出$FFFF的地址时，有效地址会返回到当前的存储段而不是下一个存储段。比如：
    lda $20, x
    x是$FFFF，将会得到一个有效的地址$1F。
    当使用绝对变址寻址时，基地址为$FF01到$FFFF，如果变址会使有效地址超过$FFFF，则会进入下一个存储段。然而在6502上，这种情况会使得寻址在零页面之内折回循环。
    请记住，当变址寄存器是16位时，绝对变址X或绝对变址Y能访问到完整的64k！比如：
    lda $6000, y
    y是$2000，将会得到一个有效地址$8000。
    85618还修正了6502的间接JMP的BUG。现在JMP($12FF)将会得到正确的地址$12FF-$1300。
    回到顶部

    6.10 新增的65816特殊寻址方式

    新增寻址方式 | 例 子
    程序指针相对长程寻址 | BRL $1234
    堆栈相对寻址 | LDA 15,S
    堆栈相对间接，Y变址寻址 | LDA (9,S),Y
    区段移动 | MVP 0,0
    绝对长程寻址 | LDA $123456
    绝对变址X长程寻址 | LDA $123456,X
    绝对变址间接寻址 | JMP ($1234,X)
    绝对间接长程寻址 | JMP [$1234]
    直接页面间接寻址 | LDA ($12)
    直接页面间接长程寻址 | LDA [$12]
    直接页面间接长程，变址Y寻址 | LDA [$77],Y
    回到顶部

    6.20 寻址方式描述

    程序指针相对长程寻址: BRL #$44

    程序指针相对长程寻址把转移指令的范围从标准的-127/+128扩展到64k(+32767/-32768)。这样，BRL转移指令的操作数就是16位的。这种寻址方式在写重新定位代码的时候会有很大用处。

    堆栈相对寻址: LDA 7,S

    堆栈相对寻址把堆栈指针作为基地址，然后加上用户提供的单字节操作数作为偏移量。S指明该模式是通过堆栈指针进行的堆栈寻址。
    当使用堆栈相对寻址寻址时，你应该记住堆栈指针将总是指向堆栈内下一个可到达的点。因此，一个为1的操作数将会取回最近一个压入堆栈的项。一个为0的操作数也许可以用来获得最近从堆栈弹出的项的一个副本，但是，当然，如果发生了一个中断，那么你会遇到麻烦，因为此时堆栈为中断程序所操纵。

    堆栈相对间接，Y变址寻址: LDA ($22,S),Y

    这种寻址方式，找出指向位于别处的数据的间接地址(和零页面间接变址寻址一样)。该两字节指令以堆栈的当前位置为基准，加上第一个操作数，最后加上Y变址寄存器的内容作为偏移量。
    这种寻址方式的价值在于，假定你有一个已经压入堆栈的地址，通过用这种寻址方式你能容易地到达到数据所在的位置。
    10 LDY #0
    20 LDA (1,S),Y ;获得16位地址
    30 TAX ;把它保存到X
    40 LDY #2
    50 LDA (1,S),Y ;取出第二地址
    在上面的例子中，我们用堆栈指针的当前位置作为基准地址。在第20行，我们通过(1,S)中的偏移量读取数据，然后加上偏移量Y以获得有效的地址。上述是假定我们处于16位变址和16位累加器模式中。

    区段移动: MVP 0,0

    这是一种主要的新增寻址方式，为65816的两条指令所使用。
    区段正向移动和区段反向移动这两条指令可以将最大达64K的数据从内存的一个位置移动到另外一个位置。要进行一次移动，累加器要读入要拷贝的字节数，X寄存器要读入拷贝源的地址，Y寄存器则保存拷贝目的的地址。然后执行区段移动指令，数据是以每7个周期一个字节的速度移动。

    绝对长程寻址: LDA $123456

    绝对长程寻址用来在65816的16位地址空间内找出任何的数据。操作数是三个字节(24位长)。该寻址方式的主要用途是可以在执行单一指令时暂不理会DBR数据存储寄存器的内容。如果使用标准的绝对寻址方式，存储段字节就会和来自DBR的地址相关联。

    绝对变址X长程寻址: LDA $123456,X

    绝对变址X长程寻址以操作数为基准，加上X变址以产生一个有效的地址。这和6502的绝对变址X寻址是一样的，除了作为基准的操作数是24位宽以外。请注意，真实的存放次序是操作码，低端字节，高端字节，存储段字节，汇编程序会将这些字节按这种次序排列。

    绝对变址间接寻址 JMP ($1234,X)

    绝对变址间接寻址是一个三字节指令，它以指定的操作数为基准，加上变址后获得有效的地址。6502程序员会认识下面的命令查找和执行的例子：
    SEC
    SBC "0" ;累加器减去ASCII的零
    ASL ;累加器左移
    TAX ;放入X内，作为单词表的偏移量
    LDA TABLE,X ;获得命令的地址字节
    STA PTR ;保存在间接指针中
    LDA TABLE+1,X ;获得表内命令地址的高端字节
    STA PTR+1 ;保存在指针的高端字节中
    JMP(PTR) ;跳转到命令处

    TABLE .WORD RUT1,RUT2,RUT3,...
    PTR .BYTE 0,0
    使用绝对变址间接寻址寻址，上面的例子可以只用一半字节数和周期数写出来：
    SEC
    SBC "0" ;累加器减去ASCII零
    ASL ;累加器左移
    TAX ;放入X内，作为单词表的偏移量
    JMP (TABLE,X) ;跳转到命令处

    TABLE .WORD RUT1,RUT2,RUT3,...
    变址间接的JMP[JMP ($1234,x)]假定操作数地址在当前程序存储段内。间接的JMP[JMP ($1234)]假定操作数地址在存储段0中。

    绝对间接长程寻址: JMP [$1234]

    该寻址方式会通过操作数所指向的位置形成有效地址

    直接页面间接寻址: LDA ($12)

    这个指令是两个字节长。操作数指向一个16位的直接页面(零页面)指针，该指针可以形成有效地址。举例来说，如果直接页面指针是 $70，那么低端字节来自$70，高端字节来自$71，存储段字节是当前数据存储段寄存器的值。

    直接页面间接长程寻址: LDA [$12]

    这个指令是两个字节长。操作数指向一个24位的直接页面(零页面)指针。举例来说，如果直接页面指针是$70，那么低端字节来自$70，高端字节来自$71，存储段字节来自$72，存储段字节会暂时和数据库寄存器无关。

    直接页面间接长程，变址Y寻址: LDA [$77],Y

    这个指令是两个字节长，允许你暂时到达内存的任何位置。操作数是一个直接页面(零页面)的指针。位于直接页面的偏移地址是三个字节长：首先是低端字节，然后是高端字节，最后是存储段字节。Y变址寄存器被加到这三字节的目的地址以获得有效地址。使用了方括号来表示该地址是完整的24位地址，而不是一个普通的16位地址。
    回到顶部

    7.00 中断

    65816的中断有一些奇怪的事情是你应该考虑的。如果你打算写65816原本模式下的代码，你应该考虑到你的中断程序如何被处理。如果你写过在原本模式下的IRQ程序，那么你需要考虑的东西可以少一些。然而，如果你正在写使用16位累加器和/或16位变址寄存器的65816原本模式下的程序，并且你是使用模拟模式下的核心IRQ，那么你必须写一些代码来处理错误。在6502模拟模式下，IRQ先将程序指针的高端字节压栈，然后是程序指针的低端字节，最后是状态寄存器。在原本模式下，IRQ按下面次序压栈：
    程序指针存储段 (PBR)
    程序指针高端字节
    程序指针低端字节
    状态寄存器
    然后，状态寄存器的十进位模式位(d)被清除(设为二进制模式)，IRQ标志被设置(只是非NMI)，最后，程序存储段(PBR)被置零，65816通过IRQ硬件向量进行跳转。
    这暗示着，如果65816在模拟模式下的一个非零ram存储段内运行，程序存储段将会丢失(不好)。有两种解决该问题的方法。第一种，避免在模拟模式下的ram存储段0以外运行。第二种，你可以先将当前的程序存储段的值保存在ram的某个位置，然后再在模拟模式的超过存储段0的位置运行。
    有一件事情现在仍不清楚：CMD超级处理器是否能良好地处理原本模式下的IRQ。原本模式具有一张新的硬件向量表。
    回到顶部

    7.10 硬件向量

    65816原本模式 | 6502模拟模式
    IRQ | $FFEE-$FFEF | IRQ/BRK | $FFFE-$FFFF
    RESET | $FFFC-$FFFD
    NMI | $FFEA-$FFEB | NMI | $FFFA-$FFFB
    ABORT | $FFE8-$FFE9 | ABORT | $FFF8-$FFF9
    BRK | $FFE6-$FFE7
    COP | $FFE5-$FFE6 | COP | $FFF4-$FFF5

    可以发现在原本模式有独立BRK向量，不需要位5作为BRK标志了。然而在模拟模式中运行时，请记住位5仍然是BRK标志，你的IRQ仍会需要检查IRQ的来源。
    在原本模式下，一个BRK指令是两字节。为零的BRK操作码后面是可选的 "签名"字节。这样，你在除错的时候就能发现是哪一个BRK引起的矢量被采用了。
    COP是为协处理器准备的中断。(见指令COP)
    请注意，在原本模式下IRQ向量的目的地址在......嗯
    当一个IRQ被引起时，当前的指令会在中断被处理之前完成。这个“中断潜伏期”可能最大达7个时钟周期。如果你在运行一个很紧急的IRQ，你可以检查WAI：等待中断指令，你可以停止处理器直到一个中断发生。
    上面列出ABORT向量可以在65816的ABORT引脚被去掉时采用，这个引脚只在65816上可用。
    回到顶部

    附录一: 65816指令集

    语法约定
    addr | 2字节地址
    addr/const | 2字节值：是一个地址或一个常数
    const | 1－2字节的常数
    destbk | 字符串将被移动到的64K存储段
    dp | 1字节直接页面(对6502是零页面)偏移量
    label | 代码标签和指令放在相同的64K存储段内
    long | 3字节的地址(包括存储段字节)
    nearlabel | 非常靠近指令的代码标签，可以用一个字节的有符号偏移量到达
    sr | 1字节的堆栈相对偏移量
    srcebk | 字符串将被移出的源64K存储段

    标志
    位
    7 6 5 4 3 2 1 0
    n v m x d i z c
    e
    n - 负的
    v - 溢出
    m - 8/16位内存/累加器
    x - 8/16位变址寄存器
    d - 十进位模式
    i - IRQ禁用/启用
    z - 零结果
    c - 进位
    e - 模拟

    ADC：加累加器（带进位）

    当使用16位累加器模式的时候，和预想的一样，操作产生的进位会正确地从从位7进入位8。(比如：$FF+4=$0103，1在累加器的高端字节，3在累加器的低端字节。)这样，只有在低端字节被加时才有清除进位的必要。和在8位累加器模式下一样，手动检测位15上的进位仍是必要的。
    在16位模式下，低端字节位于有效地址上，而高端字节则位于有效地址的下一个单元上。
    该指令会影响的标志：nv----zc
    n：在结果的最高有效位被设置时设置。
    v：如果出现有符号的溢出则设置。
    z：如果结果是零则设置。
    c：如果值满了则设置。

    寻址方式 语法 操作码 指令长度 周期 备注
    直接寻址 ADC #const
    69
    2*
    2
            1
    绝对寻址 ADC addr
    6D
    3
    4
            1
    绝对长程寻址 ADC long
    6F
    4
    5
            1
    直接页面寻址 ADC dp
    65
    2
    3
            1,2
    直接页面间接寻址 ADC (dp)
    72
    2
    5
            1,2
    直接页面间接长程寻址 ADC [dp]
    67
    2
    6
            1,2
    绝对变址X寻址 ADC addr,X
    7D
    3
    4
            1,3
    绝对变址X长程寻址 ADC long,X
    7F
    4
    5
            1
    绝对变址Y寻址 ADC addr,Y
    79

    3
    4
            1,3
    直接页面变址X寻址 ADC dp,X
    75
    2
    4
            1,2
    直接页面变址X，间接寻址 ADC (dp,X)
    61
    2
    6
            1,2
    直接页面间接，变址Y寻址 ADC (dp),Y
    71
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 ADC [dp],Y
    77
    2
    6
            1,2
    堆栈相对寻址 ADC sr,S
    63
    2
    4
            1
    堆栈相对间接，变址Y寻址 ADC (sr,S),Y
    73
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    AND：与累加器

    一般是将位于操作数给出的有效地址的数据和累加器进行“与”操作。如果在16位累加器模式(m=0)下，从内存取出的数据是16位宽的，低端字节位于有效地址而高端字节位于有效地址的下一个单元。
    该指令会影响的标志：n-----z-
    n：在结果的最高有效位被设置时设置。
    z：如果结果是零则设置。

    寻址方式 语法 操作码 指令长度 周期 备注
    直接寻址 AND #const
    29
    2*
    2
            1
    绝对寻址 AND addr
    2D
    3
    4
            1
    绝对长程寻址 AND long
    2F
    4
    5
            1
    直接页面寻址 AND dp
    25
    2
    3
            1,2
    直接页面间接寻址 AND (dp)
    32
    2
    5
            1,2
    直接页面间接长程寻址 AND [dp]
    27
    2
    6
            1,2
    绝对变址X寻址 AND addr,X
    3D
    3
    4
            1,3
    绝对变址X长程寻址 AND long,X
    3F
    4
    5
            1
    绝对变址Y寻址 AND addr,Y
    39

    3
    4
            1,3
    直接页面变址X寻址 AND dp,X
    35
    2
    4
            1,2
    直接页面变址X，间接寻址 AND (dp,X)
    21
    2
    6
            1,2
    直接页面间接，变址Y寻址 AND (dp),Y
    31
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 AND [dp],Y
    37
    2
    6
            1,2
    堆栈相对寻址 AND sr,S
    23
    2
    4
            1
    堆栈相对间接，变址Y寻址 AND (sr,S),Y
    33
    2
    7
            1

    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    ASL：算术左移

    所有的位左移，最高有效位移入进位标志。如果在16位累加器模式(m=0)下，被移动的数据是16位的。
    该指令会影响的标志：n-----zc
    n：在结果的最高有效位被设置时设置。
    z：如果结果是零则设置。
    c：如果高位（位7或位15）移入进位则设置。

    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    累加器寻址 | ASL a | 0A | 1 | 2
    绝对寻址 | ASL addr | 0E | 3 | 6 | 1
    直接页面寻址 | ASL dp | 06 | 2 | 5 | 1,2
    绝对变址X寻址 | ASL addr,X | 1E | 3 | 7 | 1
    直接页面变址X寻址 | ASL dp,X | 16 | 2 | 6 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    转移指令：

    下面的转移指令和在6502下表现相同，因此不会作深入的讨论。转移指令不会影响任何的状态寄存器标志。单字节操作数的范围是+128到-127。
    BCC 进位清除则转移（别名BLT：小于时转移）
    BCS 进位设置则转移（别名BGE：大于或等于时转移）
    BEQ 如果相等则转移
    BNE 如果不相等则转移
    BMI 如果结果为负数则转移
    BPL 如果结果为正数则转移
    BVC 如果溢出标志被清除则转移
    BVS 如果溢出标志被设置则转移

    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    程序指针相对寻址 | BCC near | 90 | 2 | 2 | 1,2
    程序指针相对寻址 | BCS near | B0 | 2 | 2 | 1,2
    程序指针相对寻址 | BEQ near | F0 | 2 | 2 | 1,2
    程序指针相对寻址 | BNE near | D0 | 2 | 2 | 1,2
    程序指针相对寻址 | BMI near | 30 | 2 | 2 | 1,2
    程序指针相对寻址 | BPL near | 10 | 2 | 2 | 1,2
    程序指针相对寻址 | BVC near | 50 | 2 | 2 | 1,2
    程序指针相对寻址 | BVS near | 70 | 2 | 2 | 1,2
    1 | 如果转移被捕获，则增加一个周期
    2 | 如果在模拟模式(e=1)下，则增加一个以上的周期

    新的转移指令：

    BRA：无条件转移
        无条件转移按操作数转移而不关心状态寄存器的当前状态。单字节操作数的范围是+128到-127。该指令和后面的BRL指令使得写重定位的代码变得容易。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    程序指针相对寻址 | BRA near | 80 | 2 | 3 | 1
    1 如果在6502模拟模式(e=1)下，则增加一个以上的周期

    BRL：无条件长程转移
        和BRA一样，但操作数是2个字节，给指令提供了64k的范围。这个指令和JMP指令类似，区别在于JMP是绝对寻址而BRL是相对寻址。
        该指令不会影响任何标志。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    程序指针相对长程寻址 | BRL $xxxx | 82 | 3 | 4

    BIT：检测累加器的某个位

        65816为旧的标志BIT指令提供了3种新的寻址方式。唯一不同的是当处理器处于16位模式下，送到状态寄存器的最高两位是位14和为15；而在8位模式下，位6和位7会被送到状态寄存器。
        该指令会影响的标志　nv----z-　( 除直接寻址之外)
                            ------z-　( 在直接模式下)
    n 取数据的最高有效位的值。
    v 取数据的位6或位14的值。
    z 如果mem和acc的逻辑与为零则设置。

    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    直接寻址 | BIT #const | 89 | 2* | 2
    绝对寻址 | BIT addr | 2C | 3 | 6 | 1
    直接页面寻址 | BIT dp | 24 | 2 | 5 | 1,2
    绝对变址X寻址 | BIT addr,X | 3C | 3 | 7 | 1
    直接页面变址X寻址 | BIT dp,X | 34 | 2 | 6 | 1,2
    * 如果m=0(16位内存/累加器)，则增加1个字节
    1 | 如果m=0(16位内存/累加器)，则增加1个周期
    2 | 如果直接页面寄存器的低端字节不是0，则增加1个周期
    3 | 如果增加变址越过页面边界，则增加1个周期

    BRK：软暂停

        在65816的原本模式下，BRK不受中断禁用标志I的影响。另外，你可以跳过一个字节的签名字节，它指示出是哪个BRK指令使得BRK被执行。新的BRK处理器包含一个硬件向量－－因此不再需要通过IRQ来检测BRK位。
        当BRK在65816的原本模式下运行时：
  • 程序指针存储寄存器被压入堆栈。
  • 程序指针增加2并且压入堆栈。
  • 状态寄存器压入堆栈。
  • 设置中断禁用标志。
  • 清除十进制模式标志。
  • 程序存储寄存器被清零。
  • 程序指针从位于$FFE6－$FFE7的暂停向量读取。
        在6502模拟模式(e=1)下，BRK和它在6502里的前身(b标志设定，状态压入堆栈，SEI和IRQ执行)一样被执行。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    堆栈/中断寻址 | BRK | 00 | 2* | 7 | 1
    * BRK是一个字节，但是压入堆栈的程序指针要增加2，因为有可选的签名字节
    1 如果在65816的原本模式(e=0)下，则增加一个周期

    状态寄存器设置和清除：

    下面列出的状态设置和复位指令在65816的原本模式和6502模拟模式下的效果是一样。
    CLC 清除进位标志。
    CLD 清除十进制标志。
    CLI 清除中断标志。
    CLV 清除溢出标志。
    SEC 设置进位标志。
    SED 设置十进制标志。
    SEI 设置中断标志。
    寻址方式
    标志：nvmxdizc     		助记符 操作码 指令长度 周期
    隐式寻址 | - - - - - - - 0 | CLC | 18 | 1 | 2
    隐式寻址 | - - - - 0 - - - | CLD | D8 | 1 | 2
    隐式寻址 | - - - - - 0 - - | CLI | 58 | 1 | 2
    隐式寻址 | - 0 - - - - - - | CLV | B8 | 1 | 2
    隐式寻址 | - - - - - - - 1 | SEC | 38 | 1 | 2
    隐式寻址 | - - - - 1 - - - | SED | F8 | 1 | 2
    隐式寻址 | - - - - - 1 - - | SEI | 78 | 1 | 2

    CMP：将累加器和内存数据作比较

    大致上，这个指令在6502模拟模式下和在65816模式下工作是一样的。
    在16位累加器模式下，用于比较的数据低端字节来自有效地址，高端字节来自有效地址的下一个存储单元。
    该指令会影响的标志　n-----zc
    n：在结果的最高有效位被设置时设置。
    z：如果结果是零则设置。
    c：如果不需要借位则设置。累加器大于等于内存数据。
      如果需要借位则c=0。累加器小于内存数据。
    寻址方式 语法 操作码 指令长度 周期 备注
    直接寻址 CMP #const
    C9
    2*
    2
            1
    绝对寻址 CMP addr
    CD
    3
    4
            1
    绝对长程寻址 CMP long
    CF
    4
    5
            1
    直接页面寻址 CMP dp
    C5
    2
    3
            1,2
    直接页面间接寻址 CMP (dp)
    D2
    2
    5
            1,2
    直接页面间接长程寻址 CMP [dp]
    C7
    2
    6
            1,2
    绝对变址X寻址 CMP addr,X
    DD
    3
    4
            1,3
    绝对变址X长程寻址 CMP long,X
    DF
    4
    7
            1
    绝对变址Y寻址 CMP addr,Y
    D9

    3
    4
            1,3
    直接页面变址X寻址 CMP dp,X
    D5
    2
    4
            1,2
    直接页面变址X，间接寻址 CMP (dp,X)
    C1
    2
    6
            1,2
    直接页面间接，变址Y寻址 CMP (dp),Y
    C1
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 CMP [dp],Y
    D7
    2
    6
            1,2
    堆栈相对寻址 CMP sr,S
    C3
    2
    4
            1
    堆栈相对间接，变址Y寻址 CMP (sr,S),Y
    D3
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    COP 可使用协同处理器

    COP通过一个独立的COP硬件向量引起一个软件中断。该硬件向量位于$FFF$－$FFF5。在6502模拟模式下：
  • 程序指针增加2并压入堆栈。
  • 状态寄存器压入堆栈。
  • 中断状态位被设置。
  • 程序指针从硬件向量载入。
  • 十进制标志被清除。
    在原本模式中：
  • 程序指针存储寄存器压入堆栈。
  • 程序指针增加2并压入堆栈。
  • 状态寄存器压栈。
  • 中断状态标志被设置。
  • 程序存储寄存器被清零。
  • 程序指针从硬件向量载入。
  • 在COP运行后，十进制标志被清除。
    会被改变的标志　----di--
    d：十进制模式标志被复位成零。
    i：中断禁用标志被设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    堆栈/中断寻址 | COP | 02 | 2* | 7 | 1
    * COP是一个字节，但是压入堆栈的程序指针要加2，因为有可选的签名字节
    1 如果在65816/65802的原本模式(e=0)下，则增加一个周期

    CPX：把X变址寄存器与内存数据作比较

    CPX和在6502下一样地工作，唯一值得注意的例外是要记住在16位变址寄存器模式(x=0)下，内存数据将是16位宽的。低端字节会来自有效地址而高端字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----zc
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    c：如果不需要借位则设置。(X大于等于内存数据)
      如果需要借位则设置。(X小于内存数据)
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    直接寻址 | CPX #const | E0 | 2* | 2 | 1
    绝对寻址 | CPX addr | EC | 3 | 4 | 1
    直接页面寻址 | CPX dp | E4 | 2 | 3 | 1,2
    * 如果m=0(16位内存/累加器)，则增加1个字节
    1 | 如果m=0(16位内存/累加器)，则增加1个周期
    2 | 如果直接页面寄存器的低端字节不是0，则增加1个周期

    CPY：把Y变址寄存器与内存数据作比较

    CPY和在6502下一样地工作，唯一值得注意的例外是要记住在16位变址寄存器模式(x=0)下，内存数据将是16位宽的。低端字节会来自有效地址而高端字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----zc
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    c：如果不需要借位则设置。(Y大于等于内存数据)
      如果需要借位则设置。(Y小于内存数据)
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    直接寻址 | CPY #const | C0 | 2* | 2 | 1
    绝对寻址 | CPY addr | CC | 3 | 4 | 1
    直接页面寻址 | CPY dp | C4 | 2 | 3 | 1,2
    * 如果m=0(16位内存/累加器)，则增加1个字节
    1 | 如果m=0(16位内存/累加器)，则增加1个周期
    2 | 如果直接页面寄存器的低端字节不是0，则增加1个周期

    DEC：内存单元递减

    DEC也几乎和在6502模式下一样地工作。在16位累加器/内存模式(m=0)下，被改变的数据将是16位宽，低端字节会自动向高端字节借位。低端字节会来自有效地址而高端字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    累加器寻址
    DEC a
    别名：DEA     		3A 1 2
    绝对寻址 | DEC addr | CE | 3 | 6 | 1
    直接页面寻址 | DEC dp | C6 | 2 | 5 | 1,2
    绝对变址X寻址 | DEC addr,X | DE | 3 | 7 | 1
    直接页面变址X寻址 | DEC dp,X | D6 | 2 | 6 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    DEX，DEY：变址寄存器递减

    两个指令都像在6502下一样地工作，在16位变址寄存器模式下，变址寄存器将会被当做16位宽。
    会被改变的标志：n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期
    隐式寻址 | DEX | CA | 1 | 2
    隐式寻址 | DEY | 88 | 1 | 2

    EOR：异或累加器

    又是一个和在6502下一样工作的指令，只是有了新的寻址方式。在16位内存/累加器模式下，数据是16位宽－－低端字节会来自有效地址而高端字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 语法 操作码 指令长度 周期 备注
    直接寻址 EOR #const
    49
    2*
    2
            1
    绝对寻址 EOR addr
    4D
    3
    4
            1
    绝对长程寻址 EOR long
    4F
    4
    5
            1
    直接页面寻址 EOR dp
    45
    2
    3
            1,2
    直接页面间接寻址 EOR (dp)
    52
    2
    5
            1,2
    直接页面间接长程寻址 EOR [dp]
    47
    2
    6
            1,2
    绝对变址X寻址 EOR addr,X
    5D
    3
    4
            1,3
    绝对变址X长程寻址 EOR long,X
    5F
    4
    5
            1
    绝对变址Y寻址 EOR addr,Y
    59

    3
    4
            1,3
    直接页面变址X寻址 EOR dp,X
    55
    2
    4
            1,2
    直接页面变址X，间接寻址 EOR (dp,X)
    41
    2
    6
            1,2
    直接页面间接，变址Y寻址 EOR (dp),Y
    51
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 EOR [dp],Y
    57
    2
    6
            1,2
    堆栈相对寻址 EOR sr,S
    43
    2
    4
            1
    堆栈相对间接，变址Y寻址 EOR (sr,S),Y
    53
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    INC：数据递增

    仍像在6502下的INC指令那样工作。在16位内存/累加器模式(m=0)下，数据按16位处理。一个新增的寻址方式是累加器寻址，那样可以递增累加器。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    累加器寻址
    INC a
    别名：INA     		1A 1 2
    绝对寻址 | INC addr | EE | 3 | 6 | 1
    直接页面寻址 | INC dp | E6 | 2 | 5 | 1,2
    绝对变址X寻址 | INC addr,X | FE | 3 | 7 | 1
    直接页面变址X寻址 | INC dp,X | F6 | 2 | 6 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    INX，INY：变址寄存器递增

    两个指令都像在6502下那样工作。在16位变址寄存器模式下，寄存器将会被当做16位宽处理。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期
    隐式寻址 | INX | E8 | 1 | 2
    隐式寻址 | INY | C8 | 1 | 2

    JMP：跳转到新位置

    JMP跳转到操作数地址。如果执行一个远程跳转，程序指针存储寄存器从目标地址的第三个字节处载入。
    65816的设计者还指出，汇编程序可能可以用JML指令代替JMP远程指令，JML [adr]也是绝对间接长程寻址的。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    绝对寻址 | JMP addr | 4C | 3 | 3
    直接页面间接寻址 JMP (addr)
    62
    3
    5
            1
    绝对变址间接寻址 | JMP (addr,X) | 7C | 3 | 6
    绝对长程寻址
    JMP long
    别名：JML long     		5C 4 4
    绝对间接长程寻址
    JMP [addr]
    别名：JML [addr]     		DC 3 6
    1 在6502模式下，如果地址的低端字节为$FF（比如：地址是$xxFF），会得到错误的结果

    JSR，JSL：跳转到子程序

    JSR的工作方式和在6502下一样，不过增加了新的寻址方式。如果汇编程序中一个绝对地址小于$FFFF，那么会调用标准的JSR指令；如果它大于$FFFF，那么会使用绝对长程寻址方式。标准的JSR指令会跳转到当前的程序存储段内的子程序。
    JSR指令能也够使用变址间接寻址。(参考寻址方式部分的例子)
    JML是四字节指令，它可以JSR到位于任何存储段的子程序。执行该指令的时候，程序指针存储段会在程序指针的高端/低端字节之前压入堆栈。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期
    绝对长程寻址
    JSL long
    别名：JSL long     		22 4 8
    绝对寻址 | JSL addr | 20 | 3 | 6
    绝对变址间接寻址 | JSL (addr,X) | FC | 3 | 8

    LDA：将数据读入累加器

    LDA和标准的6502下的LDA指令几乎是一样的，新特点是使用了新的寻址方式。当状态寄存器被设置为16位内存/累加器模式(m=0)时，载入的数据是16位的，累加器的低端字节来自有效地址而高度字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 语法 操作码 指令长度 周期 备注
    直接寻址 LDA #const
    A9
    2*
    2
            1
    绝对寻址 LDA addr
    AD
    3
    4
            1
    绝对长程寻址 LDA long
    AF
    4
    5
            1
    直接页面寻址 LDA dp
    A5
    2
    3
            1,2
    直接页面间接寻址 LDA (dp)
    B2
    2
    5
            1,2
    直接页面间接长程寻址 LDA [dp]
    A7
    2
    6
            1,2
    绝对变址X寻址 LDA addr,X
    BD
    3
    4
            1,3
    绝对变址X长程寻址 LDA long,X
    BF
    4
    5
            1
    绝对变址Y寻址 LDA addr,Y
    B9

    3
    4
            1,3
    直接页面变址X寻址 LDA dp,X
    B5
    2
    4
            1,2
    直接页面变址X，间接寻址 LDA (dp,X)
    A1
    2
    6
            1,2
    直接页面间接，变址Y寻址 LDA (dp),Y
    B1
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 LDA [dp],Y
    B7
    2
    6
            1,2
    堆栈相对寻址 LDA sr,S
    A3
    2
    4
            1
    堆栈相对间接，变址Y寻址 LDA (sr,S),Y
    B3
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    LDX：将数据读入X寄存器

    LDX和在6502下工作时的唯一不同是在16位变址寄存器模式(x=0)下，数据将会是16位宽的。X寄存器的低端字节来自有效地址而高端字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    直接寻址 | LDX #const | A2 | 2* | 2 | 1
    绝对寻址 | LDX addr | AE | 3 | 4 | 1
    直接页面寻址 | LDX dp | A6 | 2 | 3 | 1,2
    绝对变址Y寻址 | LDX addr,Y | BE | 3 | 4 | 1,3
    直接页面变址Y寻址 | LDX dp,Y | B6 | 2 | 4 | 1,2
    * 如果m=0(16位内存/累加器)，则增加1个字节
    1 | 如果m=0(16位内存/累加器)，则增加1个周期
    2 | 如果直接页面寄存器的低端字节不是0，则增加1个周期
    3 | 如果增加变址越过页面边界，则增加1个周期

    LDY：将数据读入Y寄存器

    LDY和在6502下工作时的唯一不同是在16位变址寄存器模式(x=0)下，数据将会是16位宽的。Y寄存器的低端字节来自有效地址而高端字节来自有效地址的下一个存储单元。
    会被改变的标志　n-----z-
    n：结果的最高有效位被设置时设置。
    z：结果为零时设置。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    直接寻址 | LDY #const | A0 | 2* | 2 | 1
    绝对寻址 | LDY addr | AC | 3 | 4 | 1
    直接页面寻址 | LDY dp | A4 | 2 | 3 | 1,2
    绝对变址X寻址 | LDY addr,X | BC | 3 | 4 | 1,3
    直接页面变址X寻址 | LDY dp,X | B4 | 2 | 4 | 1,2
    * 如果m=0(16位内存/累加器)，则增加1个字节
    1 | 如果m=0(16位内存/累加器)，则增加1个周期
    2 | 如果直接页面寄存器的低端字节不是0，则增加1个周期
    3 | 如果增加变址越过页面边界，则增加1个周期

    LSR：逻辑右移

    又是从标准的6502指令集中完全照搬来的指令。在16位模式(m=0)下，被改变的数据将是16位宽的。
    会被改变的标志　n-----zc
    n：被清除。
    z：结果为零时设置。
    c：位0成为新的进位。
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    累加器寻址
    LSR a     		4A 1 2
    绝对寻址 | LSR addr | 4E | 3 | 6 | 1
    直接页面寻址 | LSR dp | 46 | 2 | 5 | 1,2
    绝对变址X寻址 | LSR addr,X | 5E | 3 | 7 | 1
    直接页面变址X寻址 | LSR dp,X | 56 | 2 | 6 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    区段移动指令

    MVP：正向移动（目标地址>源地址）
    MVN：负向移动（目标地址<源地址） 这是65816的新指令。MVN和MVP可以在没有用户干预的情况下移动内存的数据。 这两个指令都是必需的，这样才能确保数据在进行负方向移动时不会覆盖自身。 移动的源地址从X寄存器中取得，目标地址从Y寄存器中取得。移动数据的长度（16位）从累加器中取得，而不需要理会m标志的值。这个长度值应该是实际移动的长度减去1(比如a=$0000，会移动1个字节)。两个操作数字节指定了来源的64K存储段和目标的64K存储段。操作数字节在汇编程序中的顺序是来源，目标－－然而，实际的二进制输出代码会是目标存储段字节，来源存储段字节，最后才是MVN或者MVP的操作码。当来源地址比目标地址大时，使用MVN进行负方向移动，或者在目标内存段范围小于来源内存段范围时使用MVP。MVN指令使用X和Y寄存器来指示两个被移动的内存段的底部(开始)地址。通过MVN，数据从X中的来源地址移动到在Y中的目标地址，然后X和Y寄存器递增，累加器递减，直到累加器下溢$FFFF。当源地址小于目标地址时，使用MVP进行正向移动，或者在目标内存段范围大于来源内存范围时使用MVN。MVP指令使用X和Y寄存器来指示两个被移动的内存段的顶部地址。数据从X中的来源地址移动到在Y中的目标地址，然后XY寄存器和累加器都递减，直到累加器下溢$FFFF。如果变址寄存器被设成8位模式(x=1)或处理器被设成6502模拟模式，那么数据移动将会被局限在页面零内，因为高端字节总是零。可以很容易地在子程序内使用移动指令，然后在运行时动态地修改代码以交换MVN和MVP操作码，这样可以减少代码的长度。状态寄存器标志不会受移动指令的影响。
    寻址方式 语法 操作码 指令长度 周期
    区段移动 MVN src,dest 54 3 *
    区段移动 MVP src,dest 44 3 *
    * 每7个周期移动一个字节

    NOP：空操作

    和在6502下一样，NOP指令不会影响任何标志
    寻址方式 | 语法 | 操作码 | 指令长度 | 周期
    隐式寻址 | NOP | EA | 1 | 2

    ORA：与累加器。

    功能和6502下的ORA相同，但是用了新的寻址方式。在16位内存/累加器模式(m=0)下，被操作的数据是16位宽的。低端字节来自有效地址，高端字节来自有效地址的下一个存储单元。该指令会影响的标志　n-----zc
    n：在结果的最高有效位被设置时设置。
    z：如果结果是零则设置。
    寻址模式 语法 操作码 指令长度 周期 备注
    直接寻址 ORA #const
    09
    2*
    2
            1
    绝对寻址 ORA addr
    0D
    3
    4
            1
    绝对长程寻址 ORA long
    0F
    4
    5
            1
    直接页面寻址 ORA dp
    05
    2
    3
            1,2
    直接页面间接寻址 ORA (dp)
    12
    2
    5
            1,2
    直接页面间接长程寻址 ORA [dp]
    07
    2
    6
            1,2
    绝对变址X寻址 ORA addr,X
    1D
    3
    4
            1,3
    绝对变址X长程寻址 ORA long,X
    1F
    4
    5
            1
    绝对变址Y寻址 ORA addr,Y
    19

    3
    4
            1,3
    直接页面变址X寻址 ORA dp,X
    15
    2
    4
            1,2
    直接页面变址X，间接寻址 ORA (dp,X)
    01
    2
    6
            1,2
    直接页面间接，变址Y寻址 ORA (dp),Y
    11
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 ORA [dp],Y
    17
    2
    6
            1,2
    堆栈相对寻址 ORA sr,S
    03
    2
    4
            1
    堆栈相对间接，变址Y寻址 ORA (sr,S),Y
    13
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    PEA：有效绝对地址压栈

    PEA将16位的操作数压入堆栈。堆栈指针减2。该指令不会影响任何标志。不像其他使用汇编程序助记符的指令，PEA是将操作数的值压入堆栈，而不是将位于有效地址的数据压入堆栈。其实它更合适的名字应该是直接数据压栈－－不知道为什么它不这么叫。

    举例来说：
    PEA $1234
    会将#$12和#$34依次压栈。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    堆栈(绝对)寻址 | PEA addr | F4 | 3 | 5

    PEI：有效间接地址压栈

    这个65816个指令将有效地址的地址压入堆栈。这个指令总是向堆栈压入16位的数据而不理会m和x状态位的值。有效地址的下一个存储单位的地址首先被压入堆栈，然后是有效地址的地址被压栈。举例来说：假如$5678按标准的低端字节/高端字节的格式被储存在$21/$22，然后执行
    PEI ($21)
    会从$21/$22取出$5678并且压入堆栈。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    堆栈(直接页面间接)寻址 | PEI (dp) | D4 | 2 | 6 | 1
    1 如果直接页面寄存器的低端字节不是0，则增加1个周期

    PER：有效程序指针相对间接地址压栈

    这个指令把程序指针加上16位的操作数，将获得的16位数据压入堆栈。目标地址必须位于当前的64K内存存储段内。用于计算的程序指针的值是PER之后的下一条指令的地址（两个字节）。相加结果的高端字节首先被压栈，然后是低端字节。因为该指令用一个相对的偏移量作为操作数，它会对编写重定位代码有帮助。可以将一个未知的运行时地址压入堆栈，然后将该地址出栈以决定程序运行时起点。该指令的另一种用途是将6502的pha:pha:rts风格的代码的返回地址压入堆栈。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    堆栈(程序指针相对长程)寻址 | PER lable | 62 | 3 | 6

    A，P，X，Y寄存器压栈，出栈指令：

    PHA，PHP，PLA，PLP和他们在6502中的前身基本一样。唯一的不同是在16位累加器/内存(m=0)模式下执行PHA或PLA时，压入堆栈的数据将是16位宽的。（PHP/PLP只在8位上操作）
    新的压栈出堆栈指令包括PHY，PLY，PHX和PLX。这四条新指令将变址寄存器压栈或者出栈。当状态寄存器被设置到16位变址寄存器模式(x=0)，被压栈和出栈的变址寄存器会按16位上操作。
    寻址模式
    标志     		助记符 操作码 指令长度 周期 备注
    堆栈(压栈) - - - - - - - - PHA 48 1 3 1
    堆栈(压栈) - - - - - - - - PHP 08 1 3
    堆栈(压栈) - - - - - - - - PHX DA 1 3 2
    堆栈(压栈) - - - - - - - - PHY 5A 1 3 2
    堆栈(出栈) | n - - - - - z - | PLA | 68 | 1 | 4 | 1
    堆栈(出栈) | n v m x d i z c | PLP | 28 | 1 | 4
    堆栈(出栈) | n - - - - - z - | PLX | FA | 1 | 4 | 2
    堆栈(出栈) | n - - - - - z - | PLY | 7A | 1 | 4 | 2
    1 | 如果是16位内存/累加器(m=0)，则增加1个周期
    2 | 如果是16位变址寄存器(x=0)，则增加1个周期

    存储段寄存器压栈，出栈指令：

    PHB将数据存储段寄存器的8位数据内容压入堆栈。
    PHD将直接页面寄存器的16位数据内容压入堆栈。高端字节被先被压栈，然后是低端字节。
    PHK将程序存储寄存器的8位数据内容压入堆栈。
    PLB将数据存储段寄存器的单字节内容出栈。这是唯一能直接改变数据存储段寄存器的指令。
    PLD将直接页面寄存器的双字节内容出栈。低端字节首先出栈，然后是高端字节。
    出栈指令会影响的标志：
    n：如果出栈的数据的最高有效位被设置，则设置。
    z：如果出栈的数据是零则设置。
    寻址模式
    标志     		助记符 操作码 指令长度 周期
    堆栈(压栈) - - - - - - - - PHB 8B 1 3
    堆栈(压栈) - - - - - - - - PHD 0B 1 4
    堆栈(压栈) - - - - - - - - PHK 4B 1 3
    堆栈(出栈) | n - - - - - z - | PLB | AB | 1 | 4
    堆栈(出栈) | n - - - - - z - | PLD | 2B | 1 | 5

    REP：复位状态位

    REP是一个新增的65816指令。当执行时，它会把由单字节直接数据值指定的位复位(清除)。
    举例来说明如何清除状态寄存器的位5：
    REP #%00100000 ;清除位5
    也可以清除多个位：
    REP #%10110000 ;清除位7，位5和位4
    其实任何组合都是可以的。
    要设置一个位，可以看SEP指令。
    会被影响的标志：nvmxdizc
    所有操作数中指定的位都会被清除，其他的位则不受影响。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    直接寻址 | REP #const | C2 | 2 | 3

    ROL：对内存数据或累加器进行循环左移

    ROL和6502下的ROL指令一样地工作。在16位累加器/内存模式(m=0)下，循环移动的数据是16位宽的，先前的位15变成了新的进位标志。低端字节位于有效地址而高端字节位于有效地址的下一个存储单元。
    会受影响的标志：n-----zc
    n：在结果的最高有效位被设置时设置。
    z：如果结果是零则设置。
    c：高位(位7或位15)变成新的进位。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    累加器寻址
    ROL a     		2A 1 2
    绝对寻址 | ROL addr | 2E | 3 | 6 | 1
    直接页面寻址 | ROL dp | 26 | 2 | 5 | 1,2
    绝对变址X寻址 | ROL addr,X | 3E | 3 | 7 | 1
    直接页面变址X寻址 | ROL dp,X | 36 | 2 | 6 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    ROR：内存数据或累加器循环右移

    该指令和6502下的ROR一样地工作。在16位内存/累加器模式(m=0)下，被循环移动的数据将是16位宽的（加上进位），低端字节位于有效地址而高端字节位于有效地址的下一个存储单元。
    会受影响的标志：n-----zc
    n：在结果的最高有效位被设置时设置。
    z：如果结果是零则设置。
    c：低位变成新的进位。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    累加器寻址
    ROR a     		6A 1 2
    绝对寻址 | ROR addr | 6E | 3 | 6 | 1
    直接页面寻址 | ROR dp | 66 | 2 | 5 | 1,2
    绝对变址X寻址 | ROR addr,X | 7E | 3 | 7 | 1
    直接页面变址X寻址 | ROR dp,X | 76 | 2 | 6 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    RTI：从中断返回

    在6502模拟模式(e=1)下RTI和在旧6502下一样地被处理。在65816的原本模式(e=0)下，RTI还将程序存储寄存器字节从堆栈中弹出。由于有这个特别的字节存在，所以要确保在中断执行时，RTI在和处理器相同的模式(e=?)下执行。
    会受影响的标志有：状态寄存器从堆栈弹出，所以所有的标志都受影响。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    堆栈寻址(RTI) | RTI | 40 | 1 | 6 | 1
    1 如果在65816原本模式，则增加1个周期

    RTL：从子程序长程返回

    RTL和RTS差不多，但它还将程序存储寄存器弹出堆栈。这个指令应该和JSR长程指令或者和一个将程序存储段压入堆栈的子程序联合使用。RTL从堆栈弹出24位数据。首先是两个字节的程序指针的低端/高端字节被弹出并且递增，然后是程序存储寄存器被弹出。
    RTL不会影响任何标志。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    堆栈寻址(RTL) | RTL | 6B | 1 | 6

    RTS：从子程序返回

    和6502下的指令一样。不会影响任何标志。
    RTS的一种有趣的用法是将返回地址压入堆栈，然后通过RTS运行它。为了使用这种方式来编码，压入堆栈的地址必须至少比真正的子程序地址小1，因为出栈后，处理器会在继续执行之前自动把程序指针加1。当在原本模式中并且使用16位累加器/内存时，这能很容易地用下面的代码完成：
    DEC A ;递减16位的累加器。或用DEA。
    PHA ;将16位返回地址压栈。
    RTS ;返回来执行指令。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    堆栈寻址(RTS) | RTS | 60 | 1 | 6

    SBC：从累加器减去

    SBC也和在6502下一样地工作。唯一的不同，又是增加了一些新的寻址方式，以及数据也许要在16位累加器或者16位内存存储单元中工作的事实。
    在16位内存/累加器模式下使用的SBC和ADC会全面地大幅度提高65816 的程序性能，一个程序员能轻易地看出加法和减法子程序在16位方式下操作比在8位方式下操作快多少。
    该指令会影响的标志　nv----zc
    n：在结果的最高有效位被设置时设置。
    v：如果有符号的溢出则设置。
    z：如果结果是零则设置。
    c：如果不需要无符号借位则设置。
    寻址模式 语法 操作码 指令长度 周期 备注
    直接寻址 SBC #const
    E9
    2*
    2
            1
    绝对寻址 SBC addr
    ED
    3
    4
            1
    绝对长程寻址 SBC long
    EF
    4
    5
            1
    直接页面寻址 SBC dp
    E5
    2
    3
            1,2
    直接页面间接寻址 SBC (dp)
    F2
    2
    5
            1,2
    直接页面间接长程寻址 SBC [dp]
    E7
    2
    6
            1,2
    绝对变址X寻址 SBC addr,X
    FD
    3
    4
            1,3
    绝对变址X长程寻址 SBC long,X
    FF
    4
    5
            1
    绝对变址Y寻址 SBC addr,Y
    F9

    3
    4
            1,3
    直接页面变址X寻址 SBC dp,X
    F5
    2
    4
            1,2
    直接页面变址X，间接寻址 SBC (dp,X)
    E1
    2
    6
            1,2
    直接页面间接，变址Y寻址 SBC (dp),Y
    F1
    2
    5
            1,2,3
    直接页面间接长程，变址Y寻址 SBC [dp],Y
    F7
    2
    6
            1,2
    堆栈相对寻址 SBC sr,S
    E3
    2
    4
            1
    堆栈相对间接，变址Y寻址 SBC (sr,S),Y
    F3
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期
    3 | 如果增加变址越过页面边界，则增加一个周期

    SEP：设置状态位

    SEP是一个新的65816指令。当被执行时，它会设置被单字节直接值指定的位。
    这是设置X和M状态寄存器位的唯一方法。
    举例来说，要设定状态寄存器位5：
    SEP #%00100000 ;设置位5
    或要清除多个位：
    SEP #%10110000; 设置位7，位5和位4
    任何组合都是可以的。
    要复位一个位，参考REP。
    会受影响的标志：nvmxdizc
    所有操作数中被设置的位对应的标志都会被设置，其他的标志则不受影响。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    直接寻址 | SEP #const | E2 | 2 | 3

    STA：储存累加器的数据

    寻址模式 语法 操作码 指令长度 周期 备注
    绝对寻址 STA addr
    8D
    3
    4
            1
    绝对长程寻址 STA long
    8F
    4
    5
            1
    直接页面寻址 STA dp
    85
    2
    3
            1,2
    直接页面间接寻址 STA (dp)
    92
    2
    5
            1,2
    直接页面间接长程寻址 STA [dp]
    87
    2
    6
            1,2
    绝对变址X寻址 STA addr,X
    9D
    3
    4
            1
    绝对变址X长程寻址 STA long,X
    9F
    4
    5
            1
    绝对变址Y寻址 STA addr,Y
    99

    3
    4
            1
    直接页面变址X寻址 STA dp,X
    95
    2
    4
            1,2
    直接页面变址X，间接寻址 STA (dp,X)
    81
    2
    6
            1,2
    直接页面间接，变址Y寻址 STA (dp),Y
    91
    2
    5
            1,2
    直接页面间接长程，变址Y寻址 STA [dp],Y
    97
    2
    6
            1,2
    堆栈相对寻址 STA sr,S
    83
    2
    4
            1
    堆栈相对间接，变址Y寻址 STA (sr,S),Y
    93
    2
    7
            1
    * 如果m=0(16位内存/累加器)，则增加一个字节
    1 | 如果m=0(16位内存/累加器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    STP 停止处理器

    STP关闭处理器，直到硬件复位。它被用于一些通过让处理器休眠来减少能耗的系统。65816处理器上有一个复位B引脚，允许使用这个指令。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    隐式寻址 | STP | DB | 1 | 3

    STX：储存X寄存器的值到内存

    又一个和在6502模式下一样工作的指令。唯一例外是当设置为16位变址寄存器(x=0)时，被储存的数据将是16位宽的。X的低8位会被储存到有效地址，而高端字节储存到有效地址的下一个存储单位。
    STX不会影响任何标志。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    绝对寻址 | STX addr | 8E | 3 | 4 | 1
    直接页面寻址 | STX dp | 86 | 2 | 3 | 1,2
    直接页面变址Y寻址 | STX dp,Y | 96 | 2 | 4 | 1,2
    1 | 如果x=0(16位变址寄存器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个以上的周期

    STY：储存Y寄存器的值到内存

    又一个和在6502模式下一样工作的指令。唯一例外是当设置为16位变址寄存器(x=0)时，被储存的数据将是16位宽的。X的低8位会被储存到有效地址，而高端字节储存到有效地址的下一个存储单位。
    STY不会影响任何标志。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    绝对寻址 | STY addr | 8C | 3 | 4 | 1
    直接页面寻址 | STY dp | 84 | 2 | 3 | 1,2
    直接页面变址X寻址 | STY dp,X | 94 | 2 | 4 | 1,2
    1 | 如果x=0(16位变址寄存器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个以上的周期

    STZ：向内存储存零字节

    65816的一个新的指令。STZ储存一个零字节到目的地址。在8位累加器/ 内存模式(m=1)下，只有一个字节被储存到有效地址；在16位内存/累加器模式(m=0)下，零会被储存到有效地址和有效地址的下一个存储单元。
    该指令不会影响任何标志。
    这个指令可以看作旧6502中下列代码的一个替代：
    lda #0
    sta $xxxx
    STZ指令的优势是执行后累加器和状态寄存器都不会变化。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    绝对寻址 | STZ addr | 9C | 3 | 4 | 1
    直接页面寻址 | STZ dp | 64 | 2 | 3 | 1,2
    绝对变址X寻址 | STZ addr,X | 9E | 3 | 5 | 1
    直接页面变址X寻址 | STZ dp,X | 74 | 2 | 4 | 1,2
    1 | 如果x=0(16位变址寄存器)，则增加一个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个以上的周期

    寄存器传送指令：

    TAX，TXA，TAY，TYA，TSX，TXS传送指令在处理器寄存器之间传送数据。
    TAX：传送累加器的值到X变址寄存器。
    TAY：传送累加器的值到Y变址寄存器。
    TYA：传送Y变址寄存器的值到累加器。
    TXA：传送X变址寄存器的值到累加器。
    TSX：传送堆栈指针到X变址寄存器。
    TXS：传送X变址寄存器的值到堆栈指针。
    两个新的寄存器传送指令是：
    TXY：直接传送X寄存器的值到Y寄存器。
    TYX：直接传送Y寄存器的值到X寄存器。
    因为变址寄存器和累加器能独立地被设置成8位或16位，移动的宽度由目标寄存器来决定。下列各项展示了所有可能的组合：
    8位累加器到8位变址寄存器。(m=1,x=1)，8个位被转移。
    8位累加器到到16位变址寄存器。(m=1,x=0)，16个位被转移。隐藏的累加器高端字节变成X或Y的高端字节。
    16位变址寄存器到8位累加器。(m=1,x=0)，8个位被转移。隐藏的累加器高端字节不受影响，并保持原来的值。
    8位变址寄存器到16位累加器。(m=0,x=1)，2个字节被转移，高端字节为零。
    16位累加器到8位变址寄存器。(m=0,x=1)，只有累加器的低端字节被传送到变址寄存器。
    16位累加器到16位变址寄存器。(m=0,x=0)，16个位被转移。
    16位堆栈指针到8位X寄存器。只有地址的低端字节被转移。
    8位X寄存器到16位堆栈指针，堆栈指针的高端字节设为零。
    该指令会影响的标志　n-----z-
    n：要传送的值的最高有效位被设置时设置。
    z：如果要传送值是零则设置。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期
    隐式寻址 | TAX | AA | 1 | 2
    隐式寻址 | TAY | A8 | 1 | 2
    隐式寻址 | TXA | 8A | 1 | 2
    隐式寻址 | TYA | 98 | 1 | 2
    隐式寻址 | TSX | BA | 1 | 2
    隐式寻址 | TXS | 9A | 1 | 2
    隐式寻址 | TXY | 9B | 1 | 2
    隐式寻址 | TYX | BB | 1 | 2

    直接页面指令：

    有两个新的65816个指令用于在累加器和直接页面寄存器之间交换数据：
    TCD：将累加器的值传送到直接页面寄存器。
    TDC：将直接页面寄存器的值传送到累加器。
    TCD将来自累加器的一个16位的值传送到直接(零页面)指针。无论累加器是在16位还是8位的设定下，都会传送全部的16个位。
    在TCD中的C用于指明累加器是作为C来引用的（当它是16位宽时，低端字节是A而高端字节是B）。
    TDC将来自直接页面寄存器的值传送到累加器的全部16个位，而不管累加器的位m的状态。
    一些汇编程序也允许使用TAD或TDA。
    该指令会影响的标志　n-----z-
    n：要传送的值的最高有效位被设置时设置。
    z：如果要传送值是零则设置。
    寻址模式 语法 操作码 指令长度 周期
    隐式寻址
    TCD
    别名：TAD     		5B 1 2
    隐式寻址
    TDC
    别名：TDA     		7B 1 2

    TCS：传送累加器到堆积指针

    TCS传送完整的16位到堆栈指针，而不管状态位m的状态如何。
    和在TCD和TDC中一样，TCS中的C表示累加器是完全16位的。
    TAS作为另一种助记符，也被一些汇编程序使用来传送堆积指针。
    在6502模拟模式下，只有八个位的累加器值被传送，因为在6502下堆栈总是位于页面1。
    TCS和TXS是仅有的两个能改变堆栈指针寄存器的指令。
    TCS不会影响任何标志。
    寻址模式 语法 操作码 指令长度 周期
    隐式寻址
    TCS
    别名：TAS     		1B 1 2

    TSC：传送堆栈指针到累加器

    TSC传送完整的16位堆栈指针到16位的累加器，不管状态位m的值如何。
    和TDC和TCD一样，TSC中的C表示累加器完全是16位的。
    TSA作为另一种助记符，也被一些汇编程序使用来传送值到堆积指针。
    在6502模拟模式下，1会被传送到隐藏的B（高8位）累加器，因为在6502下堆栈总是位于页面1。
    该指令会影响的标志　n-----z-
    n：要传送的值的最高有效位被设置时设置。
    z：如果要传送值是零则设置。
    寻址模式 语法 操作码 指令长度 周期
    隐式寻址
    TSC
    别名：TSA     		3B 1 2

    TRB：测试并复位内存位

    TRB执行一个累加器和有效的地址的逻辑与操作－－然后数据会被写回到指定的地址上。
    该指令会清除内存中和累加器内相对应的位（累加器中为1的位被清除），其他的位则无变化。
    从另外一个角度来说－－TRB翻转累加器的位值然后和内存操作数进行逻辑与操作，再将结果保存回有效地址。
    在16位累加器模式(m=0)下，数据按16位进行操作。操作数的低端字节位于有效地址，高端字节位于有效地址的下一个存储单元。
    该指令会影响的标志　------z-
    z：如果要和累加器与的值是零则设置。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    绝对寻址 | TRB addr | 1C | 3 | 6 | 1
    直接页面寻址 | TRB dp | 14 | 2 | 5 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    TSB：测试并且设置内存位

    TSB将累加器和位于有效地址的数据进行逻辑与操作。
    该指令能高效地设置内存中和累加器内设置位对应的位。
    在16位累加器模式(m=0)下，数据按16位进行操作。操作数的低端字节位于有效地址，高端字节位于有效地址的下一个存储单元。
    在累加器和内存数据值进行逻辑与操作之后，状态寄存器的零标志被设置。（和BIT指令相同）
    该指令会影响的标志　------z-
    z：如果要和累加器与的值是零则设置。
    寻址模式 | 语法 | 操作码 | 指令长度 | 周期 | 备注
    绝对寻址 | TSB addr | 0C | 3 | 6 | 1
    直接页面寻址 | TSB dp | 04 | 2 | 5 | 1,2
    1 | 如果m=0(16位内存/累加器)，则增加两个周期
    2 | 如果直接页面寄存器的低端字节不为零，则增加一个周期

    WAI：等待中断

    WAI将操作挂起，直到外部硬件中断发生。处理器的能耗被降低。如果中断禁用标志(i=1)被设置并且IRQ在执行WAI之前是未决定的，那么等待会结束并继续执行下一条指令。
    WAI不会影响任何标志。
    寻址模式 语法 操作码 指令长度 周期
    隐式寻址
    WAI     		CB 1 3

    WDM：为将来扩充而保留的指令

    WDM是被设计成运行于未来版本处理器上的多字节指令的第一个字节。
    现在的WDM则像NOP那样，被当成空操作。
    这个指令不应该在现在版本的处理器上使用。
    WDM：William D. Mensch, JR（65816的设计者）
    寻址模式 语法 操作码 指令长度 周期

    WDM     		42 2

    XBA：交换B和A累加器

    XBA交换累加器的低8位(A)和高8位(B)。该操作不理会状态位M的值。
    这个指令在6502模拟模式下也能工作。
    XBA能用来在高端累加器上保存低端累加器的一个临时副本。它用来反转16位模式下值的高位和低位也很不错。
    在模拟模式下，XBA是能唯一能存取累加器高端8位的指令。
    一些汇编程序也用SWA(swap)。
    该指令会影响的标志　n-----z-
    n：新的低8位的最高有效位（原来的位15）被设置时设置。
    z：如果新的累加器低8位值为零则设置。
    寻址模式 语法 操作码 指令长度 周期
    隐式寻址
    XBA
    别名：SWA     		EB 1 3

    XCE：交换进位和模拟位

    XCE交换E模拟位和C进位标志的值。这是存取E模拟位的唯一方法。
    要设定模拟模式：
    sec ;设置进位
    xce ;交换进位和模拟位
    要设定原本模式：
    clc ;清除进位
    xce ;交换进位和模拟位
    该指令会影响的标志 　　　　e
                      --mx/b---c
    e：取自原先的进位。
    c：取自原先的模拟位。
    m：只在本地模式下用的标志，要切换到原本模式时设为1 。
    x：只在本地模式下用的标志。
    b：只在6502模拟模式下用的标志。如果设置为1，则会变成原本模式下的x标志。
    寻址模式 语法 操作码 指令长度 周期
    隐式寻址
    XCE     		FB 1 2

    回到顶部

    附录 B: 指令一览表

    操作码 | 助记符 | 寻址方式 | 指令长度 | 周期 | 备注
    00 | BRK | 堆栈/中断寻址 | 2** | 7 | 9
    01 | ORA | 直接页面变址X间接寻址 | 2 | 6 | 1,2
    02 | COP | 堆栈/中断寻址 | 2** | 7 | 9
    03 | ORA | 堆栈相对寻址 | 2 | 4 | 1
    04 | TSB | 直接页面寻址 | 2 | 5 | 2,5
    05 | ORA | 直接页面寻址 | 2 | 3 | 1,2
    06 | ASL | 直接页面寻址 | 2 | 5 | 2,5
    07 | ORA | 直接页面间接长程寻址 | 2 | 6 | 1,2
    08 | PHP | 堆栈寻址(压栈) | 1 | 3
    09 | ORA | 直接寻址 | 2* | 2 | 1
    0A | ASL | 累加器寻址 | 1 | 2
    0B | PHD | 堆栈寻址(压栈) | 1 | 4
    0C | TSB | 绝对寻址 | 3 | 6 | 5
    0D | ORA | 绝对寻址 | 3 | 4 | 1
    0E | ASL | 绝对寻址 | 3 | 6 | 5
    0F | ORA | 绝对长程寻址 | 4 | 5 | 1
    10 | BPL | 程序指针相对寻址 | 2 | 2 | 7,8
    11 | ORA | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3
    12 | ORA | 直接页面间接寻址 | 2 | 5 | 1,2
    13 | ORA | 堆栈相对间接，变址Y寻址 | 2 | 7 | 1
    14 | TRB | 直接页面寻址 | 2 | 5 | 2,5
    15 | ORA | 直接页面变址X寻址 | 2 | 4 | 1,2
    16 | ASL | 直接页面变址X寻址 | 2 | 6 | 2,5
    17 | ORA | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2
    18 | CLC | 隐式寻址 | 1 | 2
    19 | ORA | 绝对变址Y寻址 | 3 | 4 | 1,3
    1A | INC | 累加器寻址 (INA) | 1 | 2
    1B | TCS | 隐式寻址 | 1 | 2
    1C | TRB | 绝对寻址 | 3 | 6 | 5
    1D | ORA | 绝对变址X寻址 | 3 | 4 | 1,3
    1E | ASL | 绝对变址X寻址 | 3 | 7 | 5,6
    1F | ORA | 绝对变址X长程寻址 | 4 | 5 | 1
    20 | JSR | 绝对寻址 | 3 | 6
    21 | AND | 直接页面变址X间接寻址 | 2 | 6 | 1,2
    22 | JSR | 绝对长程寻址 | 4 | 8
    23 | AND | 堆栈相对寻址 | 2 | 4 | 1
    24 | BIT | 直接页面寻址 | 2 | 3 | 1,2
    25 | AND | 直接页面寻址 | 2 | 3 | 1,2
    26 | ROL | 直接页面寻址 | 2 | 5 | 2,5
    27 | AND | 直接页面间接长程寻址 | 2 | 6 | 1,2
    28 | PLP | 堆栈寻址 (压栈) | 1 | 4
    29 | AND | 直接寻址 | 2* | 2 | 1
    2A | ROL | 累加器寻址 | 1 | 2
    2B | PLD | 堆栈寻址 (压栈) | 1 | 5
    2C | BIT | 绝对寻址 | 3 | 4 | 1
    2D | AND | 绝对寻址 | 3 | 4 | 1
    2E | ROL | 绝对寻址 | 3 | 6 | 5
    2F | AND | 绝对长程寻址 | 4 | 5 | 1
    30 | BMI | 程序指针相对寻址 | 2 | 2 | 7,8
    31 | AND | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3
    32 | AND | 直接页面间接寻址 | 2 | 5 | 1,1
    33 | AND | 堆栈相对间接，Y变址寻址 | 2 | 7 | 1
    34 | BIT | 直接页面变址X寻址 | 2 | 4 | 1,2
    35 | AND | 直接页面变址X寻址 | 2 | 4 | 1,2
    36 | ROL | 直接页面变址X寻址 | 2 | 6 | 2,5
    37 | AND | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2
    38 | SEC | 隐式寻址 | 1 | 2
    39 | AND | 绝对变址Y寻址 | 3 | 4 | 1,3
    3A | DEC | 累加器寻址 | 1 | 2
    3B | TSC | 隐式寻址 | 1 | 2
    3C | BIT | 绝对变址X寻址 | 3 | 4 | 1,3
    3D | AND | 绝对变址X寻址 | 3 | 4 | 1,3
    3E | ROL | 绝对变址X寻址 | 3 | 7 | 5,6
    3F | AND | 绝对变址X长程寻址 | 4 | 5 | 1
    40 | RTI | 堆栈寻址/RTI | 1 | 6 | 9
    41 | EOR | 直接页面变址X间接寻址 | 2 | 6 | 1,2
    42 | WDM 　 | 2 　 | 16
    43 | EOR | 堆栈相对寻址 | 2 | 4 | 1
    44 | MVP | 区段移动 | 3 　 | 13
    45 | EOR | 直接页面寻址 | 2 | 3 | 1,2
    46 | LSR | 直接页面寻址 | 2 | 5 | 2,5
    47 | EOR | 直接页面间接长程寻址 | 2 | 6 | 1,2
    48 | PHA | 堆栈寻址 (压栈) | 1 | 3 | 1
    49 | EOR | 直接寻址 | 2* | 2 | 1
    4A | LSR | 累加器寻址 | 1 | 2
    4B | PHK | 堆栈寻址 (压栈) | 1 | 3
    4C | JMP | 绝对寻址 | 3 | 3
    4D | EOR | 绝对寻址 | 3 | 4 | 1
    4E | LSR | 绝对寻址 | 3 | 6 | 5
    4F | EOR | 绝对长程寻址 | 4 | 5 | 1
    50 | BVC | 程序指针相对寻址 | 2 | 2 | 7,8
    51 | EOR | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3
    52 | EOR | 直接页面间接寻址 | 2 | 5 | 1,2
    53 | EOR | 堆栈相对间接变址Y寻址 | 2 | 7 | 1
    54 | MVN | 区段移动 | 3 　 | 13
    55 | EOR | 直接页面变址X寻址 | 2 | 4 | 1,2
    56 | LSR | 直接页面变址X寻址 | 2 | 6 | 2,5
    57 | EOR | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2
    58 | CLI | 隐式寻址 | 1 | 2
    59 | EOR | 绝对变址Y寻址 | 3 | 4 | 1,3
    5A | PHY | 堆栈寻址 (压栈) | 1 | 3 | 10
    5B | TCD | 隐式寻址 | 1 | 2
    5C | JMP | 绝对长程寻址 | 4 | 4
    5D | EOR | 绝对变址X寻址 | 3 | 4 | 1,3
    5E | LSR | 绝对变址X寻址 | 3 | 7 | 5,6
    5F | EOR | 绝对变址X长程寻址 | 4 | 5 | 1
    60 | RTS | 堆栈寻址 (RTS) | 1 | 6
    61 | ADC | 直接页面变址X间接寻址 | 2 | 6 | 1,2,4
    62 | PER | 堆栈寻址 (程序指针相对长程) | 3 | 6
    63 | ADC | 堆栈相对寻址 | 2 | 4 | 1,4
    64 | STZ | 直接页面寻址 | 2 | 3 | 1,2
    65 | ADC | 直接页面寻址 | 2 | 3 | 1,2,4
    66 | ROR | 直接页面寻址 | 2 | 5 | 1
    67 | ADC | 直接页面间接长程寻址 | 2 | 6 | 1,4
    68 | PLA | 堆栈寻址 (压栈) | 1 | 4 | 1
    69 | ADC | 直接寻址 | 2* | 2 | 1,4
    6A | ROR | 累加器寻址 | 1 | 2
    6B | RTL | 堆栈寻址 (RTL) | 1 | 6
    6C | JMP | 绝对间接寻址 | 3 | 5 | 11,12
    6D | ADC | 绝对寻址 | 3 | 4 | 1,4
    6E | ROR | 绝对寻址 | 3 | 6 | 5
    6F | ADC | 绝对长程寻址 | 4 | 5 | 1,4
    70 | BVS | 程序指针相对寻址 | 2 | 2 | 7,8
    71 | ADC | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3,4
    72 | ADC | 直接页面间接寻址 | 2 | 5 | 1,2,4
    73 | ADC | 堆栈相对间接，变址Y寻址 | 2 | 7 | 1,4
    74 | STZ | 直接页面变址X寻址 | 2 | 4 | 1,2
    75 | ADC | 直接页面变址X寻址 | 2 | 4 | 1,2,4
    76 | ROR | 直接页面变址X寻址 | 2 | 6 | 2,5
    77 | ADC | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2,4
    78 | SEI | 隐式寻址 | 1 | 2
    79 | ADC | 绝对变址Y寻址 | 3 | 4 | 1,3,4
    7A | PLY | 堆栈寻址 (压栈) | 1 | 4 | 10
    7B | TDC | 隐式寻址 | 1 | 2
    7C | JMP | 绝对变址间接寻址 | 3 | 6
    7D | ADC | 绝对变址X寻址 | 3 | 4 | 1,3,4
    7E | ROR | 绝对变址X寻址 | 3 | 7 | 5,6
    7F | ADC | 绝对变址X长程寻址 | 4 | 5 | 1,4
    80 | BRA | 程序指针相对寻址 | 2 | 3 | 8
    81 | STA | 直接页面变址X间接寻址 | 2 | 6 | 1,2
    82 | BRL | 程序指针相对长程寻址 | 3 | 4
    83 | STA | 堆栈相对寻址 | 2 | 4 | 1
    84 | STY | 直接页面寻址 | 2 | 3 | 2,10
    85 | STA | 直接页面寻址 | 2 | 3 | 1,2
    86 | STX | 直接页面寻址 | 2 | 3 | 2,10
    87 | STA | 直接页面间接长程寻址 | 2 | 6 | 1,2
    88 | DEY | 隐式寻址 | 1 | 2
    89 | BIT | 直接寻址 | 2* | 2 | 1
    8A | TXA | 隐式寻址 | 1 | 2
    8B | PHB | 堆栈 (压栈) | 1 | 3
    8C | STY | 绝对寻址 | 3 | 4 | 10
    8D | STA | 绝对寻址 | 3 | 4 | 1
    8E | STX | 绝对寻址 | 3 | 4 | 10
    8F | STA | 绝对长程寻址 | 4 | 5 | 1
    90 | BCC | 程序指针相对寻址 | 2 | 2 | 7,8
    91 | STA | 直接页面间接变址Y寻址 | 2 | 6 | 1,2
    92 | STA | 直接页面间接寻址 | 2 | 5 | 1,2
    93 | STA | 堆栈相对间接，Y变址寻址 | 2 | 7 | 1
    94 | STY | 直接页面变址X寻址 | 2 | 4 | 2,10
    95 | STA | 直接页面变址X寻址 | 2 | 4 | 1,2
    96 STX
    直接页面变址Y寻址     		2 4 2,10
    97 | STA | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2
    98 | TYA | 隐式寻址 | 1 | 2
    99 | STA | 绝对变址Y寻址 | 3 | 5 | 1
    9A | TXS | 隐式寻址 | 1 | 2
    9B | TXY | 隐式寻址 | 1 | 2
    9C | STZ | 绝对寻址 | 3 | 4 | 1
    9D | STA | 绝对变址X寻址 | 3 | 5 | 1
    9E | STZ | 绝对变址X寻址 | 3 | 5 | 1
    9F | STA | 绝对变址X长程寻址 | 4 | 5 | 1
    A0 | LDY | 直接寻址 | 2+ | 2 | 10
    A1 | LDA | 直接页面变址X间接寻址 | 2 | 6 | 1,2
    A2 | LDX | 直接寻址 | 2+ | 2 | 10
    A3 | LDA | 堆栈相对寻址 | 2 | 4 | 1
    A4 | LDY | 直接页面寻址 | 2 | 3 | 2,10
    A5 | LDA | 直接页面寻址 | 2 | 3 | 1,2
    A6 | LDX | 直接页面寻址 | 2 | 3 | 2,10
    A7 | LDA | 直接页面间接长程寻址 | 2 | 6 | 1,2
    A8 | TAY | 隐式寻址 | 1 | 2
    A9 | LDA | 直接寻址 | 2* | 2 | 1
    AA | TAX | 隐式寻址 | 1 | 2
    AB | PLB | 堆栈寻址 (压栈) | 1 | 4
    AC | LDY | 绝对寻址 | 3 | 4 | 10
    AD | LDA | 绝对寻址 | 3 | 4 | 1
    AE | LDX | 绝对寻址 | 3 | 4 | 10
    AF | LDA | 绝对长程寻址 | 4 | 5 | 1
    B0 | BCS | 程序指针相对寻址 | 2 | 2 | 7,8
    B1 | LDA | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3
    B2 | LDA | 直接页面间接寻址 | 2 | 5 | 1,2
    B3 | LDA | 堆栈相对间接，Y变址寻址 | 2 | 7 | 1
    B4 | LDY | 直接页面变址X寻址 | 2 | 4 | 2,10
    B5 | LDA | 直接页面变址X寻址 | 2 | 4 | 1,2
    B6 | LDX | 直接页面变址Y寻址 | 2 | 4 | 2,10
    B7 | LDA | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2
    B8 | CLV | 隐式寻址 | 1 | 2
    B9 | LDA | 绝对变址Y寻址 | 3 | 4 | 1,3
    BA | TSX | 隐式寻址 | 1 | 2
    BB | TYX | 隐式寻址 | 1 | 2
    BC | LDY | 绝对变址X寻址 | 3 | 4 | 3,10
    BD | LDA | 绝对变址X寻址 | 3 | 4 | 1,3
    BE | LDX | 绝对变址Y寻址 | 3 | 4 | 3,10
    BF | LDA | 绝对变址X长程寻址 | 4 | 5 | 1
    C0 | CPY | 直接寻址 | 2+ | 2 | 10
    C1 | CMP | 直接页面变址X间接寻址 | 2 | 6 | 1,2
    C2 | REP | 直接寻址 | 2 | 3
    C3 | CMP | 堆栈相对寻址 | 2 | 4 | 1
    C4 | CPY | 直接页面寻址 | 2 | 3 | 2,10
    C5 | CMP | 直接页面寻址 | 2 | 3 | 1,2
    C6 | DEC | 直接页面寻址 | 2 | 5 | 2,5
    C7 | CMP | 直接页面间接长程寻址 | 2 | 6 | 1,2
    C8 | INY | 隐式寻址 | 1 | 2
    C9 | CMP | 直接寻址 | 2* | 2 | 1
    CA | DEX | 隐式寻址 | 1 | 2
    CB | WAI | 隐式寻址 | 1 | 3 | 15
    CC | CPY | 绝对寻址 | 3 | 4 | 10
    CD | CMP | 绝对寻址 | 3 | 4 | 1
    CE | DEC | 绝对寻址 | 3 | 6 | 5
    CF | CMP | 绝对长程寻址 | 4 | 5 | 1
    D0 | BNE | 程序指针相对寻址 | 2 | 2 | 7,8
    D1 | CMP | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3
    D2 | CMP | 直接页面间接寻址 | 2 | 5 | 1,2
    D3 | CMP | 堆栈相对间接，Y变址寻址 | 2 | 7 | 1
    D4 | PEI | 堆栈寻址 (直接页面间接寻址) | 2 | 6 | 2
    D5 | CMP | 直接页面变址X寻址 | 2 | 4 | 1,2
    D6 | DEC | 直接页面变址X寻址 | 2 | 6 | 2,5
    D7 | CMP | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2
    D8 | CLD | 隐式寻址 | 1 | 2
    D9 | CMP | 绝对变址Y寻址 | 3 | 4 | 1,3
    DA | PHX | 堆栈寻址(压栈) | 1 | 3 | 10
    DB | STP | 隐式寻址 | 1 | 3 | 14
    DC | JMP | 绝对间接长程寻址 | 3 | 6
    DD | CMP | 绝对变址X寻址 | 3 | 4 | 1,3
    DE | DEC | 绝对变址X寻址 | 3 | 7 | 5,6
    DF | CMP | 绝对变址X长程寻址 | 4 | 5 | 1
    E0 | CPX | 直接寻址 | 2+ | 2 | 10
    E1 | SBC | 直接页面变址X间接寻址 | 2 | 6 | 1,2,4
    E2 | SEP | 直接寻址 | 2 | 3
    E3 | SBC | 堆栈相对寻址 | 2 | 4 | 1,4
    E4 | CPX | 直接页面寻址 | 2 | 3 | 2,10
    E5 | SBC | 直接页面寻址 | 2 | 3 | 1,2,4
    E6 | INC | 直接页面寻址 | 2 | 5
    E7 | SBC | 直接页面间接长程寻址 | 2 | 6 | 1,2,4
    E8 | INX | 隐式寻址 | 1 | 2
    E9 | SBC | 直接寻址 | 2* | 2
    EA | NOP | 隐式寻址 | 1 | 2
    EB | XBA | 隐式寻址 | 1 | 3
    EC | CPX | 绝对寻址 | 3 | 4 | 10
    ED | SBC | 绝对寻址 | 3 | 4 | 1,4
    EE | INC | 绝对寻址 | 3 | 6 | 5
    EF | SBC | 绝对长程寻址 | 4 | 5 | 1,4
    F0 | BEQ | 程序指针相对寻址 | 2 | 2 | 7,8
    F1 | SBC | 直接页面间接变址Y寻址 | 2 | 5 | 1,2,3,4
    F2 | SBC | 直接页面间接寻址 | 2 | 5 | 1,2,4
    F3 | SBC | 堆栈相对间接，Y变址寻址 | 2 | 7 | 1,4
    F4 | PEA | 堆栈寻址 (绝对寻址) | 3 | 5
    F5 | SBC | 直接页面变址X寻址 | 2 | 4 | 1,2,4
    F6 | INC | 直接页面变址Y寻址 | 2 | 6 | 2,5
    F7 | SBC | 直接页面间接长程，变址Y寻址 | 2 | 6 | 1,2,4
    F8 | SED | 隐式寻址 | 1 | 2
    F9 | SBC | 绝对变址Y寻址 | 3 | 4 | 1,3,4
    FA | PLX | 堆栈寻址 (压栈) | 1 | 4 | 10
    FB | XCE | 隐式寻址 | 1 | 2
    FC | JSR | 绝对变址间接寻址 | 3 | 8
    FD | SBC | 绝对变址X寻址 | 3 | 4 | 1,3,4
    FE | INC | 绝对变址X寻址 | 3 | 7 | 5,6
    FF | SBC | 绝对变址X长程寻址 | 4 | 5 | 1,4
    * 1 如果m=0(16位内存/累加器)则增加1
    ** 操作码是1个字节，但是压入堆栈的程序指针的值是2个字节，因为有可选的签名字节
    + 如果x=0(16位变址寄存器)则增加1个字节
    1 | 如果m=0(16位内存/累加器)则增加的1个周期
    2 | 如果直接页面(零页面)寄存器的地位不是0(DL<>0)则增加一个周期
    3 | 如果增加变址越过页面边界则增加 1个周期
    4 | 如果处理器是65C02并且d=1(十进位模式。只有65C02下可用)则增加一个周期
    5 | 如果m=0(16位内存/累加器)则增加2个周期
    6 | 如果处理器65C02并且没有越过页面边界则减少一个周期
    7 | 如果转移被捕获则增加1个周期
    8 | 如果转移在6502，65C02，或65816/65082的模拟模式(e=1)下穿过页面边界则增加一个以上的周期
    9 | 在65802/65816的原本模式(e=1)下增加1个周期
    10 | 如果x=0(16位变址寄存器)则增加1个周期
    11 | 如果处理器65C02则增加一个周期
    12 | 在6502下：如果操作数的低端字节是$FF(比如操作数是$xxFF)会产生不正确的结果
    13 | 每移动一个字节要7个周期
    14 | 用3个周期来关闭处理器，需要增加周期来复位以重启处理器。
    15 | 用3个周期来关闭处理器，需要增加周期来调用中断以重启处理器
    16 | 字节和周期数受将来的处理器影响，因为WDM指令的操作码部分的长度可能会被扩展成2个字节。

    回到顶部

    附录 C: 集成电路引脚

    W65C816
    VP
            1 40 RES
    RDY
            2 39 VDA
    ABORT
            3 38 M/X
    IRQ
            4 37 o2 (IN)
    ML
            5 36 BE
    NMI
            6 35 E
    VPA
            7 34 R/W
    Vdd
            8 33 D0/BA0
    A0
            9 32 D1/BA1
    A1
            10 31 D2/BA2
    A2
            11 30 D3/BA3
    A3
            12 29 D4/BA4
    A4
            13 28 D5/BA5
    A5
            14 27 D6/BA6
    A6
            15 26 D7/BA7
    A7
            16 25 A15
    A8
            17 24 A14
    A9
            18 23 A13
    A10
            19 22 A12
    A11
            20 21 Vss
    6502
    Vss
            1 40 RES
    RDY
            2 39 o2 (OUT)
    o1 (OUT)
            3 38 SO
    IRQ
            4 37 o2 (IN)
    NC
            5 36 NC
    NMI
            6 35 NC
    SYNC
            7 34 R/W
    Vdd
            8 33 D0
    A0
            9 32 D1
    A1
            10 31 D2
    A2
            11 30 D3
    A3
            12 29 D4
    A4
            13 28 D5
    A5
            14 27 D6
    A6
            15 26 D7
    A7
            16 25 A15
    A8
            17 24 A14
    A9
            18 23 A13
    A10
            19 22 A12
    A11
            20 21 VSS

    注意:
    ML: 内存锁(引脚5)，在读写和修改指令（asl，dec，inc，lsr，rol，ror，trb和tsb）执行期间必须被置为低电位，以通知其他集成电路总线繁忙。
    VP: 硬件中断（IRQ）时，该引脚电压被拉低，中断向量指向的函数将被调用。
    Abort: 这是一个输入引脚。触发它会使当前执行的指令退出。
    VPA/VDA: 有效程序地址和有效数据地址。这两个信号是在6502的SYNC线扩展的－－为了更好的处理直接内存存取计划。
    VPA　VDA
    0　　0　－内在的操作
    0　　1　－有效程序地址
    1　　0　－有效数据地址
    1　　1　－取得操作码
    M/X: 内存和索引线。这些信号在引脚38上被多次使用。M在零状态下可用，X在一状态下可用。这两种信号反映状态寄存器的内容：M标志和X标志，允许其他装置获得操作码并解码。
    E: 模拟引脚。信号反映处理器模拟位(E)的状态。

    下面是44针封装形式的W65C816S的引脚排列：

    M
    L
    B
    I
    R
    Q
    A
    B
    O
    R
    T
    B
    R
    D
    Y
    V
    P
    B
    V
    S
    S
    R
    E
    S
    B
    V
    D
    A
    M
    /
    X
    O
    2
    I
    N
    B
    E
    6 | 5 | 4 | 3 | 2 | 1 | 44 | 43 | 42 | 41 | 40
    NMIB 7
    W65C816S
            39 E
    VPA | 8 | 38 | R/WB
    VDD | 9 | 37 | VDD
    A0 | 10 | 36 | D0/BA0
    A1 | 11 | 35 | D1/BA1
    VSS | 12 | 34 | D2/BA2
    A2 | 13 | 33 | D3/BA3
    A3 | 14 | 32 | D4/BA4
    A4 | 15 | 31 | D5/BA5
    A5 | 16 | 30 | D6/BA6
    A6 | 17 | 29 | D7/BA7
    18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28

    A
    7
    A
    8
    A
    9
    A
    1
    0
    A
    1
    1
    V
    S
    S
    V
    S
    S
    A
    1
    2
    A
    1
    3
    A
    1
    4
    A
    1
    5
    回到顶部


776菜单汉化经验篇

本文作者Alan Yang ，如欲转载请直接和他联系。欢迎访问狼的模拟器世界。

776菜单汉化基本结束了，这是我第一次进行asm hack,没有一点经验，也没有hack资料。所以花了不少时间，不过收获还是不小的，相信汉化下一个ROM时会省不少力气。我把我的思路写下来，有感兴趣的同行可以参考，应当可以少走一段弯路。

1. 菜单汉化的限制

菜单字库与对话字库不一样，它是完全被读入SFC的显示缓存里，这样不论你显示什么字，都不用再去ROM里找字摸了。而对话字模则是用到哪个就把哪个放进显存，上一句话显示完了，就把上一句用到的字模都清掉。所以，对话字库中的字模没有限制，用1000个或用10000个都行。而菜单字库就有限制了，SFC的显存才64K，除了放菜单字库，还要放所有的背景、图像、Sprite，这也是为什么大多数游戏的菜单，都是用日文假名来表示所有的item,magic,etc.

2. 776日文版是如何节省菜单字库的

776的菜单字库，除了假名以外，还有250个左右的汉字，为了在有限的空间里尽量多塞进一些汉字，它采用了以下几种办法：

A. 半个汉字：菜单里的每个字（包括假名）都是8X16的字体，也就是用两个8X8的tile拼起来的。那么，对某些字，如“か”与“が”，因为他们的下半部分是一样的，就可以共用一个tile, 这样两个字一共用去3个tile，省出一个tile。

B. 动态更新字库：有些字只出现在特定的菜单下。例如：山，湖，仓库等只在大地图下显示地形时才用到；而“剑枪斧弓杖”只在部队菜单才显示。那么，在进入大地图时，把“山湖仓库”的字模读入显存；而在进入“部队”菜单时，就用“剑枪斧弓杖”的字模替换掉“山湖仓库”等字模。再次进入大地图时，再把“山湖仓库”的字模替换回来。
这样，假设你有10个字的空间，实际上就可以使用20个字。当然，这20个字中，只有10个字可以同时出现在屏幕上。

3. 汉化方法

776中原有的方法都可以充分利用。但是，汉化后的菜单部分共有450多字，所以原来的字库肯定放不下。我用到以下几种方法：

A. 半个汉字：这种方法完全可以沿用，我请Dark01在做字模时尽量考虑这一点，象“大夫天”这样的字，下半部分完全可以共用。利用这种方法，我们节省了40个tile, 也就是说，整整多出来20个汉字可以利用！而且，仍旧有汉字可以再合并的。

B. 动态更新字库：这个也可以利用。而且还要尽量使用。日版中只在“音乐欣赏”和大地图的地形，这两处用到了动态更新，我则在道具、状态、设定、存放所、等多处都用了这种方法，具体技术细节见后面。

C. 开发显存中未利用的空间：菜单字库放在显存的B800-DFFF处。我发现在“状态”页上，胜利条件是用大字库的汉字显示的。这几个大字库的字模放在显存的什么位置呢？跟踪后发现，是放在A800-AFFF。其中AC00-AFFF基本没用到，而且，在其他各页上，AC00-AFFF这段空间也基本没用，大概是这段空间实在太小了吧，连一个人物头像也放不下。但是这点空间却可以放下32个汉字！所以我更改了显示子程序，在显示物品说明，魔法说明时，就把它们用到的字模放在这里；显示下一个物品说明时，清空这里，再把下一个说明用到的字模放进去。明眼人可以看出，这是套用了对话文本（大字库）的处理机制。这样，这些说明文字也基本不受限制了，想用多少字就可以用多少字。

D. 必须永远在显存中的字模：物品、魔法、人名、武器因为在部队、道具页上，所有这些物品、魔法、人名、武器都可能会出现在同一屏幕中，这些字不能利用B/C的方法动态交换，它们必须常驻在显存中。
也就是说，这些字是有空间限制的，必须仔细地翻译，确保总字数最少。在这方面，Aike做了很多整理工作，保证了字数在可用空间之内。为此，必须做一些取舍，比如“尤里乌斯”，里字在别处都没用到，我们就把里字去掉，换成“利夫”的利字。“尤利乌斯”

4. 技术细节

每个字在显示时，都要调用一个显示子程序，把它的编码转换为tile的编号，并写入显示缓冲，这个字才会显示出来。例如：“利”字，编码为82A0,经过显示子程序处理，查出组成“利”字的两个tile，其编号分别是8021，9021，再把这两个编号写入显存，这个字才显示出来。

我在菜单汉字的编码中找了一些没被用到的编码(99xx)，做特殊用途。

9900-99EF: 物品说明、魔法说明的汉字编码
99F0-99FF: 不显示汉字，是做特殊操作：

99FF: 清空AC00-AFFF (C方法用到)
99FE: 把“姓名同行疲劳状态备值道具”的字模放入显存 (B方法，状态页)
.....(略)

例：99FF 9900 9920 993E
这上面的编码代表“技能值”。第一个99FF不代表汉字，只负责清空AC00-ACFF,
后三个分别代表三个汉字。

本文作者Alan Yang ，如欲转载请直接和他联系。欢迎访问狼的模拟器世界。


776的解压缩算法篇

本文作者Alan Yang ，如欲转载请直接和他联系。欢迎访问狼的模拟器世界。

解压缩子程序入口：$80B737
$05单元存放解压后的字节长度

以下列出的数值，均为第一字节。设其为n。

1) n = $00-$3F:
直接输出其后的n+1个字节。

例：原码为 02 0F 71 35
解压缩后为 0F 71 35

2) n = $40-$4F: (极其少见，反正我在776中没见它用过)
根据下一字节(n+1)的值不同而不同，长度为（n的低四位+2）

a.若(n+1)>=#$80,
if (n+1)的高位为C或E, 解压后高位为F
例：原码为 42 C3 F6 70 解压缩后为 F3 FF F6 F7
^ ^^ ^ ^ ^ ^ ^
else 解压后低位为0
例：原码为 41 83 9A 解压缩后为 30 90 A0
^ ^^ ^ ^ ^
b.若(n+1)<=#$80,
if (n+1)的高位为C或E, 解压后高位为F
例：原码为 42 C3 F6 70 解压缩后为 F3 FF F6 F7
^ ^^ ^ ^ ^ ^ ^
else 解压后低位为0
例：原码为 41 83 9A 解压缩后为 30 90 A0
^ ^^ ^ ^ ^
3) n = $50-$5F:
其后各字节重复两次，长度为n的低四位+1

例：原码为 52 7C 42 30
解压缩后为 7C 7C 42 42 30 30

4) n = $60-$7F:
见例子。长度为n的低四位+2

$60-$6F: 原码为 61 C5 01 02 03
解压缩后为 C5 01 C5 02 C5 03

$70-$7F: 原码为 71 C5 01 02 03
解压缩后为 01 C5 02 C5 03 C5

5) n = $80-$BF:
一个字节有8bit, 最高位是bit7, 最低位是bit0, 即：
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

Copy [当前地址-偏移量] To [当前地址]
长度为 (bit2- bit5) +2
偏移量 bit1 bit0 与(n+1)合成为偏移量

例：原码为 84 10
解压缩后为：从[当前位置-#$10]开始，拷贝3个字节

6) n = $C0-$DF:
类似5），只是长度、偏移量都更大。

Copy [当前地址-偏移量] To [当前地址]
长度为: (n的末5位及下一字节的bit7) +2
偏移量: n+1字节的末7位与(n+2)合成为偏移量

例：原码为 CD 80 23
解压缩后为：从[当前位置-#$0023]开始，拷贝$1C个字节

7) n = $E0-$EF:
将第n+2字节复制[长度]次
长度为: n的后4位与(n+1)合成 + 3

例：原码为 E0 09 6A
解压缩后为 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A (12个"6A")

8) n = $F0-$F7:
将第n+1字节复制[长度]次，类似7）。
长度为: n的后3位 + 3

例：原码为 F1 C8
解压缩后为 C8 C8 C8 C8

9) n = $F8-$FB:
对压缩码的再压缩。参见10), 码长3，允许更大范围。
长度：(n的末2位及n+1的高3位) +3
偏移：n+1的低5位 与 n+2 合成

10) n = $FC-$FD:
对压缩码的再压缩，码长2。
长度：(n的bit0及n+1的高2位) +3，放在$09, 记录从前面借几个字节
偏移：n+1的低6位 +2 ，放在$03

例：前面的压缩码中有80 0C 82 10.....
原码为 FC 10
解压缩后为 80 0C 82
(从前面借3个字节80 0C 82，与“FC 10”后面的字节(假设是07 1A)
拼成新字串80 0C 82 07 1A，然后再次对这个新字串解压缩。）
相当于两次解压，776中未使用。

11) n= $FE 或 $FF
解压结束。

本文作者Alan Yang ，如欲转载请直接和他联系。欢迎访问狼的模拟器世界。


DMA及HDMA指南

DMA 和 HDMA 是 SNES 上用得很多的功能之一，了解它对 hack 图象和字符显示子程序很有帮助，下面是我翻译的一篇有关文章^^

=============================================================================
SNES 上的 DMA 和 HDMA 指南（v 1.0）

原文标题：Grog's Guide to DMA and HDMA on the SNES (version 1.0)

翻译：xade（我只捡了主要部分，其它诸如版权声明的废话就 pass 了，毕竟老外对知识产权的问题很在意的^^）
=============================================================================
前言：
=============================================================================
    也许我的这篇文章里会有些错误，请不要盲目地全部相信，你最好能自己搞个 demo 来一一验证。
    （xade：我翻译时若发现错误会注明并尽量纠正，但也不保证绝对正确^^）

=============================================================================
（01）DMA 的简单介绍
=============================================================================
    大多数的 computer 系统都会有一个某种形式的 直接存储器访问（Direct Memory Access）控制器，简称 DMA。简单来说，DMA 就是一块可以不依靠 CPU 而在各种存储介质之间进行数据的拷贝和传送的硬件。任天堂在 SNES 中使用了一套设计古怪的 DMA 系统。它有 8 个 DMA 通道，每一个都可以实现从主内存到 PPU 寄存器或从 PPU 寄存器到主内存的数据传送。通过使用 WRAM（Work RAM）地址寄存器，甚至可以实现从主内存到主内存的快速数据传送。

    为什么要使用 DMA 呢？因为它快。通过使用 DMA 来进行数据传送，对每个字节只需要 8 个时钟周期（Master Clock cycles）就可以实现从主内存到 PPU 寄存器的传送，而使用一串诸如
    LDA $nnnn,X
    STA $21xx
    INX
等语句的组合来干同样事情的话，将会需要 80 个以上（具体是多少我没算，懒~~~）（xade：还好意思说^^b 另外，如果寻址方式不同，花费的时钟周期也会不同，所以实际计算出来没什么太大的意义，只要了解两种方式所花的时间相差几乎一个数量级就可以了^^）的时钟周期。即使是使用串操作指令 MVP/MVN 进行纯粹的从主内存到主内存的数据传送，每个字节也要 56 个时钟周期（xade：而且有些限制，总之没有 DMA 好用^^）。

    要使用 DMA 之前，必须事先设定好数据源地址、目标 PPU 寄存器、需要传送的字节数等等参数，才能进行数据传送。当你设定好这些参数后，只要在 $420B 对应于要使用的 DMA 通道的相应位写入“1”，就开始进行数据传送了。和 DMA 有关的寄存器的详细说明可以在文末找到。现在给出一个快捷的例子来告诉你如何快速地传送 tile 数据到 VRAM 由 PPU 寄存器 $2116（xade：关于这个寄存器，最多告诉你它是“Video port address”，要解释的话就得和其它 $21xx 寄存器一起解释，这可不是三言两语能说清的，得再来一篇文章了，所以请参考其它文档吧^^）指定的当前位置中：
    LDX #TILEOFFSET　；源地址的段内偏移（xade：就是 XX:XXXX 冒号后面的部分）装入 X
    STX $4302　　　　；将 X 写到 $4302 中，源地址的段内偏移就设置好了
    LDA #TILEBANK　　；源地址的段地址（xade：就是 XX:XXXX 冒号前面的部分）装入 A
    STA $4304　　　　；将 A 写到 $4304 中，源地址的段地址就设置好了
    LDX #TILESIZE　　；需要传送的字节数装入 X
    STX $4305　　　　；将 X 写到 $4305 中，需要传送的字节数就设置好了
    LDA #$18 　　　　；因为 $2118 是目标寄存器，所以将 $18 装入 A，
    STA $4301　　　　；　然后写到 $4301 中，目标 PPU 寄存器就设置好了
                     ；　（xade：在进行传送的时候，目标 PPU 寄存器的地址是由
                     ；　$4301 中的这个 8 位值加上 $2100 形成的，后面有说明）
    LDA #$01 　　　　；设置 DMA 传送模式，
    STA $4300　　　　；　设为 1 意味着一次写入一个字（Word）
    LDA #$01 　　　　；因为上面我们设置的是 DMA 通道的参数，
    STA $420B　　　　；　所以使用 DMA 通道 0 进行传送（置 $420B 最低有效位）
这个简明的例子说明了使用一个 DMA 传送的基本要点。需要注意的是目标 PPU 寄存器只需要设定低 8 位就可以了，因为目标 PPU 寄存器总是在 $2100-$21FF 范围之内的。在 LOADVRAM.ASM 的 demo 源代码中，我大量使用了这些代码来实现将 tile 数据快速地载入到 VRAM 中。

    下面是另一个例子，实现从 ROM 到 WRAM 的快速数据传送：
LoadRAM .macro (SOURCEPTR, DESTPTR, SIZE)（xade：对宏汇编不熟悉的可以不管这行）
    LDX #(SOURCEPTR AND $FFFF) 　　　；源地址的段内偏移装入 X
    STX $4302　　　　　　　　　　　　；将 X 写到 $4302 中，设置源地址的段内偏移
    LDA #((SOURCEPTR >> 16) AND $FF) ；源地址的段地址装入 A
    STA $4304　　　　　　　　　　　　；将 A 写到 $4304 中，设置源地址的段地址
    LDX #SIZE　　　　　　　　　　　　；需要传送的字节数装入 X
    STX $4305　　　　　　　　　　　　；将 X 写到 $4305 中，设置需要传送的字节数
    LDX #(DESTPTR AND $FFFF) 　　　　；目标地址的段内偏移装入 X
    STX $2181　　　　　　　　　　　　；将 X 写到 $2181 中，设置目标地址的段内偏移
    LDA #((DESTPTR >> 16) AND $FF) 　；目标地址的段地址装入 A
    STA $2183　　　　　　　　　　　　；将 A 写到 $2183 中，设置目标地址的段地址
    LDA #$80 　　　　　　　　　　　　；因为 $2180 是目标寄存器，所以将 $80 装入 A，
    STA $4301　　　　　　　　　　　　；　然后写到 $4301 中，设置目标寄存器
    LDA #$01 　　　　　　　　　　　　；设置 DMA 传送模式，
    STA $4300　　　　　　　　　　　　；　设为 1 意味着一次写入一个字（Word）
    STA $420B　　　　　　　　　　　　；使用 DMA 通道 0 进行传送
.endm（xade：对宏汇编不熟悉的可以不管这行，不影响我们要了解的内容，上面的也是^^）

    上面的两个例子都使用了源地址自动增长的写入模式 1。源地址也可以自动减少或保持不变。源地址保持不变的写入模式可以用来将 WRAM 或 VRAM 快速清零。DMA 传送也可以实现从 PPU 寄存器到主内存的数据传送，但是我实在想不出有任何理由需要这么做。但是一旦有需要的话，可以通过将 $43x0 的第 8 位置位以实现反方向的传送。

=============================================================================
（02）HDMA 模式
=============================================================================
    DMA 本身就已经非常有用了，但是任天堂硬是又想出了另一种使用 DMA 的方法，这使 SNES 成为了一个就像它得到的成就一样伟大的平台。他们发明了一种称之为“HMDA”的模式，在每个水平空白周期（Horizontal Blanking period，也略作 HBlank）里进行一次数据传送，而不是立刻将 CPU 挂起进行数据拷贝。这可以让你做出相当漂亮的冬冬来，比如每条扫描线改变一个颜色，就像在 Chrono Trigger 中菜单背景里用到的渐变效果一样。另一个比较常用的地方就是控制窗口遮罩寄存器来实现不规则形状的遮罩效果（比如 Mario World 中的圆形遮罩过渡效果）。你可以找到成吨的用来展示 HDMA 威力的 demo 程序。

    HDMA 使用和 DMA 一样的通道寄存器，但是有一个专门用于激活 HDMA 的寄存器（$420C）。HDMA 和 DMA 还有其它一些不同之处，特别是它的源数据是以存放在一个具有特殊格式的表格中的。当你设定 $420C 后，DMA 控制器（xade：你没看错，我也没打错，就是 DMA 控制器，因为 HDMA 是使用 DMA 的一种方法，而不是物理上存在的硬件，请注意区别^^）的指定通道将在下一个 HBlank 周期内取出 HDMA 表格中的下一条项目，然后把它写到目标地址中去。需要注意的是每个 HBlank 周期内只进行一次传送，可以设定一次是传送 1、2 还是 4 个字节。因此，HDMA 是不适合用来拷贝大量连续的数据到 VRAM 中去的场合的，但是对于每一条扫描线需要改变最多 8 个 PPU 寄存器（因为一共有 8 个通道）的特效是相当有用的。

    另一个有趣的冬冬是跟踪 HDMA 表格的寄存器的值可以在垂直中断（Vertical IRQ）中改变。于是我们可以通过一些小动作使 HDMA 的威力变得更加强大。

=============================================================================
（03）HDMA 表格的格式
=============================================================================
    一个 HDMA 表格可以有任意多项的“单元”。每一个单元都包含告诉 HDMA 要在什么时候把什么数据写入目标地址的信息。每个单元由一个计数字节 N 和紧跟其后的数据项组成。（xade：原文下面的解释不准确，我会将语言重新组织以解释清楚，而不采用直译的方式^^）N 可以看成是由 1 位的写入方式标志位加上一个 7 位的计数值（为后面描述方便，记作 n，通常 n 的范围是十进制的 1 到 127）两部分组成的一个 8 位的字节值。如果 N 的第 8 位被置位，那么后面的数据将在每个 HBlank 中写入一次，总共写入的次数由 n 决定；若第 8 位被清零，那么后面的数据将只写入一次，而后面的 n-1 个 HBlank 将跳过不做任何事情。当 N 的值是 $80 时是特殊情况，相当于上述前者的第 8 位被置位，n 等于 128 时的情况。当 N 的值是 0 时，代表 HDMA 表格结束。

    紧跟 N 后面的数据长度由寄存器 $43x0 的 HDMA 模式设置值决定，可以是 1、2 或 4 个字节。还有一种“间接”模式，这种模式不是直接将数据项写入到目标地址内，而是通过和 $43x4 中指定的段地址组合后形成有效地址，然后再将这个有效地址内的数据写入到目标地址内。这种间接模式也是由寄存器 $43x0 的 HDMA 模式设置值打开的（通常的模式叫做“绝对”模式）。下面是几个单元的例子：

；绝对模式 001（一次 1 个字节）
；将 $FF 写入目标地址，然后跳过 $2F 次
    .db $30（xade：“.db”是定义一个字节长度数据的操作，没学过汇编的
            可以不管，只要知道这就是前面说的 N 就可以了）
    .db $FF（xade：这就是跟在 N 后面的数据项）

；绝对模式 100（一次 4 个字节）
；将 $AABBCCDD 写入目标地址 $20 次
    .db $A0
    .db $AA,$BB,$CC,$DD

；间接模式 001（一次 2 个字节）
；将 ($43x4):(TBL) 处的数据写入目标地址，然后跳过 $10 次
    .db $11
    .dw #TBL（xade：“.dw”是定义一个字长度数据的操作）

；HDMA 表格结束
    .db $00

    注意绝对模式和间接模式是不能简单地混合起来使用的（我想这可以通过在垂直中断中动些手脚来实现）。同时还要注意除非收到一个 N 为 0 的表格结束记号，否则 HDMA 是不会结束的，所以你可以隔几个帧使用一次 HDMA，尽管你可能会想在非屏蔽中断（NMI，即 NonMaskable Interrupt）中手动设置一个新的 HDMA 传送或重置 HDMA 表格的地址。
（xade：上面这段对搞 ROM Hack 作用不大，看不懂也不要紧，除非你想自己用汇编写 SNES 的游戏来自虐，否则完全可以无视^^b）

=============================================================================
（04）DMA 和 HDMA 寄存器详细说明
=============================================================================
（xade：由于以下的说明很简单，而且还有很多其它解释寄存器的文档，有些说法不一样，所以我就不翻译出来了，我也很懒的~~~ ^^b）

$420B: DMA Enable Register
            Each bit enables one DMA channel; the moment you set a bit to
            one, the DMA transfer begins. Up to 8 transfers can be done
            at a time, but they do not actually occur concurrently. The
            first transfer is the lowest #ed enabled channel.
            Least significant bit==Channel 0, MSB==Channel 7

$420C: HDMA Enable Register
            Each bit enables one HDMA channel; the moment you set a bit to
            One, the HDMA transfer begins. During each HBlank, a single
            transfer takes place per enabled HDMA channel. The lowest #ed
            enabled channel goes first.
            Least significant bit==Channel 0, MSB==Channel 7

Note that the DMA and HDMA channels use the same configuration registers (as
outlined below), ie so if 4 HDMA are active then only 4 DMA can be done, etc.

There are 8 channels, so substitute the channel # (0-7) for the x in the
register address:

$43x0: AB0CDEEE DMA Setup Register [DMAPx]
        A -- Transfer Direction (0==CPU -> PPU, 1==PPU -> CPU)
        B -- HDMA Addressing Mode (0==Absolute, 1==Indirect)
        C -- CPU addr Auto inc/dec selection (0==Increment, 1==Decrement)
        D -- CPU addr Auto inc/dec enable (0==Enable, 1==Disable (fixed))
        E -- DMA Transfer Word Select
            For DMA: (B0-B3 are the source data bytes, $21XX is PPU destination)
                000 == Write 1 byte, B0->$21xx
                001 == Write 2 bytes, B0->$21xx B1->$21XX+1
                010 == Identical to 000
                011 == Write 4 bytes, B0->$21XX B1->$21XX B2->$21XX+1 B3->$21XX+1
                100 == Write 4 bytes, B0->$21XX B1->$21XX+1 B2->$21XX+2 B3->$21XX+3

            For HDMA: (B0-B3 are the source data bytes, $21XX is PPU destination)
                000 == Write 1 byte, B0->$21xx
                001 == Write 2 bytes, B0->$21XX, B1->$21XX+1
                010 == Write 2 bytes, B0->$21XX, B1->$21XX
                011 == Write 4 bytes, B0->$21XX, B1->$21XX B2->$21XX+1 B3->$21XX+1
                100 == Write 4 bytes, B0->$21XX, B1->$21XX+1, B2->$21XX+2, B3->$21XX+3

$43x1: nnnnnnnn PPU register selection [BBADx]
            The byte written here is ORed with $2100 to form the source or
            destination (depending on bit 7 of $43x0) address for the DMA
            transfer. This is the "$21XX" in the description of the DMA
            Transfer Word Select for $43x0.

$43x2, $43x3, $43x4: CPU memory area address
            For DMA:
                The 24-bit address points to the source or destination
                CPU memory address for the DMA transfer. Whether this is
                the source or destination depends on bit 7 of $43x0.
            For HDMA:
                The 24-bit address points to the HDMA table in the CPU's
                memory space. The table format is outlined elsewhere in this
                document.

$43x5, $43x6, $43x7: Transfer Size or Indirect Address
            For DMA:
                16-bit value tells how many bytes to transfer; if zero,
                wraps around to 65536 bytes. Take care that this value
                is evenly divisible by the number of bytes in a single
                transfer; ie 13 bytes in DMA mode 4 is a BAD idea.
            For HDMA:
                Not usually read or written to directly. Holds the
                final address of the HDMA transfer on completion.

$43x8, $43x9: Table Address
                At the beginning of a transfer, this is automatically set to
                the value of $43x2. It is used as the "active" address during
                the transfer. Rarely need to access this directly, but you
                can if you want to "reset" the transfer or something.

$43xA: Number of lines to transfer
            HDMA only:
                No need to set this directly; stores the # of lines during
                HDMA transfer, corresponding to the current entry in the
                HDMA table being processed.

=============================================================================


字典压缩编码
原作者：Neill Corlett 翻译：xade

从初学者到中等hacker的水平
这篇短短的文章将尝试解释一些关于“如何找出被简单地压缩了的文本的编码方式（即文本压缩）（xade：不是字库的压缩！）”的基本要领，并且让你能够向你的朋友证明：我也能成为一个优秀的 ROM hacker（汗~这个我想还是用英文的好点^^b）！

在ROM破解这个圈子里混的时间越长，你就能看到越多这样的消息出现在网站上：

> NEWS：5/21/99
> 因为遇到了压缩的文本，这个工程翘辫子了。残念…………

有八成的可能，这种声明是来自一个刚刚接触ROM破解不久的新手，他想当然地认为这个ROM的文本是被压缩了的，因为他不知道那堆乱七八糟的东西还能是别的什么冬冬。那么现在，这篇文章将向你介绍几种很简单的压缩编码方式，你，没错就是你（xade：我倒~~~这几个字竟然还用了加粗，再倒~~~），能够轻松加愉快地搞定它们，并且做出那个Neill Corlett引以为傲的破解。*（xade：这句话应该理解为“并且做出那个Neill Corlett自以为只有他才能做出来的破解”，实际上指的就是破解压缩文本这个工作。由于本文的作者就是Neill Corlett本人，所以不存在对他自己用语不敬，而是谦虚）

想当年的NES和SNES，它们的ROM是极其昂贵并且极难制作的。在今天，如果要把N64上的游戏仍然用老方法装在由芯片组成的卡带上的话，我们几乎是不可能以一个合理的价格买倒它们的。（xade：也不会有这么严重的D版了^^）所以游戏的制作者们必须想方设法地在那极小的可用空间中塞下尽可能多的东西。（xade：这就是为什么PS游戏里的资料比如字库几乎不会用到压缩的原因，光盘的容量可是NES和SNES的几百上千倍，这就叫牛~呵呵^^）

最常见的通过位压缩（比如RLE、Huffman（xade：就是大名鼎鼎的霍夫曼编码^^）、LZSS等等）来压缩一个脚本的方法就是使用字典编码技术（xade：Dictionary Encoding）。这种方法通过使用一个表（xade：Table，后面我将译作“字典表”），就有点像你用来看ROM里的文本的那个（xade：就是我们用来dump文本的那个对照表），来减少文本所占据的空间。如果用得好的话，这招是非常有效的。但是因为这种方式需要很大的空间（xade：我想应该是指解压缩时会用到的内存空间），所以你会发现这种方法在游戏里通常只用在大量文本的压缩上。（xade：实际上像gif等图像文件也使用了字典压缩，只不过在那里，“字典”被叫作“调色板（palette）”。不只图像和游戏的压缩，其它很多地方都用到了这种压缩编码的方式。）

看看下面的文本：

You've gotta find the Dragon King. He's hiding somewhere up in the Dragon Mountains. Only he possesses the Orb of Light, the item which will open the doorway to the Kingdom of the Undead. Beware however, for the Kingdom of the Undead does actually contain the Undead, as the name would indicate.

不，这不是什么游戏里的，只是我临时想出来的（xade：晕~~~我还正在努力回忆这是不是《圣剑3》英文版里的台词^^b顺便说说，《圣剑3》的英文版就是这个人做的，上面那段很像《圣剑3》英文版里有关龙皇岛的台词^^）。我们来看看，“Dragon”这个单词出现了两遍。现在假设游戏中有好几条不同的龙，比如还有四条其它的龙在游戏中是必须要找到的。想像一下当那些烦人的村民向你提到那几条不同的龙时一共会说多少次“Dragon”这个单词。“Dragon”是一个很长的单词，有6个字母（xade：好像说得很夸张，到后面你就知道这是不是在吹了）。如果“Dragon”这个单词会出现30次，那就意味着总共有180个字节将被同一个单词在不同的地方占据掉。现在，要是我们在要显示“Dragon”这个单词的位置上用1个字节来替换那6个字节，我们能省下多少空间？很多。

字典压缩编码就是像这样工作的。游戏制作者从全部的脚本中找出一大堆重复出现的单词（或整段的台词），把它们作成一个字典表，然后为每一项分配一到两个字节来表示它们。如果上面的那段文字真的是一个游戏的脚本而且这个游戏的制作者又决定要使用字典压缩的话，当你用十六进制编辑器来看的时候，它最终看起来可能会是这样：

You<01> gotta find <02><03> <04>. He<05> hid<06> somewhere up in <02><03> Mountains. Only he possesses the <07>, <02>item which will .....

然后在ROM的什么地方会有这么一个字典表：

've<FF>the <FF>Dragon<FF>King<FF>'s<FF>ing<FF>Orb of Light<FF>

（xade：实际上这只是某些英文游戏ROM的情况。而对我们来说，通常会在使用对照表所导出的文本中见到类似的冬冬，只不过除了尖括号里的冬冬其它的都是日文假名和繁体汉字或标点符号什么的，当然还包括控制符。）

当然，在很多情况下你会看到这里甚至只会留下更少的文字。而且如果脚本是日文的并且你甚至不知道对照表各项的值就在十六进制编辑器里找脚本的话，你可以看出来要想弄清楚这些冬冬是如何工作的有多么困难。如果他们（xade：指游戏制作者）只用了几个位来表示字典项，用到了大段的台词项，用到了元音对，你会更加感到迷惑，就像你使用相对搜索得不到任何有用的结果一样。（xade：实际上这也只是某些英文游戏ROM的情况，在日文游戏ROM中，你会更加地不知所措，因为日文假名的数量远比英文字母要多，再加上汉字，足以让你晕死掉^^）

现在的问题是，如何得到一个使用了字典压缩的游戏的对照表？嗯，开始并不是那么容易的，但是只要一旦上手了，后面的工作将会是非常轻松的事。首先，我们要做的是找到基本的字母（xade：日文是假名和用到的汉字）所对应的代码。这里我们将用一个英文游戏来作为例子以使情况相对简单一些。Final Fantasy Mystic Quest 将会很好地配合我们的工作。这个游戏的字典相当地难以应付。实际上它是我遇到过的让我工作得既愉快又不愉快的拥有最复杂的字典系统的一个游戏。

首先，我们要找到一些最不可能包含字典值的文本。菜单是个很不错的下手的地方。第一个菜单选项是“ITEM”，这是一个很不错的目标。在十六进制编辑器中打开游戏ROM，然后作一次“ITEM”的相对搜索（如果你的十六进制编辑器没有相对搜索的功能，你可以到我们的主页上下载一个相对搜索的工具或换一个支持相对搜索的十六进制编辑器，这会使一切变得简单一些）摆弄一阵之后，你会发现第二个（依赖于你的查找器而不同）找到的结果是我们要找的那个。这个文本在0x1AD0Ch的位置上。看来9Ah就是代表大写字母“A”的值了。这看起来好像是一个很大的十六进制值，但是另一方面，现在我们知道表示大写字母的值了。那么其它的字母的值呢？嗯，幸运的是有一个叫做SNEMUL的SNES模拟器，这真是神赐给hacker们的礼物啊！（xade：倒~~~~~我可是无神论者^^）它可以让你看到SNES的显存（xade：Video Memory，即VRAM）里都有些什么冬冬。这对观察游戏的字库可是非常有用的，很多时候即使游戏的字库是被压缩了的也不在话下。它还为那些懂65816汇编语言的高手们准备了一个很好用的调试器（xade：Debugger。zsnes和rzsnes也有的）。从SNEMUL里可以看到在游戏的VRAM中，字库从按0～9然后A～Z然后a～z的顺序排列。好，现在我们运用从小学里学到的那些可爱的数学技巧可以算出90h表示“0”，一直到CDh表示“z”。（xade：我倒~~~~~美国的小学竟然会教十六进制这种这么可爱的冬冬，我晕~~~~~）那么你来猜猜看90h以下的十六进制值都表示些什么？好，让我们来把它们找出来。现在我们已经知道了菜单文本“ITEM”的存放位置了，我们可以把表示“ITEM”的那四个字节的内容替换成我们想要测试的值，然后进入游戏看看效果。就这样一直下去，直到得到一个比较完整的对照表。这样你就可以逐渐看到游戏中的对话文本了。从这里，你就可以进一步完善你的对照表了。（xade：一些控制符在菜单文本中是无法测试的，所以必须在对话的文本中进行测试）

现在你可能会问：“有快一点的方法吗？”回答是肯定的。一旦你找出了一些字典值之后，你就能找出实际的字典表在ROM中的什么地方，然后简单地把它复制并粘贴到你的对照表中就行了。我们假设你在“ITEM”的位置上的其中一个字节输入了41h并且发现它是表示单词“the”的字典值，然后你又发现41h表示字母“t”后面加一个空格。好，现在我们就利用这两个字典值在ROM里来找找“the”这个单词。经过一番搜索后，你会在某个位置找到一个相隔一个字节的“the”和“t”后面加一个空格。看来这就是我们要找的字典表了。对于那些无法找到人，我告诉你它在0x1BCA0h。还有，这个小巧可爱的ROM中有不止一张的字典表。现在我已经把基本的要领写了出来，剩下的就交给你们中间的自虐狂来完成它了。（xade：…………。居然把这文翻译出来了，我还真自虐^^b）我敢向你们保证，大多数的游戏都没有这么恐怖。但是这是学习的一个好方法，对付如此困难的一个游戏将使你在作其它游戏时感到“简单一些”。

*Neill Corlett 能够引以为傲的东西可不是保证有效的。
----------------------------------------
终于不用在括号里说话了（汗~~~~~）^^
实际上这只是非常简单的一种压缩方式，在有些ROM里是没有攻击力的。其他的压缩方式如固定位长度编码、可变位长度编码（实际上就是霍夫曼编码）、行程长度编码、LZxx编码、LZW编码等等等等在各种各样的 ROM 里都有用到的，有的甚至几种都用，或压缩n多次，像《圣剑3》就是。据英文化《圣剑3》的 Neill Corlett 说，它的文本就被压缩了n多次，我晕~~~~~~~~~目前努力尝试 DASM 中…………

实际上我也是边学边做的^^

呵呵呵！^^


ASM Tutorial
Written by Jay

Table of contents
-------------------
1) Introduction
2) CPU Register/Flags
3) Addressing Modes
4) Opcode Reference
5) FAQ's

1) Introduction
-----------------

This is a tutorial on 65816 asm used in the snes, made easy for dumb people to understand (sorta). In case you are wondering, I don't program in this language, so it is possible that I will write something incorrectly in this tutorial. If so, you can e-mail me at tennj@yahoo.com, to complain about how I suck at tutorials. Learning asm language isn't easy. If you already know a high-level programming language, the process will be a lot more easier. If you already know a low-level programming language, then you may scrap my tutorial as you probably won't need it. (This tutorial is for dumb people remember?) The purpose of this tutorial to for you to learn the basics of asm.

2) CPU Registers/Flags
-------------------------

The 65816 CPU has a set of registers and flags. So what the hell is a register? You may think of a register as sorta a storage space. Like a variable. Each register has it's own purpose. The standard registers are A, X, Y, D, S, PBR, DBR, PC, and P. Now I will explain the usage of each register.

A - Accumulator:

The accumulator is a general purpose math register. In other words, we can store anything we feel like into it and perform math operations to it. For example, if you wanted to store $20 into the accumulator, then you can do this:
lda #$20 (lda = Load Accumulator with value)
and then if you want to add 5 to it (assuming carry clear {<-more on this later}):
adc #$05 (adc = Add to accumulator with carry)
and the accumulator will have the value 25 in it.
The accumulator can either be 8-bits and 16-bit. What I mean by this is when the accumulator is 8-bit, it can only hold values from (0 - 255), but when it's 16-bit it can hold values from (0 - 65535). There is absolutely no way of telling when the accumulator is 8 or 16 bits unless you check bit 5 of the P flag (again more on this later). If bit 5 is set, then accumulator is 8-bit, otherwise it's 16-bit.

X, Y - Index Registers:

The X, Y registers is much like the accumulator. You can store values into it and perform math operations. However, they serve one additional purpose. They are used to index memory locations. The X, and Y registers like the accumulator, can be 8 or 16 bits depending on bit 4 of the P flag.

D - Direct Page Register:

The direct page register is a pointer that points to a region within the first 64k of memory. This register is used to access memory in direct addressing modes. In direct addressing mode, a 8-bit value (0 - 255) is added to the direct page address, which will form an effective address. For example, if the direct page register was $5000, then:
sta $10 (sta = Store accumulator to address)
will store the accumulator to address $5000 + $10 = $5010.

S - Stack Register:

The stack register points to a region where the stack is stored. So what is a stack? Think of it this way. Suppose you have a table. When you push a book on the stack, you place the book on the table. Suppose you push another book on the table, so you have 2 books on the table. Now when you pop a book, you remove the top book off the table. So what the heck am I saying with all this push and pop crap? We can push values and pop values off and on the stack. Every time we push a value onto the stack, the value is stored at where the stack is pointing to and the stack will decrement. When we pop a value from the stack the value is stored to the destination and the stack increments. Eg.
Suppose our stack was at $1FFF, then
pea $1000 (pea = push effective address)
we push $1000 onto the stack, the stack pointer will be at $1FFD since $1000 takes up 2 bytes.
pla (pla = pop to accumulator <-assuming 16-bits)
will store the accumulator with $1000 and the stack register restored to $1FFF.

PBR - Program Bank Register:

The program banks register hold the current bank the code is running in. Usually, an absolute address passed with a JMP (Jump to location) or JSR (Jump to subroutine) uses the PBR register to form an effective address. eg.
PBR: $80 (The current value of PBR)

jmp $1C00
will jump to location $80:1C00.

DBR - Data Bank Register:

Like the PBR register, this register refers to data accesses.
DBR: $90

lda $8000 (Load accumulator from address)
loads a value from $90:8000.

PC - Program counter:

This register hold the address of the location of the current instruction. Along with PBR, PBR:PC forms the effective address to the current instruction.

P - Flag Register:

The flag is a 8 bit register that stores the state of the CPU. It can also tell you whether the accumulator and index registers is 8 or 16 bit. The layout of the flag is:

________________________________
| n | v | m | x | d | i | z | c |
---------------------------------------------

n: Negative
v: Overflow
m: Memory/Accumulator Select
x: Index Register select
d: Decimal
i: Interrupt
z: Zero
c: Carry

Each of the boxes above represent the bits in a byte. Therefore, you must convert the flag register from decimal to binary, then compare it to above to check which flags are set. The carry flag is usually set on error or and unsigned overflow. The adc (Add with carry) command performs addition to the accumulator and adds 1 if carry is set. So to perform a pure add, you must clear the carry flag first.
clc (Clear carry flag)
adc #$20
So if you were wondering what I was talking about earlier in the tutorial about carry, now you know. The m and x flag controls whether the accumulator and index register is 8 or 16 bits. When m is set, then accumulator is 16 bits. Most assemblers can not detect whether you are working with an accumulator of 8 or 16 bits so it is up to you to keep track of the m flag as you program. Failure to do so will result in a very hard to debug code. We can use the SEP (Set P flag) and REP (Reset P Flag) to set and clear flags. To make the accumulator 16 bits, we take the binary code 00010000 and convert it to hex, $20 or decimal #32, and do
rep #$20
which will make the accumulator 16-bits.

3) Addressing Modes
-----------------------

Addressing mode is how the processor interprets a command. In other words, we cannot say that:
lda #$20
means the same as
lda $20
The first mode loads $20 directly to the accumulator. The second loads a byte from address: direct page + $20. Therefore, a lesson on addressing modes must be taught. From the examples here, the accumulator and index registers are assumed to be 8-bit.

First let's consider a few things in the syntax I use below.
<exp> is an expression.
A <8-bit exp> is an 8-bit expression. An 8-bit expression is any number between $00 - $FF.
A <16-bit exp> ranges from $0000 - $FFFF.
A <24-bit exp> as you guessed it, from $00000 - $FFFFFF
A <dp exp> is a direct page expression. A dp expression is a <8-bit exp> but it refers the direct page, and is always written in hex and is 2 digits. The direct page is always calculated by the <dp exp> + D.
An <abs exp> is a <16-bit exp> but is always written in hex and has 4 digits.
A <long exp> is a <24-bit exp> but is 6 digits and also written in hex.

Immediate:

opcode #<8/16-bit exp>

Immediate address mode is specified with a value.
eg.
lda #$FF ; Loads accumulator with $FF.
sep #$30 ; Puts $30 into P

Direct:

opcode $<8-bit exp>

The destination is formed by adding the direct page register with the 8-bit address to form an effective address.
eg.
lda $20 ; Loads from $20 + D
lda $90 ; Loads from $90 + D
If D = $1000, then it will read a byte (if A is 8-bit) from address $1020.

Absolute:

opcode $<16-bit exp>

The effective address is formed by DBR:<16-bit exp>.
eg.
DBR: $88

lda $901C ; Loads a byte from address $88:901C

Absolute Long:

opcode $<24-bit exp>

The effective address is the expression.
eg.
lda $808000 ; Loads a byte from $80:8000
lda $FF9090 ; Loads from $FF:9090

Accumulator:

opcode

The destination is the accumulator.
eg.
inc ; Increments the accumulator

Implied:

opcode

The opcode has it's own special function.
eg.
clc ; Clears carry flag
inx ; Increments the X register

Direct Indirect Indexed:

opcode ($<dp exp>), y

This is where the index registers come into play. 2 bytes are loaded from the direct page address to form a base address that is combined with DBR. Finally y is added to the base address to form the absolute address.
eg.
DBR: $80, D: $0020, Y: 0001
Memory dump:
0030: 30 40 23 22 F4 22 23 1C
0038: 23 2D DD F4 FF FF FF FF

lda ($10), y
First we will calculate the DP address:
$10 + D = $0030
Then pull 2 bytes from $0030, 30 & 40 (and they are reversed for the LSB ordering) to get the address $4030. The address ($4030) is used with DBR to get the base address.
DBR:$4030 -> $80:4030
The base address is added with y to create the effective address.
base + y = $80:4030 + $0001 = $80:4031
Basically, the command loads a byte from $80:4031 to the accumulator. Usually, this mode is used in a loop where Y is incremented each time to pull a set of data from a memory location.

eg2.
DBR: $80, D: $0020, Y: 0001
Memory dump:
0030: 30 40 23 22 F4 22 23 1C
0038: 23 2D DD F4 FF FF FF FF

lda ($15), y

DP Address = $0020 + $15 = $ 0035
Base Address = DBR:<2 bytes from $0035>
= $80:2322
Effective Address = Base Address + Y = $80:2323

P.S. I may make this more complicated that it looks. Most of the time the D register is 0, and therefore takes a lot less time calculating all of this.

Direct Indirect Indexed Long:

opcode [$<dp exp>], y

This mode is like the previous addressing mode, but the difference is that rather than pulling 2 bytes from the DP address, it pulls 3 bytes to form the effective address.
eg.
(Same example as last time)
eg.
DBR: $80, D: $0020, Y: 0001
Memory dump:
0030: 30 40 23 22 F4 22 23 1C
0038: 23 2D DD F4 FF FF FF FF

lda [$10], y

DP Address = $0020 + $10 = $0030
Base Address is form by pulling 3 bytes from $0030, 30 40 23 which becomes the address $23:4030. Now we add Y to the base address to form the effective address $23:4031.

Direct Indexed Indirect:

opcode ($<dp exp>, x)

The direct page address is calculated and added with x. 2 bytes from the dp address combined with DBR will form the effective address.
eg.
DBR: $80 D: $0020 X: $0004
Memory dump:
0020: FF 00 FF 09 33 33 09 88
0028: 08 76 66 36 D7 23 99 00

lda ($02, x)
First DP address is calculated.
DP Address = $02 + D = $0022
Then added with X
dp address + x = $0026
2 bytes are pulled from $0026, (09 88) to become $8809, and combined with DBR:
DBR:$8809 = $80:8809
which will be the effective address of where the byte is loaded.

Direct Indexed by X:

opcode $<dp exp>, x

The DP address is added to X to form the effective address. The effective address is always in bank 0.
eg.
D: $0020 X: $0004

lda $30, x
DP Address = $30 + $0020 = $0050
Effective Address = DP Address + X = $00:0054

Direct Indexed by Y:

opcode $<dp exp>, y

Same as above except we add the Y register instead of X.

Absolute Indexed by X:

opcode $<abs exp>, x

The absolute expression is added with X and combined with DBR to form the effective address.
eg.
DBR: $80 X: $0001

lda $8000, x
Effective address = DBR:($8000 + x) = $80:8001
lda $6988, x
Effective address = DBR:($6988 + x) = $80:6989

Absolute Long Indexed by X:

opcode $<long exp>, x

The effective address is formed by adding the <long exp> with X.
eg.
X: $0001

lda $808000, x
loads a byte from $80:8001.
lda $589112, x
loads from $58:9113

Absolute Indexed by Y:

opcode $<abs exp>, y

Same as Absolute Indexed by X, except with Y.

Program Counter Relative:

opcode $<8-bit signed exp>

This addressing mode is only used in branch commands. The <8-bit signed exp> is added to PC (program counter) to form the new location of the jump. The <8-bit signed exp> can range from (-128 to 127). Most assemblers will allow you to enter an <abs exp> in which the +-128 is automatically calculated.
eg.
bra $8005 ; branch to location $8005 as long as it's within the
; (-128 to 127) range

Program Counter Relative Long:

opcode $<16-bit signed exp>

Like above, but the range is between (0 to 65535). Only the BRL and PER commands use this.

Absolute Indirect:

opcode $(<abs exp>)

2 bytes are pulled from the <abs exp> to form the effective address.
eg.
Memory dump:
0000: 90 77 78 00 43 00 00 00
0008: 33 32 12 33 11 11 FF FF

jmp ($0008)
will first grab 2 bytes from $0008 (33 32), then jump to the address of $3233.

Absolute Indexed Indirect:

opcode $(<abs exp>, x)

The <abs exp> is added with X, then 2 bytes are pulled from that address to form the new location.
eg.
X: 0001
Memory dump:
0000: 90 77 78 00 43 00 00 00
0008: 33 32 12 33 11 11 FF FF

jmp ($0008, x)
Abs Address = $0008 + X = $0009
2 bytes from $0009 = 32 12
$1232 is the new location.

Direct Indirect:

opcode ($<dp exp>)

2 bytes are pulled from the direct page address to form the 16-bit address. It is combined with DBR to form a 24-bit effective address.
eg.
D: 0000 DBR: $80

Memory Dump:
0040: 50 00 80 00 22 23 33 44
0050: 60 21 21 21 22 55 55 66

lda ($40)
DP Address = $40 + D = $40
16-bit address from $40 = $0050
Effective Address = DBR:$0050 = $80:0050

Direct Indirect Long:

opcode [$<dp exp>]

3 bytes are pulled from the direct page address to form an effective address.
eg.
D: 0000 DBR: $80

Memory Dump:
0040: 50 00 80 00 22 23 33 44
0050: 60 21 21 21 22 55 55 66

lda [$40]
DP Address = $40 + D = $40
Effective address = 24-bit address from $40 = $80:0050

Stack:

opcode

Like implied but affects the stack.
eg.
pha ; push Accumulator
pla ; pop accumulator

Stack Relative:

opcode <8-bit exp>, s

The stack register is added to the <8-bit exp> to form the effective address.
eg.
S: 1FF0

lda 1, s
loads a byte from 1 + S = $1FF1.

Stack Relative Indirect Indexed:

opcode (<8-bit exp>, s), y

The <8-bit exp> is added to S and combined with DBR to form the base address. Y is added to the base address to form the effective address.
eg.
S: $1FF0 Y: $0001 DBR: $80

lda (1, s), y
Base address = DBR:($1FF0 + 1) = DBR:$1FF1 = $80:1FF1
Effective Address = $80:1FF1 + Y = $80:1FF2

Block Move:

mvn $<8-bit exp>,$<8-bit exp>
mvp $<8-bit exp>,$<8-bit exp>

This is by far the weirdest instruction I've seen. It bascially moves chunks of blocks from one place to another. The first $<8-bit exp> is the bank of the destination. The second is the bank of the source. X is loaded with the 16-bit address of the source, and Y is loaded with the 16-bit address of the destination. A is loaded with how many bytes to transfer.
eg.

X: ???? Y: ???? A: ???? rep #$30 ; Make accumulator and index 16-bit
X: ???? Y: ???? A: ???? ldy #$8000 ; load X with $8000
X: ???? Y: 8000 A: ???? ldx #$9000 ; load X with $9000
X: 9000 Y: 8000 A: ???? lda #$0005 ; lead A with 5
X: 9000 Y: 8000 A: 0005 mvp $80, $A0; block move increment
will transfer 5 bytes from $80:8000 -> $A0:9000 ??

4) Opcode Reference
----------------------------

Here is a simple explanation of most of the commands in asm.

ADC - Add with carry

The operand is added to the accumulator. 1 is added in addition is carry flag is set.
eg.

A: 0010 adc #$50 ; adds $50 to accumulator or 51 if carry is set
A: 0060 ; result

AND - Perform AND to accumulator

The operand is "AND"ed to the accumulator.
eg.
A: 0010 and #$80 ; "AND"s $80 to accumulator
A: 0000

ASL - Left shifts Accumulator, Memory

Performs a shift left.
eg.
A: 0010 asl #$01 ; Left shifts accumulator with 1
A: 0020

BCC - Branch is carry clear (carry=0, jump)

Jump to a new location within the -128 to 128 range if the carry flag is clear. Useful for comparing 2 numbers, and branches if it is greater than.
eg.
P: --mxdi-- bcc next ; since the carry flag is clear, it'll jump to next
lda #$00 ; this will not be executed
next: lda #$40

BCS - Branch if carry set (carry=1, jump)

Like BCC, but only when carry is set. Good for branching "if less than" in comparison.

BEQ - Branch if equal (zero, jump)

Branches is zero flag is set. This is useful for comparing numbers.
eg.
cpx #$50 ; if X is = $50, the zero flag is set
beq next ; branches to next if X = $50
lda #$44
next: lda #$00

BIT - Bit Test

Performs AND except only the flags are modified.
eg.
A: 0010 bit #$80 ; "AND"s $80 to accumulator but result not stored
A: 0010

BMI - Branch if Minus (negative=1,jump)

Branches if negative flag is set.

BNE - Branch if not equal ( <>0, jump)

Branches if zero flag clear. Good when used with comparison. You can branch if the number it not equal to.

BPL - Branch if plus (negative=0,jump)

Branches if negative flag clear.

BRA - Branch always

Hmm…..

BRK - Break to instruction

Causes a software break. The PC is loaded from a vector table from somewhere around $FFE6.

BRL - Branch Always Long

Like BRA but with longer range (0 - 65535).

BVC - Branch if Overflow Clear =0

Branches if overflow flag is clear.

BVS - Branch if Overflow Set =1

Opposite of BVC.

CLC - Clear Carry Flag

Clears the carry flag.

CLD - Clear Decimal Flag

Clears the decimal flag.

CLI - Clear Interrupt Flag

Clears the interrupt Flag.

CLV - Clear Overflow Flag

Clears the Overflow flag.

CMP - Compare Accumulator with memory
CPX - Compare X with memory
CPY - Compare Y with memory
Compares accumulator, X, or Y with an operand. The n-----zc flags are affected by the comparison. If the result is negative, the n flag is set. If the result is zero (or equal), the z flag is set. Carry is set usually when borrow is required.
eg.
A: 0020 P: --mx-i-- cmp #$20 ; compare accumulator with $20
; if equal, z flag is set
A: 0020 P: --mx-iz- beq next ; branch if equal

COP - Coprocessor Empowerment

Causes a software interrupt using a vector.

DEC - Decrement Accumulator
DEX - Decrement X
DEY - Decrement Y

Subtracts 1 from A, X, or Y.
eg.
Y: 0001 dey
Y: 0000

EOR - Exclusive OR accumulator

Performs XOR (I think) to the accumulator.
eg.
A: FFFF eor #$DDDD XOR: 1 1 ->0, 0 1->1, 0 0->0
A: 2222

INC - Increment Accumulator
INX - Increment X
INY - Increment Y

Adds 1 to A, X, or Y.
eg.
A: 0000 inc
A: 0001

JMP - Jump to location
JML - Jump long

The JMP command will jump to a location within the bank. The JML will jump to places out of the current bank.
eg.
PBR: $80 jmp $5500 ; jumps to $80:5500
PBR: $80 jml $908000 ; jumps to $90:8000
PBR: $90 ; the PBR is updated in long jump

JSR - Jump Subroutine
JSL - Jump Subroutine Long

If you already know a programming language, this is basically calling a function. This performs the same as JMP except the address of the current program counter is saved. In subroutines, the RTS and RTL are used to return back to the saved address.

LDA - Load Accumulator with memory
LDX - Load X with memory
LDY - Load Y with memory

Loads the accumulator, X, Y with a value.

LSR - Shift Right Accumulator, Memory 右移

Performs right shift.
eg.
A: 0080 lsr #$01 ; shift right by 1
A: 0040

MVN - Block move negative
MVP - Block move positive

*See Block Move Addressing mode.
The difference between the positive and negative block move is that with negative block move, the source address is greater than the destination.

NOP - No operation

Does nothing but take up 2 cycles.

ORA - "OR" Accumulator with memory

Performs "OR" to accumulator.
eg.
A: 005F ora #$7F
A: 007F

PEA - Push Effective Address =PEA #…

Pushes a 16-bit operand onto the stack. The instruction is very misleading because you are really pushing an immediate onto the stack.
eg.
S: 1FFF pea $FFFF ; Pushes $FFFF onto the stack
S: 1FFD ; stack decrements by 2

PEI - Push Effective Indirect Address

Pushes a 16-bit value from the indirect address of the operand.
eg.
Memory Dump:
0000: 23 33 45 22 DD C7 FF 8D
0001: 99 99 90 88 DD FF CC 67

S: 1FFF pei ($01) ; push $4533 on the stack
S: 1FFD

PER - Push Program Counter Relative

Pushes a 16-bit from that address taken by the current PC added to the operand. The range must be within (0 - 65535).

PHA - Push Accumulator
PHD - Push Direct Page Register
PHK - Push PBR
PHP - Push Flags
PHX - Push X
PHY - Push Y

Pushes the operand onto the stack.

PLA - Pull Accumulator
PLD - Pull Direct Page Register
PLP - Pull Flags
PLX - Pull X
PLY - Pull Y

Pops a value off the stack and stores it in the operand.

REP - Reset Flag

Clears the bits specified in the operands of the flag.
eg.
rep #$30 ; Clears bit 4 & 5 to make A, X, Y 16-bits

ROL - Rotate Bit Left 循环左移

Much like shift left except any bits moved off the most significant bit are restored to the right.
eg.
A: 8000 rol #$0001 ;rotate 1 left
A: 0001

ROR - Rotate Bit Right

Much like shift right except any bits moved off the least significant but are restored to the left.
eg.
A: 0001 ror #$0001 ; rotate 1 right
A: 8000

RTI - Return from Interrupt

Used to return from a interrupt handler.

RTS - Return from Subroutine
RTL - Return from Subroutine Long

Return the PC to the saved address caused by the JSR and JSL command.
eg.
jsl sub ; jump to subroutine at label "sub"
.
.
.
sub:
lda #$0000 ; some code
rtl ; return

SBC - Subtract with Carry A-oprand+carry

Subtracts Accumulator with memory, and subtract and additional 1 If carry is set.

SEC - Set Carry Flag

Sets the carry flag.

SED - Set Decimal Flag

Sets the decimal flag.

SEI - Set Interrupt flag

Sets the interrupt flag.

SEP - Set Flag

Sets certain bits of the flag depending on the operand.
eg.
sep #$20 ; set bit 5 of P to make A 8-bits

STA - Store Accumulator to memory
STX - Store X to memory
STY - Store Y to memory

Stores the accumulator, X, or Y to a memory location.
eg.
A: 000F sep #$20 ; 8-bit A
A: 000F sta $2100 ; Stores $0F to $2100

STP - Stop the clock

Don't know how this works.

STZ - Store zero to memory

Stores zero to a memory location.
eg.
stz $2101 ; store 0 to $2101

TAX - Transfer Accumulator to X
TAY - Transfer Accumulator to Y
TCD - Transfer Accumulator to Direct Page
TCS - Transfer Accumulator to Stack
TDC - Transfer Direct Page to Accumulator
TSC - Transfer Stack to Accumulator
TSX - Transfer stack to X
TXA - Transfer X to Accumulator
TXS - Transfer X to Stack
TXY - Transfer X to Y
TYA - Transfer Y to Accumulator
TYX - Transfer Y to X

Copies the content of one register to another.

eg.
A: 0099 X: 1FFF Y:5656 D:0020 S: FFFF rep #$30
A: 0099 X: 1FFF Y:5656 D:0020 S: FFFF lda #$0000 ; load A with 0
A: 0000 X: 1FFF Y:5656 D:0020 S: FFFF tcd ; transfer A -> D
A: 0000 X: 1FFF Y:5656 D:0000 S: FFFF txa ; X -> A
A: 1FFF X: 1FFF Y:5656 D:0000 S: FFFF tcs ; A -> S
A: 1FFF X: 1FFF Y:5656 D:0000 S: 1FFF

You get the idea.

TRB - Test and Reset Bit

Tests the bit similarly to "AND", and clears it, while affecting the flags.

TSB - Test and Set Bit

Tests the bit similarly to "AND", and sets it, while affecting the flags.

WAI - Wait for Interrupt

Waits until a hardware interrupt is triggered.

XBA - Exchanges B with A

Due to the fact that the accumulator was either 8 or 16 bit, the low and high ends of the accumulator was given a name A and B (and C is the whole 16-bit Accumulator). This command swaps the low and high of the accumulator.
eg.
A: 5690 xba
A: 9056

XCE - Exchange Carry with Emulation

Well, the 65816 has actually 2 modes. Native and (6502) emulation mode. This tutorial deals with native only. The emulation bit shares the same bit as the carry flag so to put ourselves in native mode, we can only set the emulation flag via the carry and exchanging it. For native mode, the emulation flag must be off. To switch to native mode, we must clear the carry and exchange it.
eg.
clc
xce

5) FAQ's
--------------

Q: Since the accumulator is 8/16 bits, how will disassemblers know when the accumulator is 8 or 16 bits?

A: It doesn't. Tracer has an option on there that will attempt to detect the Accumulator and index size. Use the -f switch.

Q: Sometimes the assembler compiles my JMP to a JML instruction. What should I do?

A: Try jmp.w

Q: OK. I got down all the basics. Does this mean I'll be able to hack snes roms like all the other groups out there with my newly gotten asm skills?

A: No. You must learn about the hardware. I don't cover this.

Q: Are you the coolest?

A: Yes.

Q: What about Tiger Claw? Ain't he cool too?

A: Of course.