Pokemon Center | 口袋中心 以口袋妖怪为主题并带有其他动漫游戏的讨论

 找回密码
 加入口袋中心
搜索
查看: 2775|回复: 5

nds gameshark金手指简易制作教程

[复制链接]

6

回帖

276

现金

8

勋章券

离开家乡的彷徨

Rank: 4Rank: 4

积分
141
发表于 2023-9-20 22:43:17 | 显示全部楼层 |阅读模式
本帖最后由 qdrz 于 2024-2-14 14:25 编辑

这个教程是想简单地说一下nds的gameshark金手指是怎么做的。
金手指的作用主要就是对nds的内存进行操纵,因为游戏运行时的各种数据其实就是内存当中的值。
例如,你去商店买东西,那么光标指向第几个物品,计算机要想知道它并在屏幕上显示出来,显然方法就是在内存的某个地方保存了这个数值。类似的,pm的hp量,当前的等级,路上的某个npc走到了哪一格,主角当前的坐标,等等,都可以成为金手指。事实上,从你可以对ds游戏进行即时存档就可以看出,理论上你只要写够多的金手指,是(几乎)可以完全确定游戏的运行状态的。之所以这么说,是因为即时存档只不过是将某一时刻ds运行时内存和cpu中所有寄存器的值完全保存了下来,而金手指就可以复现所有的内存状况(不过cpu的寄存器确实复现不了。。)
因此,首先我们需要知道ds的虚拟内存排布。不过在此之前,我想先对ds的一个硬件特征作些简单的介绍(楼主对ds其实也不怎么了解,如果有哪里说错了希望大佬们可以不吝斧正,谢谢)
ds有两个cpu,一个运行arm9指令集,一个运行arm7指令集。其中,arm9这个带了一个叫做cp15的协处理器,用来帮助进行内存管理。这两个cpu的某些内存是公用的,某些是独有的。正是这个原因,这两个cpu各自拥有自己的虚拟内存映射。
另外,本文使用h表示16进制后缀。也就是说,某个数以h结尾则表明它是16进制数。
那么下面正式开始介绍:

arm9的虚拟内存映射:

00000000h 到 00007fffh 32KB ITCM 这一块内存是arm9独有的一块内存,比主内存快。不过,由于ds的固件在ds启动时给itcm分配的虚拟空间有32MB,因此这32KB会以重复的方式遍布00000000h 到 01ffffffh之间的所有内存区域。此时,如果游戏在cp15协处理器的内存保护单元(这个单元中的东西都是cpu的寄存器,没有映射到虚拟内存里)中对某些部分设置内存保护,那么实际的效果就是不同的游戏,itcm的位置可能完全不同,并且可能位于00000000h到01ffffff的任何位置。不过,对于制作金手指而言,大多数情况下碰到的都是01xxxxxx的地址。

0xxxx000h 到 0(xxxx+3)fff 16KB DTCM  这一块内存也是arm9独有的,比主内存快。xxxx的具体数值可以在ds的cp15协处理器的某些寄存器中设置,因此这块内存的地址是不定的。不过默认情况下,它的起始地址在027C0000h处。

02000000h 到 023fffffh 4MB Main RAM 这一块内存是arm9与arm7共有的内存,即ds的主内存,两块cpu都可以对此区域进行读写。有了解过计算机内存访问机理的朋友要问,那如果两个cpu同时读写一个区域,地址总线不会短路导致cpu被烧了吗?其实的确会。所以为了解决这个问题,nds提供了一个io接口,它位于04000204h(插播一句,ds采用的是所谓哈弗架构。也就是说,和x86的cpu的io接口独立编址,并用专门的in/out指令访问不同,ds的io是统一编址,并用通常的内存访问指令进行访问的,访问io接口和访问普通内存在cpu层面没有区别)‬,这个io接口有16个bit,其中低7个bit是arm9和7公用的,而高9个bit是arm9专用的,其中第15个bit就表明当前的主内存可以优先由哪块cpu读写,0表示arm9,1表示arm7)

03000000h 到 037fffff 8MB shared Wram image (wram即work ram,工作内存,一般用于存放各种变量之类的) 这一块地址空间是arm9独有的。虽然看上去很大,可是实际上可以用的内存只有最多32KB。它的原理是这样的:nds在物理意义上只有32KB的工作内存(wram),这一块内存要么给arm9用,要么给arm7用,要么分成两半,一半给arm9用,另一半给arm7用。决定到底怎么用的也是一个io接口,位于04000247h,同样只有arm9才可以进行设置。在通过设置io给arm9分配物理wram后,这些分配到的物理wram的内容将会以重复的方式充满整个arm9的这8MB虚拟地址空间(例如地址03008000h的内容会复制03000000h的内容,以此类推),这种机制其实类似于上面的ITCM。这也就是尽管这块虚拟内存空间是arm9独有的,可它却被叫做共享工作内存的原因(共享指的是物理wram)。不过不知道为什么,一般来说厂商都会使用这段地址空间最后的wram映像,即037f8000h到037fffffh来操纵ds 的wram。那么这里可能会有朋友问,不是已经有main ram了吗?反正main ram也是可读可写的,为什么还要再搞一块wram出来?其实这个我也不知道,我猜测可能是main ram没有wram快,毕竟gba上就是这样,03打头的内存,速度比02快(*^_^*)。

03800000h 到 03ffffffh 8MB Wram image 这一块虚拟地址空间也是重复上面分配的物理wram。之所以要分开说,是因为arm7与arm9在上面 shared的部分虽然可能分配的物理wram不同,但是其对应的地址空间的行为是相同的。也就是说,arm7的03000000到037fffff也是重复物理wram。但是03800000这里开始,arm7是64KB独有的wram,并且固定分配在这个虚拟地址上,不会变动,这就和arm9不同了。

04000000h 到 04ffffffh IO接口 众所周知,任何一个可以跑自制程序的计算机不外乎完成4项工作:输入,输出,存储与计算(其实就是通用图灵机T.T)。对于ds而言,它如果想要与外界通信,同样需要进行输入输出(输入比如触摸板,按键,输出比如显示屏,音乐)。IO接口就是cpu识别并控制输入输出的地方。这些接口有的是arm9独有的,有些是arm9与arm7都可以访问的(比如上面04000204就是IO接口的例子)。具体哪些是哪些可以参考与ds相关的手册。对于制造金手指而言,通常只需要关注与按键有关的io接口(为了制造按键触发型金手指),我将在下文指出按键io在哪个地址

05,06,07都是与2D显示有关的内存空间,这里就不详细介绍了。不过有一点对于做改版或许有点帮助,那就是ds的显存(06开头的)实际上也是采用了类似wram的分配机制,物理显存切成bank,并通过04000240处的几个显存控制io,决定将哪个bank连接到ds的2D渲染引擎(上屏叫引擎A,下屏叫引擎B)。如果想要通过hook插入图片的话,这一点可能要注意。

08000000h是gba插槽,一般不用管。

ffff0000h arm9的bios,属于nds固件的一部分,在ds上电启动时,arm9的pc寄存器会被硬件电路强制设置为这个地址,这个对于制作金手指基本没有帮助

下面是arm7的,实际上arm7在上面已经附带地说过了,这里就说一下不一样的:
00000000h arm7的BIOS,上电时arm7的pc指针会被硬件设置到这里
01000000h 完全没用,与arm9不同。
03800000h 到 0380ffffh 这64KB是arm7独有的wram
这就是所有与金手指制作相关的内存区域了。

?可是,ds的rom上哪去了?
如果有朋友研究过gba的虚拟内存映射,一定会发出这样的疑问,因为gba的rom在08000000到09ffffff之间,正好32MB,结果ds 4Gb的rom,居然完全没有出现?那么,rom里的内容到底是哪来的?
其实,对于gba那种将整个rom直接映射到虚拟空间的方式,叫做xip(就地执行),而对于现代计算机而言,基本不会将rom或硬盘之类的大型存储介质直接映射到cpu的地址总线上,而是会提供io接口,对这些介质进行读写。(例如我们的电脑的cpu就是使用out指令将硬盘里的可执行文件(exe什么的)读进内存条里再进行执行),gba那种方式反而是小众做法。
因此,ds也是通过提供io接口的方式对rom数据进行读取,并且其读取还可以设置加密的方式。(这里插播一句,通过对这个io下断点,我从某个汉化游戏中抄到了一段可以从ds的rom任意位置读取内容到main ram的asm程序,一会儿我发到楼下去)不过,这个主要是为了解答可能产生的疑惑(因为我当初研究ds的时候就产生了)。也就是说,这里其实更多是给我自己看的T.T

OK,那么很显然,通过上面的描述,我们就知道了,要想制作金手指,首先我们得知道我们想修改的数据被存在了什么地址。这个部分可以通过desmume的search ram功能完成。
至于如何完成...可以看一下cheat engine之类的修改电脑游戏的软件的使用教程,道理是相同的。例如游戏里想修改的值是100,那就搜索100,然后让100变成99,再搜索99,之类的。

在知道地址之后,我们就可以开始制作金手指了。

本文的标题是gameshark金手指制作,显然重头戏就是gameshark金手指的格式。不过在此之前,还要对ds的gameshark做一点小小的简介(我知道铺垫很长,但是马上就要关键了,相信我)
gameshark是datel公司制作的一种金手指,又叫action replay。它其实本来是一种机器,插在nds实机上用的。不过,大多数nds模拟器也提供对这种金手指类型的支持。万恶的是,根据我查到的资料,ds的gameshark与gba不同,gba的gameshark是用fpga实现的,结构也比较简单,而ds的gameshark,似乎是内置了一块mcu,结果导致其功能也复杂了很多T_T,编写复杂的金手指时,甚至类似于编写程序(我看这东西,能条件能循环还能存取,这东西没准是图灵完备的。。。)
因此,在介绍gameshark金手指格式之前,我首先解释一下,gameshark机器里面,其实有6个寄存器,它们分别叫做offset1,offset2,data1,data2,storage1,storage2,共有三类,即offset类,data类,storage类,每类两个。每个类都有一个寄存器是当前默认寄存器。在金手指开始执行时,每一类的默认寄存器都是1号寄存器。可以通过一种金手指切换默认寄存器。默认寄存器存在的意义就是某些金手指会让寄存器参与运算,并且参与运算的就是当前的默认寄存器。在下文我书写金手指时,每一类的类名就代表这一类的默认寄存器。

ok,铺垫了这么一大堆,终于到了激动人心的环节(希望看到这里的朋友还没有被我烦到,因为我希望尽可能全面地对相关问题进行解释,所以可能会聊一些对于金手指制作而言没那么重要的东西),我们在上面通过search ram得到的地址,终于要变成金手指了!

一.内存直写型:
0XXXXXXX YYYYYYYY 32bit写入 将地址XXXXXXX + offset上的32bit值修改为YYYYYYYY
1XXXXXXX 0000YYYY 16bit写入 将地址XXXXXXX + offset上的16bit值修改为YYYY
2XXXXXXX 000000YY 8bit写入   (8bit)[XXXXXXX + offset] = YY

第三行是一种省略的写法,方括号表示括号内地址的值,等号表示将右边的值赋给左边,(8bit)表示这个地址上数据的长度是8bit,对比上面两行即可明白,下文同理

如上所述,此处的offset是指当前的offset类的默认寄存器,下文同理

二.条件判断型:
如果条件成立,继续执行,否则结束执行当前金手指块(下文有结束型金手指)
这种条件判断型金手指的行为会为下文的条件操纵型金手指改变。
1. 32bit类
3XXXXXXX YYYYYYYY – 大于 (YYYYYYYY > [XXXXXXX + offset])
4XXXXXXX YYYYYYYY – 小于 (YYYYYYYY < [XXXXXXX + offset])
5XXXXXXX YYYYYYYY – 等于 (YYYYYYYY == [XXXXXXX + offset])
6XXXXXXX YYYYYYYY – 不等于 (YYYYYYYY != [XXXXXXX + offset])

2. 16bit类
7XXXXXXX ZZZZYYYY – 大于 (YYYY > [XXXXXXX + offset] & ~ZZZZ)
8XXXXXXX ZZZZYYYY – 小于 (YYYY < [XXXXXXX + offset] & ~ZZZZ)
9XXXXXXX ZZZZYYYY – 等于 (YYYY == [XXXXXXX + offset] & ~ZZZZ)
AXXXXXXX ZZZZYYYY – 不等于  (YYYY != [XXXXXXX + offset] & ~ZZZZ)
此处,& ~符号的含义是与运算和取反运算,它们都属于位运算,就是和加减乘除一样都是运算。如果不了解位运算的计算方法,百度应该比我说得清楚(*^_^*)

三.条件操纵型:
DFFFFFFF 00000000 – 默认状态,上述条件判断金手指的用法就是在此状态下工作的
DFFFFFFF 00000001 – 从地址中取出的值不再与YYYY进行比较,而是与data比较
DFFFFFFF 00000002 – YYYY与data比较
DFFFFFFF 00000003 – storage与YYYY比较
DFFFFFFF 00000004 – data与storage比较

这些金手指执行后,条件判断型金手指的行为将会发生改变。
注意:这一类型的金手指,在gbatek中没有记载,很可能它是新版本的gameshark中才有的类型

四.offset寄存器操纵型:
BXXXXXXX 00000000 – offset = [XXXXXXX + offset]  这一种金手指可能是用来支持多级指针的
D3000000 XXXXXXXX – offset1 = XXXXXXXX
D3000001 XXXXXXXX – offset2 = XXXXXXXX
DC000000 XXXXXXXX – offset = XXXXXXXX+offset

五.循环控制型:
C0000000 YYYYYYYY – 执行下一个金手指块YYYYYYYY次
C1000000 00000000 – 执行下一个金手指块data1次
C2000000 00000000 – 执行下一个金手指块data2次
D0000000 00000001 – 停止当前循环执行

六.终止型:
D0000000 00000000 – 条件块结束
D2000000 00000000 – 停止所有块与循环
D2000000 00000001 – 停止金手指执行。不会结束块,所以可以条件执行

七.data寄存器操纵型:
D4000000 XXXXXXXX – data = XXXXXXXX +data
D4000001 XXXXXXXX – data1 = data1 + data2 + XXXXXXXX
D4000002 XXXXXXXX – data2 = data2 + data1 + XXXXXXXX

D5000000 XXXXXXXX – data = XXXXXXXX
D5000001 XXXXXXXX – data1 = XXXXXXXX
D5000002 XXXXXXXX – data2 = XXXXXXXX

D6000000 XXXXXXXX – (32bit) [XXXXXXXX+offset] = data ; offset += 4
D6000001 XXXXXXXX – (32bit) [XXXXXXXX+offset] = data1 ; offset += 4
D6000002 XXXXXXXX – (32bit) [XXXXXXXX+offset] = data2 ; offset += 4

D7000000 XXXXXXXX – (16bit) [XXXXXXXX+offset] = data & 0xffff ; offset += 2
D7000001 XXXXXXXX – (16bit) [XXXXXXXX+offset] = data1 & 0xffff ; offset += 2
D7000002 XXXXXXXX – (16bit) [XXXXXXXX+offset] = data2 & 0xffff ; offset += 2

D8000000 XXXXXXXX – (8bit) [XXXXXXXX+offset] = data & 0xff ; offset++
D8000001 XXXXXXXX – (8bit) [XXXXXXXX+offset] = data1 & 0xff ; offset++
D8000002 XXXXXXXX – (8bit) [XXXXXXXX+offset] = data2 & 0xff ; offset++

D9000000 XXXXXXXX – (32bit) data = [XXXXXXXX+offset]
D9000001 XXXXXXXX – (32bit) data1 = [XXXXXXXX+offset]
D9000002 XXXXXXXX – (32bit) data2 = [XXXXXXXX+offset]

DA000000 XXXXXXXX – (16bit) data = [XXXXXXXX+offset] & 0xFFFF
DA000001 XXXXXXXX – (16bit) data1 = [XXXXXXXX+offset] & 0xFFFF
DA000002 XXXXXXXX – (16bit) data2 = [XXXXXXXX+offset] & 0xFFFF

DB000000 XXXXXXXX – (8bit) data = [XXXXXXXX+offset] & 0xFF
DB000001 XXXXXXXX – (8bit) data1 = [XXXXXXXX+offset] & 0xFF
DB000002 XXXXXXXX – (8bit) data2 = [XXXXXXXX+offset] & 0xFF

八.寄存器操纵型:
DF000000 00000000 - 将offset1设为默认
DF000000 00000001 - 将offset2设为默认
DF000001 00000000 - 将data1设为默认
DF000001 00000001 - 将data2设为默认
DF000002 00000000 - 将storage1设为默认
DF000002 00000001 - 将storage2设为默认

DF000000 00010000 - offset2 = offset1
DF000000 00010001 - offset1 =offset2
DF000001 00010000 - data2 = data1
DF000001 00010001 - data1 = data2
DF000002 00010000 - data1 = storage1
DF000002 00010001 - data2 =storage2

DF000000 00020000 - data1 = offset1
DF000000 00020001 - data2 = offset2
DF000001 00020000 - offset1 = data1
DF000001 00020001 - offset2 = data2
DF000002 00020000 - storage1 = data1
DF000002 00020001 - storage2 = data2

九.算数运算型
F1XXXXXX YYYYYYYY - [XXXXXX + offset] += YYYYYYYY 相当于[XXXXXX + offset] = [XXXXXX + offset] +YYYYYYYY,下同
F2XXXXXX YYYYYYYY - [XXXXXX + offset] *= YYYYYYYY
F3XXXXXX YYYYYYYY - [XXXXXX + offset] /= YYYYYYYY
F4000000 YYYYYYYY -  data *= YYYYYYYY
F5000000 YYYYYYYY -  data /= YYYYYYYY
F6000000 YYYYYYYY -  data &= YYYYYYYY
F7000000 YYYYYYYY -  data |= YYYYYYYY
F8000000 YYYYYYYY -  data ^= YYYYYYYY
F9000000 00000000 -  data = ~data
FA000000 YYYYYYYY -  data <<= YYYYYYYY
FB000000 YYYYYYYY -  data >>= YYYYYYYY

OK,以上就是常用的gameshark金手指的代码格式啦。
用搜索到的地址,做你想做的任何事情都可以~

最后补充三个小细节:
1.按键io
上文中谈到,我们经常制作按键触发型金手指,实际上这里的实现就需要知道io接口内存区域,对应按键的地址是多少。其实很简单,gba的按键io与nds相同,都位于04000130。
这里我们复制gbatek中的内容:

04000130h - KEYINPUT - Key Status (R)
  Bit   Expl.
  0     Button A        (0=Pressed, 1=Released)
  1     Button B        (etc.)
  2     Select          (etc.)
  3     Start           (etc.)
  4     Right           (etc.)
  5     Left            (etc.)
  6     Up              (etc.)
  7     Down            (etc.)
  8     Button R        (etc.)
  9     Button L        (etc.)
  10-15 Not used

上面这段话的意思是说,04000130这个地址是只可读不可写的,每个按键对应一个二进制位,0表示按下,1表示未按下
结合这个,我们就可以制作检测按键的金手指了~
例如
94000130 FFFE0000
就表示,当玩家按下a键时,继续执行当前金手指块

不过,有一点需要注意,就是ds有独有的x和y键,这两个键是单独映射在04000136这个内存地址里的,感兴趣的朋友可以自己看一下gbatek。

2.gameshark里这3组寄存器的稳定性是不同的。只有storage的两个寄存器,在代码块之间仍然会保持其数值。

3.本文主要参考了gbatek。不过实际上,金手指的格式也参考了一些别的文档,它们与gbatek中的描述并不完全相同,我猜测可能是由于datel公司发布过不同版本的gameshark所致。
因此,某些金手指类型,可能会出现模拟器不兼容的现象,不过常见的条件之类的,应该都是支持的。

最后,非常感谢Martin Korth写出了gbatek这么全面的文档,也感谢一切我为了写出此文而参阅的其它文档的作者~

评分

参与人数 2积分 +100 勋章券 +8 现金 +200 收起 理由
海のLUGIA + 50 + 6 + 100 妈妈问我为什么要跪着看帖系列
jiangzhengwenjz + 50 + 2 + 100

查看全部评分

回复

使用道具 举报

6

回帖

276

现金

8

勋章券

离开家乡的彷徨

Rank: 4Rank: 4

积分
141
 楼主| 发表于 2023-9-20 22:51:57 | 显示全部楼层
本帖最后由 jiangzhengwenjz 于 2023-9-21 03:43 编辑
  1. .nds
  2. .arm

  3. READFILE:
  4. stmfd    r13!,{r0-r5,lr}
  5. ldr      r2,=0x04100010
  6. ldr      r5,=0x040001A4
  7. bl       Set_B7
  8. ldr      r3,=0xA1416657
  9. str      r3,[r5]
  10. mov      r3,0

  11. Read:
  12. ldr      r0,[r5]
  13. ands     r4,r0,0x800000
  14. beq      End

  15. ldr      r4,[r2]
  16. cmp      r3,0x80 // 这里,想读多少字节数据,改成对应的就好了
  17. strcc    r4,[r1, r3, lsl 2]
  18. addcc    r3,r3,1
  19. ands     r4,r0,0x80000000
  20. bne      Read
  21. End:
  22. mov      r0, 0   
  23. bl       Set_B7
  24. ldmfd    r13!,{r0-r5,pc}

  25. Set_B7:
  26. stmfd    r13!,{r1-r2}
  27. ldr        r2, =0x040001A4

  28. Wait:
  29. ldr       r1, [r2]
  30. ands      r1, r1, 0x80000000
  31. bne       Wait
  32. mov       r1,0xC0
  33. strb      r1,[r2, -0x03]
  34. ldr       r1, =0x040001A8
  35. cmp       r0, 0
  36. movne     r2, 0xB7
  37. moveq     r2, 0xB8  
  38. strb      r2, [r1], 1
  39. mov       r2, r0, lsr 0x18
  40. strb      r2, [r1], 1
  41. mov       r2, r0, lsr 0x10
  42. strb      r2, [r1], 1
  43. mov       r2, r0, lsr 0x8
  44. strb      r2, [r1], 1
  45. strb      r0, [r1], 1
  46. mov       r2, 0
  47. strb      r2, [r1], 1
  48. strb      r2, [r1], 1
  49. strb      r2, [r1], 1
  50. ldmfd     r13!,{r1-r2}
  51. bx        lr
  52. .pool
复制代码



其中,READFILE接受两个参数,r0是rom的偏移,r1是main ram指针
这个是从塞尔达里抄过来的,如果侵权了之类的麻烦说一声,楼主给它删掉

读一读这个程序就知道rom的io接口该怎么设置了~
回复 支持 反对

使用道具 举报

3369

回帖

535

现金

485

勋章券

超级版主

Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26Rank: 26

积分
22138
QQ

时光印记Lv.3挥金如土勋章水中王者勋章Lv3Omega红宝石发售确认纪念章Alpha蓝宝石发售确认纪念章金银好CP-Ho-oh/LUGIA【里】金银好CP-Ho-oh/LUGIA【真·正常向】异化型LUGIA【M超梦版】

发表于 2023-9-21 11:28:35 | 显示全部楼层
本帖最后由 jiangzhengwenjz 于 2023-9-21 13:10 编辑

  1. #define AUXSPICNT  (*(vu16 *)0x40001A0)
  2. #define AUXSPIDATA (*(vu16 *)0x40001A2)
  3. #define ROMCTRL    (*(vu32 *)0x40001A4)
  4. #define CMDOUT     (*(vu64 *)0x40001A8)
  5. #define DATAIN     (*(vu32 *)0x4100010)

  6. #define AUXSPICNT_TRANSFER_READY         0x8000
  7. #define AUXSPICNT_NDS_SLOT_ENABLE        0x4000
  8. #define AUXSPICNT_NDS_SLOT_MODE          0x2000

  9. #define ROMCTRL_STATUS_START                0x80000000
  10. #define ROMCTRL_STATUS_BUSY                 0x80000000
  11. #define ROMCTRL_DATA_WORD_STATUS_READY        0x800000

  12. static void ConfigTransfer(uintptr_t romAddr) {
  13.     vu8 *cmds = (vu8 *)&CMDOUT;

  14.     while (ROMCTRL & ROMCTRL_STATUS_BUSY) ;

  15.     AUXSPICNT &= ~AUXSPICNT_NDS_SLOT_MODE;
  16.     AUXSPICNT |= (AUXSPICNT_TRANSFER_READY | AUXSPICNT_NDS_SLOT_ENABLE);

  17.     cmds[0] = romAddr == 0 ? 0xB7 : 0xB8; // 0xB7: get data
  18.                                           // 0xB8: get rom chip ID
  19.     cmds[1] = romAddr >> 24;
  20.     cmds[2] = romAddr >> 16;
  21.     cmds[3] = romAddr >> 8;
  22.     cmds[4] = romAddr;
  23.     cmds[7] = cmds[6] = cmds[5] = 0;
  24. }

  25. void ReadFile(uintptr_t romAddr, u32 *ramAddr) {
  26.     s32 i = 0;

  27.     ConfigTransfer(romAddr);
  28.     // set:
  29.     // key1 gap1 length for newer 1T ROMs = 0x657
  30.     // enable key2 encrypt data
  31.     // do not apply key2 encryption seed
  32.     // key1 gap2 length = 1
  33.     // enable key2 encrypt cmd
  34.     // data block size to 0x100 << 1 bytes
  35.     // clock rate to 6.7MHz
  36.     // hold clock high during gaps
  37.     // release RESB
  38.     // data direction to "read"
  39.     // block start
  40.     ROMCTRL = 0xA1416657;

  41.     while (ROMCTRL & ROMCTRL_DATA_WORD_STATUS_READY) {
  42.         if (i < 0x80)
  43.             ramAddr[i++] = DATAIN;
  44.         if (!(ROMCTRL & ROMCTRL_STATUS_BUSY))
  45.             break;
  46.     }
  47.     ConfigTransfer(0);
  48. }
复制代码

完全不懂DS,简单翻译了下那段读取rom数据的程序。也许楼主可以再详细解释一下,比如为什么要切换成B8命令,以及对40001A4设置的一些细节?
回复 支持 反对

使用道具 举报

6

回帖

276

现金

8

勋章券

离开家乡的彷徨

Rank: 4Rank: 4

积分
141
 楼主| 发表于 2023-9-21 12:05:55 | 显示全部楼层
最后列举一下本文的参考~
首先,感谢JZW大大在各方面的指导(asm,核与线程,等等)
然后是本文的主要参考,gbatek
http://problemkaputt.de/gbatek-contents.htm
我主要参考了其中关于ds内存管理的部分,不过它也有gameshark金手指的格式,就在 DS Cartridges, Encryption, Firmware 一节
然后是一些金手指格式的文档,其中主要是这个:
https://gist.github.com/Nanquita ... fa76de860/revisions
这个页面显示了这个文档较为全面的变化流程~(不过,我其实有点怀疑这个可能是用于3ds的,因为它里面提到了创造页。一般来说,分页机制应该是在有操作系统的机器上才能实现的)
https://github.com/turtleisaac/i ... er/include/macros.s
https://github.com/Eiim/DS_ARCodeTools/blob/master/main.js
https://github.com/mkdsk/DSAR-translator/blob/master/notes.txt
以上这几个是我参考的其它关于金手指格式的文档,它们应该也可以直接用来处理ar码
如果在实际操作中遇到一种码不灵的情况,可以分别看看这几个文档,对照着使用
https://blog.csdn.net/LuckilyYu/article/details/6182300
这篇文章是我之前入门学习nds架构时看到的一篇文章,我虽然具体说不出来它对我这篇文章有啥帮助,但是我觉得它在当时无疑帮助我更容易地理解了nds的硬件结构,后面看gbatek时也稍微没那么吃力了
https://www.intel.cn/content/www ... ical/intel-sdm.html
https://github.com/torvalds/linux
以及百度百科(误)
之前为了学习线程,就自己试着看pthread,结果感觉还是有点搞不懂,于是就试着读了读linux 0.11的源码,然后就在查intel手册的过程中中误打误撞地弄明白了in与out指令
https://zhuanlan.zhihu.com/p/368276428
xip技术,我是在查找内存管理相关的知识时,偶然了解到的。这一篇文章关于xip解释地比较清楚,我当时就是看了这个之后基本就明白了
https://www.bilibili.com/video/BV1Am4y1D7VP?p=50
关于多核访问可能导致总线短路之类的知识,我是最早为了研究存储器的结构而学习数字电路的时候了解到的,感谢西安电子科技大学给力的网课,讲得很清楚
https://cheatengine.org/
这个是cheatengine的官网,我从它自带的教程中也学习了一些内存扫描技巧(不过,实事求是地说,这东西我接触地挺晚的,很多东西是自己之前摸索的,如果早点接触也许就不会走那么多弯路了T_T)
需要注意的是cheatengine官方网站和github上的release都带有垃圾捆绑软件,如果想得到纯净的版本可以上贴吧或者什么地方找人家弄好的版本(讲道理这一点还挺坑爹的,我之前就中过一次招)
对以上各个文档或软件的作者表示感谢,你们使得这篇文章成为可能!
由于也经过了一段时间,上面列举的参考难免有遗漏之处,在此也对所有我没有提到,但是事实上对这篇文章的写出有贡献的所有个人或组织表示感谢!

评分

参与人数 1积分 +5 现金 +10 收起 理由
jiangzhengwenjz + 5 + 10

查看全部评分

回复 支持 反对

使用道具 举报

6

回帖

276

现金

8

勋章券

离开家乡的彷徨

Rank: 4Rank: 4

积分
141
 楼主| 发表于 2023-9-21 12:56:02 | 显示全部楼层
我个人猜想是,从这段asm在设置b8后并没有读取rom来看,设置为b8的目的应该就只是关闭io接口ヾ(&#8226;ω&#8226;`)o
比如是为了防止b7读取时出错的一种保护措施之类的(大概T.T)
040001a4那里我也是对照着gbatek看的,里面我想应该涉及到ds卡带的加密机制,这个我暂时也没完全搞懂T_T,不过我想之所以出来的数据没有加密是因为没有apply key2 encryption seed,而之所以要启动key2加密应该是因为所有的b7指令都以key2加密模式运行。
其它的位,我也没有完全搞懂为什么应该那样设置T_T,我想或许可以进一步研究一下ds相关的手册之类的来搞清楚
不过这段asm我烧录到其它ds游戏中是可用的<(^-^)>,如果不深入研究原理的话应该直接用也没什么问题(大概T_T)

评分

参与人数 1积分 +5 现金 +10 收起 理由
jiangzhengwenjz + 5 + 10

查看全部评分

回复 支持 反对

使用道具 举报

6

回帖

276

现金

8

勋章券

离开家乡的彷徨

Rank: 4Rank: 4

积分
141
 楼主| 发表于 2024-1-3 16:28:23 | 显示全部楼层
本帖最后由 qdrz 于 2024-1-3 16:35 编辑

重新观看这个帖子,感觉自己应该举个例子,要不然可能还是不太清楚。
例如nds上的ffta2(game code为A6FJ),有这样一个金手指:

按下select键时获取全部战斗道具:
94000130 FFFB0000
D5000000 6300019C
C0000000 00000012
D6000000 0212E28C
D4000000 00000001
D2000000 00000000
D0000000 00000000
我们可以通过上文对gameshark格式的列举,逐行对这个金手指进行解析:
94000130 FFFB0000:
如果04000130这个内存地址上的16bit值与0xfffb取反后相与的结果为0时,继续执行接下来的金手指。
这句金手指起到了通过select键激发的作用。其原因可以通过计算得知:
0xfffb取反的结果为4,即0100b(一个数字后面加上b则表明这个数是二进制数)由于与运算具有“全1才1,有0则0”的特点,故与0100b相与后,04000130上的值只有从低位开始的第三个位会保留下来,也就是表示select键是否按下的那一个位。如上所述,当select键按下时,这个位会置零,此时条件成立,继续执行接下来的金手指。
D5000000 6300019C:
将data寄存器赋值为6300019c。这个值的意义我们后面解释。
C0000000 00000012:
执行下方金手指块0x12次。
D6000000 0212E28C:
向0212E28C+offset这个地址上写入data寄存器的值,之后offset寄存器+4
从上文没有初始化data寄存器,结合金手指的实际效果,可以合理地猜测,金手指块在执行时,各个寄存器的初始值应该是自动设置为了0
D4000000 00000001:
data寄存器+1
D2000000 00000000:
停止所有块与循环。也就是说循环体到此处结束,下方的金手指不再属于循环体的组成部分
D0000000 00000000:
条件块结束。此处应该是用于结束整个金手指块的执行,对应上方94000130处指示的金手指块的末尾
综合上述解释,这个金手指的作用就是,当玩家按下select键时,在0212E28C这个内存地址上顺序填写这样的内容:
9C 01 00 63    9D 01 00 63    9E 01 00 63    9F 01 00 63......(注意大小端序翻转)
结合金手指的作用,我们不难猜出,ffta2的战斗道具有0x12种,其编码使用了一个16bit数,第一种道具的编码为019C,并且存储玩家道具数据的方式为以32bit为1个单位,前16bit存储玩家拥有的道具类型,后16bit存储玩家拥有该类型道具的数量,各个单位顺序排列在0212E28C这个内存地址上。0x63=99,因此开启金手指后玩家各种战斗道具的数量都是99个。
我们可以通过简单地在模拟器中观察内存的变化情况来了解金手指的作用。附件中的图是no$gba debugger的内存视窗。图1是我在没有按下select键时的状况,图2是我按下后的状况。可以看到,内存发生了明显的变化。
这个小例子也许能更好地解释金手指的工作方式,在读懂每条金手指的含义后,通过模仿现存金手指的写法,并修改关键的参数信息,也是一种制造金手指的策略。

触发金手指前

触发金手指前

触发金手指后

触发金手指后

评分

参与人数 1积分 +10 现金 +20 收起 理由
jiangzhengwenjz + 10 + 20

查看全部评分

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入口袋中心

本版积分规则

手机版|Archiver|Pokemon Center

GMT+8, 2024-11-23 16:03 , Processed in 0.118779 second(s), 46 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表