cheat engine 内联 C 的使用
cheat engine 内联 C 的使用

为群友提供的文字版备忘录,适用 7.3.0.11 以上版本,使用 Tiny C 编译器。

§简介

内联 C 是 7.3 版本增加的新功能,包括 $CCODE$C 代码块。

性能排行:汇编($ASM) > 内联 C($CCODE/$C) > 内联 LUA($LUACODE) > 外部 LUA($LUA)。

内联 C 被编译到目标进程空间中,由于实现方式的原因,有一定的性能损失,相对适合在频繁调用的方法里面,比如 GameLoop.Update()

内联 LUA 无依赖的部分被编译到目标进程空间中,有依赖的部分仍然在 CE 中(lua-server),由于实现方式的原因,有一定的性能损失,饥荒这个特例有巨额开销,尽可能不要放在频繁调用的方法里面,比如 GameLoop.Update()

外部LUA 基于 CE(lua-server)运行,如果产生数据交互则带来较大开销,一般用于检查、搜索、备份内存使用。

§函数定义

c/* 
 * $C 中定义的函数和变量可直接在 $CCODE 中使用,且是全局注册符号(即在 CE 的地址列表中也可直接使用)
 * 使用 C 函数之前,可以先在内存浏览器中检查是否已导出,否则需要在 $C 中手动导出
 * 建议使用到的函数都手动导出,避免被重定向到非目标重载,我自己被这个坑过几次了
 */
/*{$C}*/
void changeValue(int currentValue,int newValue){ currentValue=newValue; }
int getValue(int currentValue){return currentValue+100;}
int setMaxValue(unsigned int *ptrObject){ *(int *)(ptrObject+0x20)=100; }
extern void MessageBoxA(int,const char *,const char *,int);
extern void _vsnprintf_s(char *,int,int,const char *,...va_list)
extern void OutputDebugStringA(const char *)
/*{$ASM}*/

§变量定义

c/*
 * $C 中定义的函数和变量可直接在 $CCODE 中使用,且是全局注册符号(即在 CE 的地址列表中也可直接使用)
 * healthAddress 友军生命值地址
 * godModeSwitch 无敌开关(boolean)
 * *ptrStruct 结构指针
 * (unsigned int) 与 (unsigned int *) 的区别是,前者在 CE 中使用 [healthAddress] 取得数值,而后者使用 [[moneyAddress]+0]
 */
/*{$C}*/
unsigned int healthAddress=0x00;
unsigned char godModeSwitch=0x00;
unsigned int *moneyAddress=0x00;
/*{$ASM}*/

§引用寄存器

c/*
 * 此代码等同汇编
 * movaps [align16_memory_address],xmm0
 * mov esi,[align16_memory_address]
 * mov [eax],esi
 * mov esi,[align16_memory_address+04]
 * mov [ebx],esi
*/
/*{$CCODE refEAX=eax refEBX=ebx refXMM0=xmm0}*/
  *(float *)refEAX=refXMM0.0F;
  *(float *)refEBX=refXMM0.1F;
/*{$ASM}*/

/*
 * 此代码等同汇编
 * movaps [align16_memory_address],xmm0
 * mov rsi,[align16_memory_address]
 * mov [rax],rsi
 * mov rsi,[align16_memory_address+08]
 * mov [rbx],rsi
*/
/*{$CCODE refRAX=rax refRBX=rbx refXMM0=xmm0}*/
  *(double *)refRAX=refXMM0.0D;
  *(double *)refRBX=refXMM0.1D;
/*{$ASM}*/

§逻辑调试

c/*{$CCODE refEAX=eax}*/
  unsigned int * ptrEAX = refEAX;
  char * ptrStringA1 = (char *)malloc(32);
  int v1 = _vsnprintf_s(ptrStringA1,32,31,"ptrEAX => 0x%x",ptrEAX);
  OutputDebugStringA(ptrStringA1);
  free(ptrStringA1);
/*{$ASM}*/

§综合使用

c// eax 生命值地址
// ebp-08 人物指针
// xmm0 [旧生命值,新生命值,最大生命值,无用数据]
// xmm0.2F 最大生命值
/*{$CCODE refEAX=eax refEBP=ebp refXMM0=xmm0 refXMM0S2=xmm0.2F}*/
unsigned int *ptrEntityStruct=(unsigned int *)(refEBP-0x08);
unsigned char *ptrEntityType=(unsigned char *)(*ptrEntityStruct-0x41);
unsigned int *ptrHealth=(unsigned int *)refEAX;
switch(*ptrEntityType){
  // 友军
  case 0x01:
    *ptrStruct=*ptrEntityStruct;
    healthAddress=ptrHealth;
    if(godMode==0x01){
      *(unsigned int *)refEAX=refXMM0S2;
    }else{
      *(unsigned int *)refEAX=refXMM0.1F;
    }
  break;
  // 敌人
  case 0x02:
    *(unsigned int *)refEAX=0F;
  break;
  // 中立
  case 0x03:
    *(unsigned int *)refEAX=refXMM0.1F;
  break;
  // 其它(默认情况下不能直接定义常量字符串,我这里只是演示)
  default:
    MessageBoxA(0,"遇到问题","当前结构无法判断类型",0);
  break;
}
/*{$ASM}*/

§实例参考

§注意事项

  • 在 C 代码块中使用 return 语句将会导致回写逻辑失效,参考此 issue
  • 如果有语法检查没发现的问题,即激活脚本时才报错,会导致一定量的内存泄露
  • RSP 寄存器需要减去 3 个 64 位寄存器的长度,即正确表达为 unsigned long long * ptrRSP = (unsigned long long *)(refRSP+0x18),参考此 issue已于 7.4.0 修复
作者
ragnaroks
发布时间
2021-10-01
创作协议