QQ登录

只需一步,快速开始

 找回密码
 定下契约(新注册)

QQ登录

只需一步,快速开始

查看: 2021|回复: 7
收起左侧

[GTAF进口]SA内存控制

[复制链接]

传奇 Legend

谷歌翻译坑爹人肉版(不是人肉饭)

Rank: 16

UID
83
宝石
140 粒
金币
573 枚
节操
30 斤
灵石
0 块
精力
32 ℃
发表于 2012-4-24 21:46:27 | 显示全部楼层 |阅读模式

你这样只看不注册,真的大丈夫?~

您需要 登录 才可以下载或查看,没有账号?定下契约(新注册)

x
本帖最后由 kwanz 于 2012-4-25 21:07 编辑

原帖地址 http://www.gtaforums.com/index.php?showtopic=262280
SA内存控制

原帖作者/ Seemann @GTAF 翻译/ kwanz @虚拟世界
未经许可 谢绝转载

译注 本帖的一楼包含了Seemann大人及其他论坛高手在SB开发早期对内存操作技术的讨论,有一些例子和结果已经过时了。有个别不准确的说法已在后续回复中补充修正,译文中不再指出。所有的示例代码仅供研究学习原理使用。新版的SB+CLEO已经实现了opcode级别的WriteMemory和ReadMemory操作。具体请参考
EN - CLEO Script Tutorial
CH - CLEO编程教程(未完成)的地板(4#)
---------------------------------
我决定开一帖来集中讲解用SCM操作SA内存的所有例子。
如果你看不懂,也许不是你的错。我稍后会编辑帖子解释更清楚一点。
所有的代码是用SB2.99编写的。要成功运行,你需要下载最新版本的SB
http://www.gtaforums.com/index.php?showtopic=211077
译者注:或者下载【下载】SB汉化版+最小启动包 有汉化版SB
下载地址:http://game.55660.net/thread-138-1-1.html

所有代码在SA1.0美版总测试通过,不同版本的内存地址可能不同。如果什么东西用不了,请确认你的程序版本正确。


现在,我们有三种方法操作游戏内存。
1. 这个帖子讨论了操作内存的最初的方法。
Stat相关opcode提供了统计数据池附近有限的地址的内存访问。它允许一些简单的操作,比如更改玩家的金钱。
优点
- 只需要用到opcode
缺点
- 可访问地址非常有限,很多有用的地址不能访问

2. 方法二:用Xieon的补丁修改gta-sa.exe的3个opcode,提供了极大量的操作内存的方式。点此下载
优点
- 所有地址都可访问
- 可以保护带有VirtualProtect的内存区域,实现重写
缺点
- 需要破解exe;可能不兼容其他版本(不过 我还没有见过这个补丁不能正常工作的例子)
3. 方法三:用SA的数组访问0到FFFFFFFF之间的任何地址,此法首发在这一帖
优点
- 可以访问所有地址
- 容易使用
- 无特殊要求,纯scm实现
缺点
- 某些地址仍然不可写(会发生非法访问错误)


对法三,可以调用三个函数访问特定地址,简单地讲,包括
1 MemoryWrite: 写入特定长度的新值
参数 0@=地址 2@=新值 3@=长度(1,2,3,4)
2 MemoeryWrite_DWORD:写入新的双字节数值
参数 0@=地址 1@=新值
3 MemeoryRead: 从内存读取双字节数值
参数 0@=地址 1@=返回值

注意仍有部分地址是不可读/写的!!!
要破解这些地址,尝试用Xieon's的内存补丁: ..\tools\Sa Memory Path\

  1. //--写特定数值

  2. :MemoryWrite   
  3. 0085: 5@ = 0@
  4. 0@ /= 4
  5. 0@ *= 4           // 地址
  6. 0062: 5@ -= 0@    // 偏移 (0, 1, 2, 3)  
  7. :_GetInitValue      // 如果你已经把偏移赋给5@, 就可以跳到这里
  8. gosub @MemoryRead // 取初值
  9. 3@ *= 8 // bytes -> bits
  10. 5@ *= 8
  11. dec(3@)
  12. for 6@ = 0 to 3@
  13.   if
  14.     08B6: test 2@ bit 6@
  15.   then
  16.     08BF: set 1@ bit 5@    // 1
  17.   else
  18.     08C5: clear 1@ bit 5@  // 0
  19.   end
  20.   inc(5@) // next memory bit
  21. end   
  22. 008A: &0(0@,1i) = 1@  // 写入新值
  23. return


  24. //--写32位数值-----------

  25. :MemoryWrite32bit
  26. 0@ -= 0xA49960
  27. 0@ /= 4
  28. 008A: &0(0@,1i) = 1@   
  29. return

  30. //--读32位数值-----------

  31. :MemoryRead
  32. 0@ -= 0xA49960
  33. 0@ /= 4
  34. 008B: 1@ = &0(0@,1i)
  35. return
复制代码



我写了几个用这些函数的例子。多数的例子都发在GTAF上,有的发在别的地方
---------------------------------
例1
用CLEO做超长火车,15+节车厢!
原文链接(俄文)
效果截图
---------------------------------
  1. :LONGTRAINS
  2. thread 'TRAINS'
  3.   for 0@ = -382229 to -382216
  4.     wait 0
  5.     &0(0@,1i) = #STREAKC        
  6.   end
  7.                                              
  8.   // 修改type0

  9.   // 加载模型
  10.   #FREIGHT.Load
  11.   #FREIFLAT.Load
  12.   #STREAKC.Load

  13.   while true
  14.     if and
  15.       Model.Available(#FREIGHT)
  16.       Model.Available(#FREIFLAT)
  17.       Model.Available(#STREAKC)   
  18.     then
  19.       Break
  20.     end
  21.     wait 0
  22.   end
  23.    
  24.   // 生成带有新车厢的火车
  25.   06D8: 1@ = create_train_at 2278.1771 -1144.8823 27.5108 type 0 direction 1

  26.   #FREIGHT.Destroy
  27.   #FREIFLAT.Destroy
  28.   #STREAKC.Destroy
  29.   
  30. end_thread
复制代码

---------------------------------
例2
运行时作弊码的新实现方法
原文链接
---------------------------------
  1. :NEWCHEATS
  2. // 例1:测试1个键(空格)
  3. thread 'CHEATS'
  4. 0@ = -229908
  5. while true
  6. 008B: 1@ = &0(0@,1i)      // 获得最后按键
  7. 0085: 2@ = 1@         
  8. div(1@, 0x 1 00)          // 1个字符: 0x100, 2个字符: 0x10000: 3个字符: 0x1000000
  9. mul(1@, 0x 1 00)          // 同上
  10. 0062: 2@ -= 1@            // 获得指定数目的按键记录 (1)

  11. if
  12.   2@ == 0x20              // 检测是否空格
  13. then  
  14.    03E5: text_box 'CHEAT1'// 显示作弊启用字样
  15.    Break
  16. end
  17. wait 1000
  18. end

  19. // 例2:测试2个键的序列 (NO)
  20. 0@ = -229908
  21. while true
  22. 008B: 1@ = &0(0@,1i)       // 获得最后按键
  23. 0085: 2@ = 1@         
  24. div(1@, 0x 1 00 00)        // 1个字符: 0x100, 2个字符: 0x10000: 3个字符: 0x1000000
  25. mul(1@, 0x 1 00 00)        // 同上
  26. 0062: 2@ -= 1@             // 获得指定数目的按键记录 (2)
  27. if
  28.   2@ == 0x4e4f             // NO
  29. then   
  30.   03E5: text_box 'CHEAT1'  // 显示作弊启用字样
  31.   Break
  32. end
  33. wait 1000
  34. end

  35. // 例3:测试3个键的序列('WOW')
  36. 0@ = -229908
  37. while true
  38. 008B: 1@ = &0(0@,1i)       // 获得最后按键
  39. 0085: 2@ = 1@         
  40. div(1@, 0x 1 00 00 00)     // 1个字符: 0x100, 2个字符: 0x10000: 3个字符: 0x1000000
  41. mul(1@, 0x 1 00 00 00)     // 同上
  42. 0062: 2@ -= 1@             // 获得指定数目的按键记录 (3)
  43. if
  44.   2@ == 0x574f57           // WOW
  45. then
  46.     03E5: text_box 'CHEAT1'// 显示作弊启用字样
  47.   Break
  48. end
  49. wait 1000
  50. end

  51. // 例4:测试4个键的序列 ('HACK')
  52. 0@ = -229908
  53. while true
  54. if
  55.   &0(0@,1i) == 0x4841434B  // HACK
  56. then
  57.   03E5: text_box 'CHEAT1'  // 显示作弊启用字样
  58.   Break
  59. end
  60. wait 1000
  61. end

  62. // 例5:测试5个键的序列 ('SANNY')
  63. // 首先在偏移+4位的地址上检测第5键是否为s,然后再测试anny
  64. //  地址      按键
  65. // -229908: X X X X    |  -229908: A N N Y
  66. // -229907: X X X O <- |  -229907: _ _ _ S                    
  67. while true
  68. 0@ = -229907 // 偏移+32位; 也就是最后按键记录地址偏移+4字节/字符处
  69. 008B: 1@ = &0(0@,1i)
  70. 0085: 2@ = 1@         
  71. div(1@, 0x100)
  72. mul(1@, 0x100)
  73. 0062: 2@ -= 1@               // 第5个字符是第二个按键块的末字母
  74. if
  75.   2@ == 0x53                 // 检测第5键是否为s
  76. then                        
  77.    0@ = -229908
  78.    008B: 3@ = &0(0@,1i)
  79.    if 3@ == 0x414E4E59       // 检测后续按键是否为anny
  80.    then  
  81.      03E5: text_box 'CHEAT1' // 作弊启用
  82.      Break
  83.    end     
  84. end
  85. wait 1000
  86. end
  87.    
  88. // 例6:测试16个键的序列: '1234567812345678'
  89. 0@ = -57477
  90. while true
  91. if
  92.   &0(0@,1v) == "8765432187654321" // "1234567812345678"
  93. then
  94.   03E5: text_box 'CHEAT1'    // 作弊启用
  95.   Break
  96. end
  97. wait 1000
  98. end
  99. end_thread
复制代码

---------------------------------
例3
修改任意线程的局部变量数值
原文链接(俄文)
---------------------------------
  1. // ---------------------------------------------
  2. //  此线程搜索名为'TEST'的线程
  3. //  并修改它的局部变量10@
  4. // ---------------------------------------------
  5. :CHANGELOCALVAR
  6. thread 'CLV'
  7. {
  8.   0@  =  thread address
  9.   1@  =  temp
  10. }
  11. 0@ = 0xA8B42C  
  12. // 查找线程循环
  13. while true
  14.   gosub @MemoryRead
  15.   // 1@ = 第一个活动线程
  16.   if 1@ == 0
  17.   then
  18.     Break // 没有找到活动线程,搜索失败
  19.   end
  20.   0085: 0@ = 1@    // 保存指针
  21.   // 获得线程名称的指针
  22.   div(1@, 8)
  23.   dec(1@, 1 348 395)
  24.   {
  25.    重要提示:
  26.     线程名称是以小写形式保存的,
  27.     所以我们要用小写进行比较.
  28.     注意SB选项'Case Converting'(大小写转换)必须设为'As is'(保持原值)!
  29.   }
  30.   if &0(1@,1s) == 'test'
  31.   // 测试线程名称是否"test"
  32.   then   
  33.     // 找到了,我们可以为所欲为
  34.     // 0@ 包含线程地址

  35.     // 获得10@的地址
  36.     inc(0@, 0x3c)     // 线程局部存储池
  37.     inc(0@, 40)       // 局部变量号 * 4; 比如说要修改9@,就设为36
  38.    
  39.     // 写入双字节数值
  40.     1@ = 3333 // 新值
  41.     gosub @MemoryWrite32bit

  42.     Break // 结束循环
  43.   else
  44.     // 否,线程名称不符
  45.     // 检查下一个
  46.     wait 0
  47.   end
  48.   // 跳转到while循环的开始处
  49. end
  50. // 变量值已修改,结束线程
  51. end_thread

  52. // ---------------------------------------------
  53. //  这个线程在被激活1秒后
  54. //  显示10@的数值
  55. // ---------------------------------------------
  56. :TEST1
  57. thread 'TEST'
  58. 10@ = 10000
  59. wait 3000
  60. 054C: use_GXT_table 'POOL'
  61. 01E3: text_1number_styled 'NUM' 10@ 5000 ms 1  // ~1~
  62. end_thread
复制代码


以下是一个改进版的CLV线程
  1. :CLV
  2. 03A4: name_thread 'CLV'
  3. 0006: 0@ = 67251

  4. :CLV_LOOP
  5. 008B: 0@ = &0(0@,1i)
  6. 00D6: if
  7. 8039:   not  0@ == 0
  8. 004D: jump_if_false @CLV_END
  9. 0001: wait 0 ms
  10. 0085: 1@ = 0@
  11. 0016: 1@ /= 8
  12. 000E: 1@ -= 1348395
  13. 0016: 0@ /= 4
  14. 000E: 0@ -= 2696792
  15. 00D6: if
  16. 05AD:   &0(1@,1s) == 'test'
  17. 004D: jump_if_false @CLV_LOOP

  18. // 25 为局部变量号+15
  19. // 比如,要修改9@就设为24
  20. 000A: 0@ += 25

  21. // 3333为新值
  22. 0004: &0(0@,1i) = 3333

  23. :CLV_END
  24. 004E: end_thread
复制代码

---------------------------------
例4
去除CJ第一次抢车时"To stop Carl..."的提示
原文链接
---------------------------------
  1. :MSGREMOVE
  2. thread 'NOMSG'
  3. 0@ = 0xC0BC15 //  地址
  4. 2@ = 1        //  数值
  5. 3@ = 1        //  长度 (字节)
  6. gosub @MemoryWrite
  7. end_thread
复制代码



你可以点这个下载带有全部示例的main.scm
http://www.mysharefile.com/v/7409206/memhandling.rar.html
已包含自述文件和源代码

更新历史
2006.12.06
- 添加了Xieon的补丁的链接
- 添加了改进版的CLV线程(例3)

---------------------------------
---------------------------------
main.scm已破解!
我终于找到不用任何补丁从main.scm运行汇编代码的方法了。
从此以后编程无极限。
这种方法类似CyQ的注入汇编代码修改opcode句柄的方法
我找到SA中有两个opcode可以实现这个效果
它们是0572和0A3D
方法是这样的
1. SA中有92个作弊码,有些是没有用的,有些大家都知道,很常用
2. Opcode里也有很常见的,比如0572给所有出租车加氮气,0A3D让女支女给你钱(无所谓,我都用0572)
但不是所有人都知道这些opcode的原理和作弊十分类似。它们会开启92个标志位之一,游戏会认为你作弊(但不会影响作弊计数,只是和调用作弊效果一样)
3. 0A3D与作弊标志90相关,0572-标志位91
4. 在标志位上,所有的作弊调用函数地址都以简单的双字节形式存储,可以用数组法在运行时改变
5. 也就是说,这个opcode做到的是用一个新数值修改main.scm的91号作弊标志(所有的地址我们都已熟知)。然后我们启用作弊,游戏就会跳转到我们设定的地址,换句话说,它跳进了main.scm,也就达到了汇编修改运行代码的效果。
在SB中使用hex...end结构,我们可以执行任意代码。
我个人asm用的不好,所以这里给的例子没什么意思(给玩家+1000刀),但这只是个开始。
随便一个会用asm的人可以写出带VirtualProtect的函数,然后我们甚至不需要Xieon的补丁就可以操作被保护的地址(比如,重力相关的地址)
  1. // ----------------------------------
  2. //      Asm代码注入器
  3. // ----------------------------------
  4. // 设置新的句柄地址
  5. 0@ = -429863
  6. &0(0@,1i) = 0xA49960
  7. &0(0@,1i) += @__AsmInjection
  8. // 运行Asm注入
  9. 0572: 1
  10. // 还原句柄
  11. &0(0@,1i) = 0
  12. return

  13. :__AsmInjection
  14. hex
  15. // 这里写上asm代码


  16. // 我的例子是加钱
  17. B8 E8030000     // mov eax, 1000
  18. 01 05 50CEB700  // add [0xB7CE50], eax
  19. C3              // return

  20. end
复制代码

这段程序对0xB7CE50(金钱)加1000。有点没意思,不过很好用!

注意:程序在1.0美版中测试通过。如果你用的是v1.01,请自行测试。

额,下面是另一个例子
http://sannybuilder.com/files/cheater.rar
这个程序允许你按下动作键启用所有作弊
---------------------------------
---------------------------------
我已经实现了从main.scm运行VirtualProtect函数的方法,现在已经没有必要使用Xieon的补丁了。我们用scm可以达到相同的效果。在1.0美版,欧版,德版中可用
下面是向带VP保护的地址写入定长数值(1,2,4字节)的完整代码
  1. :MemoryProofWrite
  2. 3@ == 1
  3. jf @_____novp
  4. 4@ = -429863
  5. &0(4@,1i) =  0xA49960
  6. &0(4@,1i) += @_____vpsv
  7. 0052: gap 0 virtual_protect_at_address 0@ size 1@ newprotect 4 gap 0 0
  8. 0572: 1
  9. gosub @MemoryWrite
  10. 0052: gap 0 virtual_protect_at_address 0@ size 1@ newprotect -1 gap 0 0
  11. 0572: 1
  12. &0(4@,1i) = 0
  13. end_thread

  14. :_____novp
  15. gosub @MemoryWrite
  16. end_thread

  17. :MemoryWrite
  18. 3@ = -429864
  19. &0(3@,1i) =  0xA49960
  20. &0(3@,1i) += @_____mwss
  21. 0052: gap 0 target_address 0@ size 1@ value 2@ gap 0 0
  22. 0A3D: 1
  23. &0(3@,1i) = 0
  24. return

  25. :_____vpsv
  26. hex
  27. 68 F4 3C A4 00 83 3D 84 3C A4 00 FF
  28. 75 08 FF 35 F4 3C A4 00 EB 06 FF 35
  29. 84 3C A4 00 FF 35 80 3C A4 00 FF 35
  30. 7C 3C A4 00 FF 15 2C 80 85 00 C3
  31. end

  32. :_____mwss
  33. hex
  34. 8B 15 7C 3C A4 00 8B 05 84 3C A4 00
  35. 83 3D 80 3C A4 00 01 75 03 88 02 C3
  36. 83 3D 80 3C A4 00 02 75 04 66 89 02
  37. C3 89 02 C3
  38. end
复制代码

这个线程只执行一次,然后停止。跟你写其他线程一样,把它摆到你的程序里。
当你用004F创建线程的时候,要传4个参数
  1. create_thread @MemoryProofWrite address (DWORD) size (Byte: 1,2,4) value (DWORD) VirtualProtect (BOOL)
复制代码

若你要写入无VP保护的地址,只需要使用
  1. create_thread @MemoryProofWrite address XXXX size XXXX value XXXX VirtualProtect 0
复制代码

或者略去VirtualProtect参数

Hello, World!
例1 修改重力(用补丁的例子)带VP
  1. create_thread @MemoryProofWrite address XXXX size XXXX value XXXX VirtualProtect 0
复制代码

编译版scm和源代码

例2 去除首次抢车消息提示 不带VP
  1. create_thread @MemoryProofWrite address 0xC0BC15 size 1 value 1
复制代码

---------------------------------
---------------------------------
引用 (DexX @ Mar 3 2007, 13:16)
你是说我们可以往游戏内存的任意位置写入任意数据么?

内存地址任意,但数值长度必须为1,2,4字节
可以包含类似这样的asm代码
  1. cmp size, 1
  2. ja @2
  3. mov byte ptr [address], value
  4. ret

  5. :2
  6. cmp size, 2
  7. ja @4
  8. mov word ptr [address], value
  9. ret

  10. :4
  11. mov dword ptr [address], value
  12. ret
复制代码

我可以实现写入任何长度的BlockWrite函数,需要不?
---------------------------------
---------------------------------
引用 (PLPynton @ Mar 3 2007, 19:12)
在反编带有十六进制数据的脚本时会不会出现丢失数据的情况?

从v.298, IIRC以后就不会了
SB控制台可以接受如何输入命令,你可以打开IGNORE_UKNNOWN选项来反编任何受保护或损坏的scm。请参考帮助文件和控制台输出。
---------------------------------
---------------------------------
引用  
SB返回
"Thread not found. Base: 194426. Probably Script.img is broken"

我成功反编过带十六进制的文件啊
顺便说,没有一种确保正确反编所有文件的方法。反编的时候标签(_____vpsv, _____mwss)也会消失。所以对其他人最有效的方法就是祈祷作者在他的mod里包含了源文件吧。

引用  
我在用
create_thread @MemoryProofWrite address 0x00863984 size 4 value 0.002 VirtualProtect 1
的时候出状况了

神马状况?无法通过编译还是游戏出错?这个我搞过,用的好好的啊(顺便说一句,gravity=2.0的效果太喜感了)

(译注:修改重力只接受整型数值)
---------------------------------
---------------------------------
引用 (PLPynton @ Mar 3 2007, 21:45)
其实我们反编以后差不多就得到源文件了吧?

反编译器不需要代码中的特殊标记(nop等等)来检测是否存在内嵌十六进制代码。编译器只是把源代码和插入代码的偏移并在一起,并在结尾记录长度。这样也节省了空间。
所以,反编译器只是检测一下长度,然后直接插入纯文本,没有字节码了(本来就是源码。。)。很简单的。
不过想像一下,比如说,我有两个自定义线程,我准备把其中一个的源码插入main.scm。但是它使用了另一个线程的自定义变量和标签。结果是:得到的源码使用原有的名字,但反编以后县城名都变掉了,结果就会和源代码不兼容。
我觉得想要避免这个挺困难的,大家觉得呢
我觉得目前只有在用hex...end结构的时候需要用到这样的代码。我可以让编译器存储所有十六进制嵌入的偏移和长度,然后反编译器解析它们,从而解决兼容问题。

引用 (PLPynton @ Mar 3 2007, 20:32)
开头的if语句漏掉了

单行if无须用到00D6。我在节省main.scm的空间
你那gravity.rar好用吧。。。
---------------------------------
---------------------------------
引用 (SteaVor @ Mar 4 2007, 00:41)
现在从内存读取数值的最佳方法是什么?

我会为读取数据写类似的代码,不过我还是不确定传递目标指针的最佳方式
理论上结果大概是这样的:
  1. create_thread @MemoryProofRead address XXX size XXX variable_offset 20
复制代码

代码将数值存入变量$5
不过我还是可能用别的方法

引用 (PLPynton @ Mar 4 2007, 01:00)
我觉得我们对内嵌代码的讨论好像没有什么结果,无视我。。

不不不,这主意很好。我会首先对十六进制代码实现它,然后再看看它对纯文本源码的效果如何。
多谢各位的反馈。
---------------------------------
---------------------------------
好了,结果是这样
  1. { -----------------------------------------------------------------
  2.   内存访问线程

  3.   要向内存地址写入定长数值,使用
  4.   create_thread @MemoryProofWrite address X:DWord size X:BYTE value X:DWORD VirtualProtect X:BOOL

  5.   要从内存地址读取定长数值,使用
  6.   create_thread @MemoryProofRead address X:DWord size X:BYTE VirtualProtect X:BOOL
  7.   The read value will be stored to the variable $MEMORY_PROOF_READ

  8.   可用的size值为 1-字节 2-字 4-双字
  9.   VirtualProtect参数为可选
  10. ----------------------------------------------------------------- }

  11. :MemoryProofWrite
  12. 4@ = @MemoryWrite
  13. 3@ == 1
  14. jf @_____novp

  15. :_____mpvp
  16. 5@ = -429863
  17. &0(5@,1i) =  0xA49960
  18. &0(5@,1i) += @_____vpsv
  19. 0052: gap 0 virtual_protect_at_address 0@ size 1@ newprotect 4 gap 0 0
  20. 0572: run_VPSV 1
  21. gosub 4@
  22. 0052: gap 0 virtual_protect_at_address 0@ size 1@ newprotect -1 gap 0 0
  23. 0572: run_VPSV 1
  24. &0(5@,1i) = 0
  25. end_thread

  26. :MemoryProofRead
  27. 4@ = @MemoryRead
  28. 2@ == 0
  29. jf @_____mpvp

  30. :_____novp
  31. gosub 4@
  32. end_thread

  33. :MemoryWrite
  34. 4@ = -429864
  35. &0(4@,1i) =  0xA49960
  36. &0(4@,1i) += @_____mwss
  37. 0052: gap 0 target_address 0@ size 1@ value 2@ gap 0 0
  38. 0A3D: run_MWSS 1
  39. &0(4@,1i) = 0
  40. return

  41. :MemoryRead
  42. 4@ = -429864
  43. &0(4@,1i) =  0xA49960
  44. &0(4@,1i) += @_____mrmm
  45. 02EC: run_MRMM 1 read_address 0@ size 1@
  46. hex
  47. 3D0A008D0100000000 {store_to} 02 $MEMORY_PROOF_READ
  48. end
  49. &0(4@,1i) = 0
  50. return

  51. :_____vpsv
  52. hex
  53. 68 F4 3C A4 00 83 3D 84 3C A4 00 FF
  54. 75 08 FF 35 F4 3C A4 00 EB 06 FF 35
  55. 84 3C A4 00 FF 35 80 3C A4 00 FF 35
  56. 7C 3C A4 00 FF 15 2C 80 85 00 C3
  57. end

  58. :_____mwss
  59. hex
  60. 8B 15 7C 3C A4 00 8B 05 84 3C A4 00
  61. 8B 0D 80 3C A4 00 83 F9 01 75 03 88
  62. 02 C3 83 F9 02 75 04 66 89 02 C3 89
  63. 02 C3
  64. end

  65. :_____mrmm
  66. hex
  67. 31 C0 BA 78 3C A4 00 89 02 8B 0D 80
  68. 3C A4 00 8B 05 7C 3C A4 00 83 F9 01
  69. 75 05 8A 00 88 02 C3 83 F9 02 75 07
  70. 66 8B 00 66 89 02 C3 8B 00 89 02 C3              
  71. end

  72. { ---------内存访问线程结束----------------------------- }
复制代码


线程写得有点复杂了,实际情况下你可能需要更简短的,随意修改。
示例:减少重力5秒,然后恢复
  1. create_thread @MemoryProofRead  address 0x863984 size 4 VirtualProtect 1
  2. wait 0
  3. create_thread @MemoryProofWrite address 0x863984 size 4 value 0.002 VirtualProtect 1
  4. wait 5000
  5. create_thread @MemoryProofWrite address 0x863984 size 4 value $MEMORY_PROOF_READ VirtualProtect 1
复制代码

---------------------------------
---------------------------------
以下是多版本的内存访问线程
你可以传递两个地址,一个用于v1,一个用于v1.01。程序检测运行游戏的版本,并且能正确处理
参数:
  1. create_thread @MemoryProofMultiWrite/Read address for v1.0 (X:DWord) address for v1.01 (X:DWord) size (X:BYTE) value (X:DWORD) VirtualProtect (X:BOOL)
复制代码
  1. { -----------------------------------------------------------------
  2.   内存访问线程
  3.   多版本 :: 支持 GTA SA v1 and v1.01

  4.   要向内存地址写入定长数值,使用
  5.   create_thread @MemoryProofMultiWrite v1地址 X:DWord v1.01地址 X:DWord size X:BYTE value X:DWORD VirtualProtect X:BOOL

  6.   示例:
  7.   create_thread @MemoryProofMultiWrite address_V1 0xC0BC15 address_V2 0xC0E295 size 1 value 1
  8.   
  9.   要从内存地址读取定长数值,使用
  10.   create_thread @MemoryProofMultiRead v1地址 X:DWord v1.01地址 X:DWord size X:BYTE VirtualProtect X:BOOL
  11.   
  12.   结果存入 $MEMORY_PROOF_READ
  13.   
  14.   示例:
  15.   create_thread @MemoryProofMultiRead address_V1 0x863984 address_V2 0 size 4 VirtualProtect 1

  16.   可用的size值为 1-字节 2-字 4-双字
  17.   VirtualProtect参数为可选
  18. ----------------------------------------------------------------- }

  19. :MemoryProofMultiWrite
  20. gosub @_____gsvo01
  21. 7@ = @MemoryWrite
  22. 4@ == 1
  23. jf @_____novp

  24. :_____mpvp
  25. 0085: 8@ = 10@(9@,2i)
  26. 008A: &0(8@,1i) = 14@(9@,2i)
  27. 005E: &0(8@,1i) += 16@(9@,2i)
  28. 0052: gap 0 virtual_protect_at_address 0@(9@,2i) size 2@ newprotect 4 gap 0 0
  29. 0572: run_VPSV 1
  30. gosub 7@
  31. 0052: gap 0 virtual_protect_at_address 0@(9@,2i) size 2@ newprotect -1 gap 0 0
  32. 0572: run_VPSV 1
  33. &0(8@,1i) = 0
  34. end_thread

  35. :MemoryProofMultiRead
  36. gosub @_____gsvo01
  37. 7@ = @MemoryRead
  38. 3@ == 0
  39. jf @_____mpvp

  40. :_____novp
  41. gosub 7@
  42. end_thread

  43. :MemoryWrite
  44. 0085: 7@ = 12@(9@,2i)
  45. 008A: &0(7@,1i) = 14@(9@,2i)
  46. 005E: &0(7@,1i) += 18@(9@,2i)
  47. 0052: gap 0 target_address 0@(9@,2i) size 2@ value 3@ gap 0 0
  48. 0A3D: run_MWSS 1
  49. &0(7@,1i) = 0
  50. return

  51. :MemoryRead
  52. 0085: 7@ = 12@(9@,2i)
  53. 008A: &0(7@,1i) = 14@(9@,2i)
  54. 005E: &0(7@,1i) += 20@(9@,2i)
  55. 02EC: run_MRMM 1 read_address 0@(9@,2i) size 2@
  56. hex
  57. 3D0A008D0100000000 {store_to} 02 $MEMORY_PROOF_READ
  58. end
  59. &0(7@,1i) = 0
  60. return

  61. :_____gsvo01
  62. 8@ = -429566
  63. &0(8@,1i) == 4611680
  64. jf @_____gsvo012 // 1.0
  65. //    9@  = 0
  66.    10@ = -429863
  67.    12@ = -429864
  68.    14@ = 0xA49960
  69.    16@ = @_____vpsv
  70.    18@ = @_____mwss
  71.    20@ = @_____mrmm
  72. return
  73. :_____gsvo012    // 1.01
  74.    9@  = 1
  75.    11@ = -431117
  76.    13@ = -431118
  77.    15@ = 0xA4BFE0
  78.    17@ = @_____vpsv2
  79.    19@ = @_____mwss2
  80.    21@ = @_____mrmm2   
  81. return

  82. :_____vpsv2
  83. hex
  84. 68 74 63 A4 00 83 3D 04 63 A4 00 FF
  85. 75 08 FF 35 74 63 A4 00 EB 06 FF 35
  86. 04 63 A4 00 FF 35 FC 62 A4 00 FF 35
  87. F8 62 A4 00 FF 15 2C 90 85 00 C3
  88. end

  89. :_____vpsv
  90. hex
  91. 68 F4 3C A4 00 83 3D 84 3C A4 00 FF
  92. 75 08 FF 35 F4 3C A4 00 EB 06 FF 35
  93. 84 3C A4 00 FF 35 80 3C A4 00 FF 35
  94. 7C 3C A4 00 FF 15 2C 80 85 00 C3
  95. end

  96. :_____mwss2
  97. hex
  98. 8B 15 FC 62 A4 00 8B 05 04 63 A4 00
  99. 8B 0D 63 00 A4 00 EB 12
  100. end

  101. :_____mwss
  102. hex
  103. 8B 15 7C 3C A4 00 8B 05 84 3C A4 00
  104. 8B 0D 80 3C A4 00 83 F9 01 75 03 88
  105. 02 C3 83 F9 02 75 04 66 89 02 C3 89
  106. 02 C3
  107. end

  108. :_____mrmm2
  109. hex
  110. 31 C0 BA F8 62 A4 00 89 02 8B 0D 00
  111. 63 A4 00 8B 05 FC 62 A4 00 EB 15
  112. end

  113. :_____mrmm
  114. hex
  115. 31 C0 BA 78 3C A4 00 89 02 8B 0D 80
  116. 3C A4 00 8B 05 7C 3C A4 00 83 F9 01
  117. 75 05 8A 00 88 02 C3 83 F9 02 75 07
  118. 66 8B 00 66 89 02 C3 8B 00 89 02 C3              
  119. end
复制代码

评分

参与人数 1宝石 +4 金币 +44 收起 理由
Ycccccc + 4 + 44 赞一个!

查看全部评分

传奇 Legend

谷歌翻译坑爹人肉版(不是人肉饭)

Rank: 16

UID
83
宝石
140 粒
金币
573 枚
节操
30 斤
灵石
0 块
精力
32 ℃
 楼主| 发表于 2012-4-24 21:50:30 | 显示全部楼层
本帖最后由 kwanz 于 2012-4-25 21:05 编辑

引用 (ceedj @ Mar 5 2007, 05:25)
那段SB的asm代码是操作内存的标准写法吗?

是的,但仅用十六进制代码不足以操作内存。整个线程是这样处理的:一部分向内存加载参数,另一部分运行asm代码。你要复制其中一个版本的线程(仅v1或多版本,随你便)到main.scm的某处,然后调用004F来使用它。
另外,我知道用程序检测键盘上的任意键按下的方法,任意是指除了ESC和PrintScreen以外的全部按键。其他的按键都很容易处理,

从而打破了opcode00E1只能检测大约20个预定义按键的限制。
有兴趣么?
-------------------------------
-------------------------------
Y_Less的回复
SA作弊码处理器。我写的程序的确是一个作弊码处理器,是为了简单易用设计的。它用了一个稍作修改的内存操作程序,允许直接调用或create_thread调用,但它完全向下兼容。比如说,你像正常调线程一样调create_thread,并且首先把3@和4@初始化为1(4@是内部返回标记,替代end_thread用,3@是为了表示MemoryProofRead进程里少一个参数而设的),这是没问题的。
我不知道Seemann获取按键的方法,不过我只是从最新按键池0x969110中读取数据,这表示我们不需要时时保存最新的按键,但是这种应用还是有一定限制的。
要做一个新的作弊码,只需要
  1. create_thread @CHEAT_PROCESSOR @CHEAT_CODE_LABEL
复制代码

然后在:CHEAT_CODE_LABEL后定义你的作弊码
  1. :CHEAT_CODE_LABEL
  2. 0661: set_cheat_text "ACTIVATE"
  3. 03E5: show_text_box 'HELP101'
  4. return
复制代码

0661设定了启用作弊需要输入的字符串,处理器会自动读取和识别。剩下的部分就是作弊启用的动作,最后需要不带参的return,请不要使用end_thread什么的。
另外注意在作弊程序中绝对禁用7@ 8@和9@,否则必挂...
完整的作弊处理器(包含了修改过的单版本内存访问程序)
  1. { -----------------------------------------------------------------
  2. 作弊码处理器线程

  3. 要创建新线程只需要调用这个,并传入作弊程序的标签作为参数
  4. 然后设置作弊程序的第一行命令为

  5. 0661: set_cheat_text "<作弊码>"

  6. (类似name_thread,但用于注册作弊码)

  7. 字符串最长为255个字符,但必须全为大写字母
  8. 空格,数字等字符是不允许的

  9. 另外,作弊程序必须以return结束
  10. 绝对不能用end_thread,除非你想立即当机
  11. ----------------------------------------------------------------- }

  12. :CHEAT_PROCESSOR
  13. 0085: 7@ = 0@
  14. 0@ += 0xA49963 // SCM偏移 + 3
  15. 1@ = 1 // 大小
  16. 2@ = 0 // virtual protect
  17. 3@ = 1 // 用return, 千万别用end
  18. gosub @MemoryProofRead
  19. 008B: 8@ = $MEMORY_PROOF_READ
  20. 7@ += 4
  21. 005A: 7@ += 8@
  22. 9@ = 0

  23. {
  24.    5@现在存储字符串的长度
  25.    我真希望scm编程能用&&和>>
  26.    然后直接转换为asm。。。

  27.    用到的变量
  28.    0@-6@ - 保留作程序内部处理用
  29.    7@ - 作弊程序段的标签
  30.    8@ - 字符串长度
  31.    9@ - 字符表的原先双字节首地址
  32.    
  33.    其他说明:
  34.    9867536 - 字符表的十进制偏移量
  35. }

  36. :CP_MAIN_LOOP
  37. wait 1000
  38. 0@ = 0x969110
  39. 1@ = 4
  40. 2@ = 0
  41. 3@ = 1
  42. gosub @MemoryProofRead
  43. 803C: $MEMORY_PROOF_READ != 9@
  44. jf @CP_MAIN_LOOP  
  45. 008B: 9@ = $MEMORY_PROOF_READ
  46. 1@ = 1
  47. 11@ = 0

  48. :CP_STRCMP
  49. 802D: 11@ < 8@ // (!>=)
  50. jf @CP_SUCCESS
  51. 0@ = 0x969110
  52. 005A: 0@ += 11@
  53. gosub @MemoryProofRead
  54. 008B: 10@ = $MEMORY_PROOF_READ
  55. 0085: 0@ = 7@
  56. 0@ += 0xA4995F // SCM offset - 1
  57. 0062: 0@ -= 11@
  58. gosub @MemoryProofRead
  59. 003C: $MEMORY_PROOF_READ == 10@
  60. jf @CP_FAILURE
  61. 11@ += 1
  62. jump @CP_STRCMP

  63. :CP_SUCCESS
  64. gosub 7@
  65. // 显示作弊启用
  66. 03E5: text box "CHEAT1"
  67. 0@ = 0x96918C
  68. 1@ = 4
  69. 2@ = 1
  70. 3@ = 0
  71. 4@ = 1
  72. // 记录已作弊
  73. gosub @MemoryProofWrite
  74. 0@ = 0xBAA472
  75. gosub @MemoryProofWrite
  76. // 增加作弊计数
  77. 0@ = 0xB79044
  78. 2@ = 0
  79. 3@ = 1
  80. gosub @MemoryProofRead
  81. 008B: 2@ = $MEMORY_PROOF_READ
  82. 2@ += 1
  83. 3@ = 0
  84. gosub @MemoryProofWrite

  85. :CP_FAILURE
  86. jump @CP_MAIN_LOOP

  87. { -----------------------------------------------------------------
  88. Memory Access Thread
  89. 内存访问线程  请参考1楼的单版本内存访问线程

  90. to write a value with the specified length to the memory address  use
  91. create_thread @MemoryProofWrite address X:DWord size X:BYTE value X:DWORD VirtualProtect X:BOOL

  92. to read a value with the specified length by the memory address  use
  93. create_thread @MemoryProofRead address X:DWord size X:BYTE VirtualProtect X:BOOL
  94. The read value will be stored to the variable $MEMORY_PROOF_READ

  95. The possible Size values: 1 (byte), 2 (word), 4 (dword)
  96. The VirtualProtect parameter is optional
  97. ----------------------------------------------------------------- }

  98. :MemoryProofWrite
  99. 5@ = @MemoryWrite
  100. 3@ == 1
  101. jf @_____novp

  102. :_____mpvp
  103. 6@ = -429863
  104. &0(6@,1i) =  0xA49960
  105. &0(6@,1i) += @_____vpsv
  106. 0052: gap 0 virtual_protect_at_address 0@ size 1@ newprotect 4 gap 0 0
  107. 0572: run_VPSV 1
  108. gosub 5@
  109. 0052: gap 0 virtual_protect_at_address 0@ size 1@ newprotect -1 gap 0 0
  110. 0572: run_VPSV 1
  111. &0(6@,1i) = 0
  112. 4@ == 0
  113. jf @MemoryReturn
  114. end_thread

  115. :MemoryProofRead
  116. 5@ = @MemoryRead
  117. 0085: 4@ = 3@
  118. 2@ == 0
  119. jf @_____mpvp

  120. :_____novp
  121. gosub 5@
  122. 4@ == 0
  123. jf @MemoryReturn
  124. end_thread

  125. :MemoryWrite
  126. 5@ = -429864
  127. &0(5@,1i) =  0xA49960
  128. &0(5@,1i) += @_____mwss
  129. 0052: gap 0 target_address 0@ size 1@ value 2@ gap 0 0
  130. 0A3D: run_MWSS 1
  131. &0(5@,1i) = 0
  132. return

  133. :MemoryRead
  134. 5@ = -429864
  135. &0(5@,1i) =  0xA49960
  136. &0(5@,1i) += @_____mrmm
  137. 02EC: run_MRMM 1 read_address 0@ size 1@
  138. hex
  139. 3D0A008D0100000000 {store_to} 02 $MEMORY_PROOF_READ
  140. end
  141. &0(5@,1i) = 0
  142. return

  143. :MemoryReturn
  144. 0051: return

  145. :_____vpsv
  146. hex
  147. 68 F4 3C A4 00 83 3D 84 3C A4 00 FF
  148. 75 08 FF 35 F4 3C A4 00 EB 06 FF 35
  149. 84 3C A4 00 FF 35 80 3C A4 00 FF 35
  150. 7C 3C A4 00 FF 15 2C 80 85 00 C3
  151. end

  152. :_____mwss
  153. hex
  154. 8B 15 7C 3C A4 00 8B 05 84 3C A4 00
  155. 8B 0D 80 3C A4 00 83 F9 01 75 03 88
  156. 02 C3 83 F9 02 75 04 66 89 02 C3 89
  157. 02 C3
  158. end

  159. :_____mrmm
  160. hex
  161. 31 C0 BA 78 3C A4 00 89 02 8B 0D 80
  162. 3C A4 00 8B 05 7C 3C A4 00 83 F9 01
  163. 75 05 8A 00 88 02 C3 83 F9 02 75 07
  164. 66 8B 00 66 89 02 C3 8B 00 89 02 C3              
  165. end

  166. { ---------end of memory access thread----------------------------- }
复制代码

还有一些没什么意思的“作弊”的例子
  1. create_thread @CHEAT_PROCESSOR @HEALTH_CHEAT
  2. create_thread @CHEAT_PROCESSOR @CAR_CHEAT
复制代码
  1. :HEALTH_CHEAT
  2. 0661: set_cheat_text "HELLO"
  3. 0256:   player $PLAYER_CHAR defined
  4. jf @HEALTH_CHEAT_END
  5. 03E5: show_text_box 'HELP101'  // 弹出提示:关于声望的帮助信息
  6. // Respect can be earned be passing certain missions, killing rival gangs members, gaining territory and tagging.
  7. 08AF: set_actor $PLAYER_ACTOR max_health_to 255
  8. 0223: set_actor $PLAYER_ACTOR health_to 255
  9. 00A0: store_actor $PLAYER_ACTOR position_to 10@ 11@ 12@
  10. 12@ += 100.0
  11. 00A1: put_actor $PLAYER_ACTOR at 10@ 11@ 12@

  12. :HEALTH_CHEAT_END
  13. return

  14. // 刷车
  15. :CAR_CHEAT
  16. 0661: sat_cheat_text "BURN"
  17. 0256:   player $PLAYER_CHAR defined
  18. jf @CAR_CHEAT_END
  19. 0247: load_model #INFERNUS
  20. 038B: load_requested_models

  21. :CAR_CHEAT_LOAD
  22. wait 0
  23. 0248:   model #INFERNUS available
  24. jf @CAR_CHEAT_LOAD
  25. 00A0: store_actor $PLAYER_ACTOR position_to 10@ 11@ 12@
  26. 10@ += 5.0
  27. 00A5: 0@ = create_car #INFERNUS 10@ 11@ 12@
  28. 0249: release_model #INFERNUS

  29. :CAR_CHEAT_END
  30. return;
复制代码


再次提醒:在代码里绝对不可以使用7@ 8@和9@!!

附,Seemann,读过你的代码之后我发现那个缺少的if是一个很给力的技巧,赞。
修改内容:向success标签中添加了一些内容,它们现在就像真的作弊一样,会弹出提示,并且在存盘的时候吐槽。。
修改2:你有没有想过修改程序,用写好的asm程序替换没有使用的opcode,那就不用使用现有的那几个了。如果这样改好的话,我们只需要传入另外一个opcode的指针指向所需的数据,操作内存可以简化为:
0D1E: write_memory 0x01234567 size 1 data 0@ protect 1
01DF: 0@ = read_memory $SOME_LABEL size 4 protect 0
(0D1E是我举的一个未知opcode的例子,不解释)
伪造一个合法的opcode调用格式应该不难,我一开始就想这么做的。另外,根据exe的实际情况作调整也不难,因为用到各种来回调用的地址都是我们熟知的。
-------------------------------
-------------------------------
引用 (ceedj @ Mar 5 2007, 21:11)
这帖和之前Y_Less讨论的编写自己的作弊有什么关系么?

没有什么关系,其实我在第一帖已经发了我写作弊的方法了。我只是举例说明了用内存地址检测按键的方法。我稍后会发这个。
自定义按键

引用 (Y_Less @ Mar 6 2007, 02:27)
你也可以考虑从文本条目和伪造GXT入手,不过我某天研究SA的伪造GXT时没有什么头绪,VC里很容易做到,SA没那么简单

额,那个我做过。我写过一个子过程,用于运行时把GXT条目修改为自己的文本。
我稍后会发那个。

引用 (Y_Less @ Mar 6 2007, 06:26)
你有没有想过修改程序,用写好的asm程序替换没有使用的opcode,那就不用使用现有的那几个了。

如果你的意思是通过任务脚本做到直接调用(就像本帖中讨论的),应该说,的确有办法来伪造opcode句柄,并且在[0A8C...0AEF]范围内增加大量的伪opcode来完成我们想做的事情(0D1E不错,可惜它就不可以)。其他的opcode只能通过修改exe来改变,不能运行时修改。
不过,我们这样又能得到什么呢?这使我们迫使游戏在每次加载时都使用伪造的opcode加载句柄。那用Xieon的补丁不也能达到添加opcode的目的吗。
顺便说,我没有修改MemoryProof中的任何opcode,0052和02EC都是NOP,我用它们只是用来向内存加载参数(十六进制代码段读取数组OpcodeParameters[32],地址已知)
另外在用到MemoryProof时,若大小等于4,大小参数不是必须的。也就是说默认为双字。如果你要进行无VP访问,你同样没法指定大小
  1. create_thread @MemoryProofRead address 100
复制代码

它从地址100读取4个字节。
别忘了在004F后添加一条wait命令,使新线程立即创建。
-------------------------------
-------------------------------
你不能向访问线程传入字符串参数
  1. create_thread @CLV thread_name 'vchng'
复制代码

这句是不行滴。你可以传递字符串的索引,或者字符串储存位置的标签
  1. :mystring
  2. hex
  3. "vchng" 00
  4. end
复制代码

-------------------------------
-------------------------------
这是获取最近的随机人物/车辆/物品的程序
原作者PLPynton
但那程序有bug,在我这从未好用过。我修正并改进了它
此程序接受4个参数进行搜索:搜索点的xyz坐标和搜索半径
如果搜索点附近有人/车/物品什么的,程序把它的句柄存储在9@
否则它返回-1。程序忽略玩家和他的车辆
这段程序是用传统的逐行句式写的,所以MB也可用(如果有人还用的话,呵呵)
只有标签和&需要转换(@->JJ &->$)
  1. {
  2.    物品截取程序
  3.       
  4.    by PLPynton and Seemann   

  5.    参数:
  6.    0@ - 坐标X
  7.    1@ - 坐标Y
  8.    2@ - 坐标Z
  9.    3@ - 搜索半径

  10.    占用变量4@..20@
  11.    
  12.    9@ = 结果 (若搜索失败返回-1)
  13.          
  14. }
  15. :AIC_GETACTOR
  16. 0006: 16@ = 305868
  17. 0006: 17@ = @AIC_TESTACTORHANDLE
  18. 0006: 20@ = 1988
  19. 0002: jump @AIC_STARTSEARCH

  20. :AIC_GETVEHICLE
  21. 0006: 16@ = 305869
  22. 0006: 17@ = @AIC_TESTVEHICLEHANDLE
  23. 0006: 18@ = -1
  24. 0006: 20@ = 2584
  25. 0256:   player $PLAYER_CHAR defined
  26. 004D: jump_if_false @AIC_STARTSEARCH
  27. 00DF:   actor $PLAYER_ACTOR driving
  28. 004D: jump_if_false @AIC_STARTSEARCH
  29. 03C0: 18@ = actor $PLAYER_ACTOR car
  30. 0002: jump @AIC_STARTSEARCH

  31. :AIC_GETOBJECT
  32. 0006: 16@ = 305871
  33. 0006: 17@ = @AIC_TESTOBJECTHANDLE
  34. 0006: 20@ = 412

  35. :AIC_STARTSEARCH
  36. 0085: 4@ = 16@ // (int)
  37. 008B: 4@ = &0(4@,1i) // (int)
  38. 0085: 19@ = 4@ // (int)
  39. 000E: 19@ -= 10787160
  40. 0016: 19@ /= 4
  41. 008B: 19@ = &0(19@,1i) // (int)
  42. 000E: 4@ -= 10787168
  43. 0016: 4@ /= 4
  44. 008B: 4@ = &0(4@,1i) // (int)
  45. 0006: 9@ = -1
  46. 0085: 11@ = 4@ // (int)
  47. 0006: 10@ = 0

  48. :AIC_SEARCHLOOPMAIN
  49. 000A: 4@ += 20
  50. 0050: gosub @AIC_READ4B
  51. 0085: 4@ = 5@ // (int)
  52. 8039:   not  4@ == 0
  53. 004D: jump_if_false @AIC_SEARCHLOOPNEXT
  54. 000A: 4@ += 48
  55. 0050: gosub @AIC_READ4B
  56. 0085: 12@ = 5@ // (int)
  57. 000A: 4@ += 4
  58. 0050: gosub @AIC_READ4B
  59. 0085: 13@ = 5@ // (int)
  60. 000A: 4@ += 4
  61. 0050: gosub @AIC_READ4B
  62. 0085: 14@ = 5@ // (int)
  63. 050A: 15@ = distance_between_XYZ 0@ 1@ 2@ and_XYZ 12@ 13@ 14@
  64. 0025:   3@ > 15@ // (float)
  65. 004D: jump_if_false @AIC_SEARCHLOOPNEXT
  66. 0085: 9@ = 10@ // (int)
  67. 0085: 4@ = 16@ // (int)
  68. 008B: 4@ = &0(4@,1i) // (int)
  69. 000A: 4@ += 4
  70. 0050: gosub @AIC_READ4B
  71. 0085: 4@ = 5@ // (int)
  72. 005A: 4@ += 9@ // (int)
  73. 0050: gosub @AIC_READ1B
  74. 0012: 9@ *= 256
  75. 005A: 9@ += 5@ // (int)
  76. 0002: jump 17@

  77. :AIC_TESTVEHICLEHANDLE
  78. 056E:   is 9@ valid_vehicle_handle
  79. 004D: jump_if_false @AIC_NOHANDLE
  80. 00D6: if or
  81. 003B:   18@ == 9@ // (int)
  82. 0119:   car 9@ wrecked
  83. 004D: jump_if_false @AIC_NEWSEARCHRADIUS
  84. 0002: jump @AIC_NOHANDLE

  85. :AIC_TESTACTORHANDLE
  86. 056D:   is 9@ valid_actor_handle
  87. 004D: jump_if_false @AIC_NOHANDLE
  88. 00D6: if or
  89. 003C:   $PLAYER_ACTOR == 9@ // (int)
  90. 0118:   actor 9@ dead
  91. 004D: jump_if_false @AIC_NEWSEARCHRADIUS
  92. 0002: jump @AIC_NOHANDLE

  93. :AIC_TESTOBJECTHANDLE
  94. //0001: wait 0
  95. 83CA:   not object 9@ exists
  96. 004D: jump_if_false @AIC_NEWSEARCHRADIUS

  97. :AIC_NOHANDLE
  98. 0006: 9@ = -1
  99. 0002: jump @AIC_SEARCHLOOPNEXT

  100. :AIC_NEWSEARCHRADIUS
  101. {  
  102.   一旦搜索成功就
  103.   调0051: return退出循环
  104.   不管是否最近
  105. }

  106. // 在这里写上额外的筛选条件

  107. {
  108. 21@ 包含了排除对象
  109. 比如说前一次搜索的结果
  110. }
  111. //803B:   21@ <> 9@
  112. //004D: jump_if_false @AIC_NOHANDLE

  113.    
  114. 0085: 3@ = 15@

  115. :AIC_SEARCHLOOPNEXT
  116. 005A: 11@ += 20@ // (int)
  117. 0085: 4@ = 11@ // (int)
  118. 000A: 10@ += 1
  119. 002D:   10@ >= 19@ // (int)
  120. 004D: jump_if_false @AIC_SEARCHLOOPMAIN
  121. 0051: return

  122. // ---------------------------------
  123. :AIC_READ1B
  124. 0085: 6@ = 4@ // (int)
  125. 0085: 8@ = 4@ // (int)
  126. 0016: 4@ /= 4
  127. 0012: 4@ *= 4
  128. 0062: 8@ -= 4@ // (int)
  129. 0012: 8@ *= 8
  130. 000E: 4@ -= 10787168
  131. 0016: 4@ /= 4
  132. 008B: 7@ = &0(4@,1i) // (int)
  133. 0085: 4@ = 6@ // (int)
  134. 0006: 5@ = 0
  135. 0006: 6@ = 0

  136. :AIC_READ1B_LOOP
  137. 08B9:   test 7@ bit 8@
  138. 004D: jump_if_false @AIC_READ1B_NEXTBIT
  139. 08BF: set 5@ bit 6@

  140. :AIC_READ1B_NEXTBIT
  141. 000A: 6@ += 1
  142. 000A: 8@ += 1
  143. 0039:   6@ == 8
  144. 004D: jump_if_false @AIC_READ1B_LOOP
  145. 0051: return

  146. :AIC_READ4B
  147. 0085: 6@ = 4@ // (int)
  148. 000E: 4@ -= 10787168
  149. 0016: 4@ /= 4
  150. 008B: 5@ = &0(4@,1i) // (int)
  151. 0085: 4@ = 6@ // (int)
  152. 0051: return
复制代码

------------------------
示例
  1. while true
  2. wait 100

  3. 00A0: store_actor $PLAYER_ACTOR position_to 0@ 1@ 2@
  4. 3@ = 15.0

  5. gosub @AIC_GETVEHICLE
  6. if
  7.    9@ <> -1
  8. then
  9.     020B: explode_car 9@ // versionA
  10.     // 0085: 21@ = 9@ // 在下次搜索中排除此对象,参考代码的筛选部分
  11. end
  12. end
复制代码

-------------------------------
-------------------------------
-------------------------------------------------
实现作弊码的最简单方法
-------------------------------------------------
编写作弊(自定义按键)似乎是SA编程中的常见问题。人们都在问如何实现。尽管已有检测按键的方法(第一帖就有),整套方法还是很复杂的,需要内存结构知识。
先前我写过一段简化编写作弊的程序。你只需要把作弊码存入一个变量,调scm函数并检查结果(true/false)就行了。
首先,阅读这一帖理解以下如何向scm函数或线程传递字符串。
下面的程序检测自定义作弊码
  1. :TestCheat
  2. if
  3.    0AA9:    is_game_version_original
  4. then
  5.    10@ = 0x969110 // keypresses buffer 1.0
  6.    11@ = 0xA48960 // mission locals 1.0
  7. else
  8.    10@ = 0x96B790 // keypresses buffer 1.01
  9.    11@ = 0xA4AFE0 // mission locals 1.01
  10. end
  11. // get 0@'s offset
  12. 0A9F: 4@ = current_thread_pointer
  13. 0A8E: 5@ = 4@ + 0xDC // mission Flag
  14. 0A8D: 5@ = read_memory 5@ size 1 virtual_protect 0
  15. if
  16.    5@ == 1
  17. then
  18.    0085: 4@ = 11@
  19. else
  20.    4@ += 0x3C
  21. end
  22. // get cheat string length
  23. 6@ = 0
  24. while true
  25.    0A8D: 5@ = read_memory 4@ size 1 virtual_protect 0
  26.    if and
  27.        5@ > 0
  28.        6@ < 16
  29.    then
  30.        inc(4@)
  31.        inc(6@)        
  32.    else
  33.        Break
  34.    end
  35. end
  36. 0085: 8@ = 10@
  37. while 6@ > 0
  38.    dec(4@)      
  39.    dec(6@)
  40.    0A8D: 5@ = read_memory 4@ size 1 virtual_protect 0 // last cheat char
  41.    // lowercase to uppercase (a->A)
  42.    if
  43.        5@ > 90
  44.    then
  45.        5@ -= 32
  46.    end
  47.    0A8D: 7@ = read_memory 8@ size 1 virtual_protect 0 // last pressed key
  48.    inc(8@)   
  49.    if
  50.        803B:   5@ <> 7@ // (int)
  51.    then
  52.        059A: return_false
  53.        0AB2: ret 0
  54.    end
  55. end
  56. 0A8C: write_memory 10@ size 1 value 0 virtual_protect 0
  57. 0485: return_true
  58. 0AB2: ret 0
复制代码

把这段代码原样复制到你的程序里,无论是任务还是脚本都无所谓
你也可以把代码存入外部文件,然后用$INCLUDE(或$I)指令把它包含到你的程序里。比如说,下载TestCheat.inc,把它复制到你编写的程序目录下,然后在程序开头写上
  1. $I TestCheat.inc}
复制代码

编译的时候,编译器会自动包含外部文件。
那么怎么用这段程序来实现自己的作弊码呢?超简单的。
先把作弊码(比如NOCOPS)存入变量
  1. 0@s = 'nocops'
复制代码

然后调用scm函数TestCheat,把作弊码传入 原理参考上面发的链接
  1. 0AB1: call_scm_func @TestCheat 2 0@ 1@
复制代码

如果函数返回true,表示玩家在游戏中输入了作弊码,所以,你需要写成循环
CLEO的例子
  1. {$CLEO}
  2. 0000:
  3. while true
  4.    wait 250 ms
  5.    0@s = 'nocops'
  6.    if
  7.        0AB1: call_scm_func @TestCheat 2 0@ 1@
  8.    then
  9.        0110: clear_player $PLAYER_CHAR wanted_level
  10.    end
  11. end

  12. {$I TestCheat.inc}
复制代码

当你输入nocops的时候,函数返回true,并且调用clear_wanted_level

如果你的作弊码长于7个字符,请使用长字符串
  1. {$CLEO}
  2. 0000:
  3. while true
  4.    wait 250 ms
  5.    22@v = "leavemealone"
  6.    if
  7.        0AB1: call_scm_func @TestCheat 4 22@ 23@ 24@ 25@
  8.    then
  9.        0110: clear_player $PLAYER_CHAR wanted_level
  10.    end
  11. end   

  12. {$I TestCheat.inc}
复制代码



1. 作弊码大小写无关。nocops,NoCoPs,nOCOPs效果都是一样的
2. 作弊码长度可以为1至16个字符。

管理员

不作死就不会死

Rank: 64Rank: 64Rank: 64Rank: 64

宝石
22 粒
金币
11446 枚
节操
155 斤
灵石
0 块
精力
243 ℃
发表于 2012-4-24 21:52:41 | 显示全部楼层
现在可以抢了咩?~

点评

可以了=v=  发表于 2012-4-24 21:54

评分

参与人数 1金币 +6 收起 理由
root + 6 抢楼奖励

查看全部评分

传奇 Legend

虚拟世界,属于你的世界

Rank: 16

UID
4
宝石
154 粒
金币
5594 枚
节操
458 斤
灵石
0 块
精力
509 ℃
QQ
发表于 2012-4-24 22:15:33 | 显示全部楼层
哈 这个教程以前看过

评分

参与人数 1金币 +2 收起 理由
root + 2 抢楼奖励

查看全部评分

璞玉 Potential

亞麻色頭髮的少女

Rank: 4

UID
402
宝石
14 粒
金币
296 枚
节操
10 斤
灵石
0 块
精力
11 ℃
发表于 2012-4-25 08:50:52 来自手机 | 显示全部楼层
各種不懂@@
慢慢看吧…囧
BASS Line

达人 Expert

来吧,来细数你的罪恶

Rank: 9Rank: 9Rank: 9

UID
477
宝石
75 粒
金币
2578 枚
节操
128 斤
灵石
0 块
精力
78 ℃
发表于 2012-4-30 14:56:43 | 显示全部楼层
这、、让我情何以堪

翘楚 Outstanding

Rank: 6Rank: 6Rank: 6

UID
168
宝石
41 粒
金币
315 枚
节操
37 斤
灵石
0 块
精力
15 ℃

灼眼的夏娜黑岩之炎

发表于 2012-5-11 20:25:53 | 显示全部楼层
到底怎么看明白那些代码的?

欢迎入群:235388838

传奇 Legend

Rank: 16

UID
183
宝石
125 粒
金币
1105 枚
节操
62 斤
灵石
0 块
精力
65 ℃
发表于 2012-5-15 17:21:55 | 显示全部楼层
囧死,完全看不明白
http://imgsrc.baidu.com/forum/pic/item/c532d188661d4ebba4c272ad.jpg

本版积分规则

    切换繁體
    Archiver|手机版|小黑屋|

GMT+8, 2024-5-3 17:47 , Processed in 0.159420 second(s), 120 queries .

沪ICP备2021020632号-1

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