最近想学习一下 CE,刚好看见游戏库里装了 Kingdom Rush 就拿它来研究吧。这里写的东西,需要一些 Cheat Engine 的基础,可以看看教程。
这里主要是看写的注释,来理解脚本。(如果什么都不想看,可以直接复制粘贴 CE 自动汇编(AA)脚本)
我学习的链接:
参考链接:
我对一些用到的技术的简单理解:
数据在内存中的存储结构:

此脚本用到的 CE 汇编指令:
| 命令例子 | 功能 |
|---|---|
| mov ebx,0000FFFF | Move,寄存器直接赋值 |
| mov ebx,eax | Move,将右边直接给左边 |
| mov ebx,[eax] | Move,将右边所指的值给左边。[ ]代表括号内的是指针,操作时,操作其指向的内存值 |
| cmp ebx,eax | Compare,比较两寄存器值,若相等则 ZF 位 置 1(左减右) |
je <label> |
Jump if Equal, ZF = 1 时跳转,可跳转至标记段代码 |
jne <label> |
Jump if Not Equal, ZF = 0 时跳转,可跳转至标记段代码 |
jmp <label> |
Jump |
此脚本用到的 CE 自动汇编函数:(Auto Assembler)
| 函数 | 参数 | 作用 |
|---|---|---|
| alloc | alloc(SymbolName, Size, AllocateNearThisAddress OPTIONAL) | Allocates a memory block of Size bytes and defines the SymbolName in the script, pointing to the beginning of the allocated memory block. |
| dealloc | dealloc(SymbolName) | Deallocates a block of memory allocated with alloc. |
| label | label(LabelName) | Enables the word 'LabelName' to be used as a symbol. |
| aobScanModule | aobScanModule(SymbolName, ModuleName, AOBString) | Scans the memory used by the module ModuleName for a specific byte pattern defined by AOBString and sets the resulting address to the symbol SymbolName. |
| registerSymbol | registerSymbol(SymbolName) | Adds a symbol to the user-defined symbol list so cheat tables and the memory browser can use that name instead of an address. |
| unregisterSymbol | unregisterSymbol(SymbolName) | Removes a symbol from the user-defined symbol list. No error will occur if the symbol doesn't exist. |
此脚本用到的 CE 汇编语言数据类型:
| 类型 | 占用空间 |
|---|---|
| Bit(整型) | 1 位 |
| Byte(整型) | 8 位(字节) |
| 2 byte(整型) | WORD(字) |
| 4 Bytes(整型) | DWORD(双字) |
| 8 Bytes(整型) | QWORD(四字) |
| Float(单浮点) | DWORD(双字) |
| Double(双浮点) | QWORD(四字) |
| String(字符串) | 任意长度 |
| Array of bytes(AOB) | 任意长度 |
CE 常用寄存器:
The x86 architecture has 8 General-Purpose Registers (GPR)
x86 架构有 8 个通用寄存器(32 位系统)
"E" (for "extended"), 32 bits.
General-Purpose Registers:EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI
学过 CE 的,通过注释,应该能看懂
{ Game : Kingdom Rush.exe
Version:
Date : 2022-06-19
Author : Tsanfer
更改金钱、生命、星
(使用 AOB 和人造指针)
}
[ENABLE]
aobscanmodule(INJECT,lua51.dll,8B 29 8B 49 04 89 2C C2) // AOB 匹配入口
alloc(newmem,1024,INJECT) // 分配 1024 字节个内存,用于代码注入
alloc(man_pointer,24) // 动态分配内存,用于存放3个人造指针
// 注册全局符号
registersymbol(INJECT)
registersymbol(man_pointer) // 人造指针
// 声明标号
label(original_code)
label(return)
label(restore_reg)
label(check_is_gold)
label(check_is_gold_END)
label(check_is_live)
label(check_is_live_END)
label(check_is_star)
label(check_is_star_END)
// 数据存储结构:
// 偏移 数据 变量类型
// 0 具体值 Double
// 8 指向值所属的公共结构 指针
// └─> 0
// 10 此公共结构的名称 字符串
// 程序执行顺序:(汇编代码如没有跳转,默认从上往下执行)
// INJECT -> newmem -> check_is_gold -> check_is_live
// -> check_is_star -> reset_reg -> original_code -> return
// 注入代码段
newmem:
pushfd // 保存所有标志位
push eax // eax 压栈保存,为后续操作腾出一个寄存器
mov eax,[ecx+08] // 将当前值所属的公共类型所在的地址,给 eax
// 判断此值的类型是否为金钱(player_gold)
check_is_gold:
cmp dword ptr [eax+10],'play' // 内存双字比较
jne check_is_gold_END // 如不匹配,则停止后续比较,跳到此比较的结尾
cmp dword ptr [eax+14],'er_g'
jne check_is_gold_END
cmp word ptr [eax+18],'ol' // 内存字比较
jne check_is_gold_END
cmp byte ptr [eax+1A],'d' // 内存字节比较
jne check_is_gold_END
mov [man_pointer],ecx // 匹配成功,将指向此值的指针保存在申请的内存中(制作人造指针)
check_is_gold_END:
// 判断此值的类型是否为生命(lives)
check_is_live:
cmp dword ptr [eax+10],'live'
jne check_is_live_END
cmp byte ptr [eax+14],'s'
jne check_is_gold_END
mov [man_pointer+8],ecx // 将指针保存在第二个内存位置
// (64位系统的指针大小为 64 bit,每个内存地址大小为 8bit,则需要平移8个内存地址,8x8=64)
check_is_live_END:
// 判断此值的类型是否为升级用的星(total_stars)
check_is_star:
cmp dword ptr [eax+10],'tota'
jne check_is_star_END
cmp dword ptr [eax+14],'l_st'
jne check_is_star_END
cmp word ptr [eax+18],'ar'
jne check_is_star_END
cmp byte ptr [eax+1A],'s'
jne check_is_star_END
mov [man_pointer+10],ecx
check_is_star_END:
// 恢复临时使用的寄存器的值
restore_reg:
pop eax
popfd // 还原所有标志位
jmp original_code
// 原始代码
original_code:
mov ebp,[ecx]
mov ecx,[ecx+04]
jmp return // 跳到返回
// 程序入口
INJECT:
jmp newmem
return: // 返回
[DISABLE]
// 还原代码
INJECT:
db 8B 29 8B 49 04
// 注销全局符号
unregistersymbol(INJECT)
unregistersymbol(man_pointer)
// 释放内存
dealloc(newmem)
dealloc(man_pointer)
{
// ORIGINAL CODE - INJECTION POINT: lua51.dll+1BDA
lua51.dll+1BBC: 23 48 08 - and ecx,[eax+08]
lua51.dll+1BBF: 6B C9 18 - imul ecx,ecx,18
lua51.dll+1BC2: 03 4D 14 - add ecx,[ebp+14]
lua51.dll+1BC5: 83 79 0C FB - cmp dword ptr [ecx+0C],-05
lua51.dll+1BC9: 75 3A - jne lua51.dll+1C05
lua51.dll+1BCB: 39 41 08 - cmp [ecx+08],eax
lua51.dll+1BCE: 75 35 - jne lua51.dll+1C05
lua51.dll+1BD0: 83 79 04 FF - cmp dword ptr [ecx+04],-01
lua51.dll+1BD4: 74 36 - je lua51.dll+1C0C
lua51.dll+1BD6: 0F B6 46 FD - movzx eax,byte ptr [esi-03]
// ---------- INJECTING HERE ----------
lua51.dll+1BDA: 8B 29 - mov ebp,[ecx]
lua51.dll+1BDC: 8B 49 04 - mov ecx,[ecx+04]
// ---------- DONE INJECTING ----------
lua51.dll+1BDF: 89 2C C2 - mov [edx+eax*8],ebp
lua51.dll+1BE2: 89 4C C2 04 - mov [edx+eax*8+04],ecx
lua51.dll+1BE6: 8B 06 - mov eax,[esi]
lua51.dll+1BE8: 0F B6 CC - movzx ecx,ah
lua51.dll+1BEB: 0F B6 E8 - movzx ebp,al
lua51.dll+1BEE: 83 C6 04 - add esi,04
lua51.dll+1BF1: C1 E8 10 - shr eax,10
lua51.dll+1BF4: FF 24 AB - jmp dword ptr [ebx+ebp*4]
lua51.dll+1BF7: 0F B6 46 FD - movzx eax,byte ptr [esi-03]
}
然后再手动添加 3 个人造指针的地址(man_pointer 和 man_pointer+8 和 man_pointer+10),就行了
Cheat Engine 使用界面:

本文由Tsanfer's Blog 发布!
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。