游戏之神
   
- 贡献度
- 12595
- 金元
- 332302
- 积分
- 83610
- 精华
- 0
- 注册时间
- 2017-6-5

|
本帖最后由 cailei110 于 2018-11-23 11:06 编辑
[模拟器找到的数据地址与ps3金手指的地址]
用ce找到的数据地址与ps3金手指的地址相差正好100000000(8个0)
比如ps3金手指文件里金钱地址是0x4000,那么ce直接定位到10004000就是模拟器里金钱的地址。
这是因为rpcs3模拟器是用固定的地址作为ps3内存存放游戏数据。也许模拟器作者觉得固定地址方便。咱修改也方便了不少。
[ps3游戏内存数值与一般游戏不同]
ps3游戏的十六进制数值与一般游戏相反,为正向排列。比如数值10000,一般游戏内存中是10 27 00 00,而ps3里面是00 00 27 10。用ce写修改脚本的时候要注意数值的顺序转换。
所以修改数值要注意这点。比如有100元钱,ce找的地址是0x40000003,那么实际上金钱地址是0x40000000,因为游戏数据是正向的。
脚本也不能直接mov [40000000],#100,而要写成mov [40000000],64000000。
或者用bswap命令转换顺序,例如mov eax,#100 ; bswap eax ; mov [40000000],eax 这样。
[用ce调试rpcs3模拟的游戏]
经过跟踪发现,rpcs3会先把游戏的代码全部翻译为64位汇编,实际是在64位汇编中运行的。汇编代码每次启动游戏会重新翻译生成,所以地址是动态的。
用ce打开rpcs3,找到要修改的数据,find access能找到对应的汇编代码。
跟踪过程和普通游戏差不多,需要注意的是,因为代码是逐条翻译过来的,所以跟踪起来感觉是一小段一小段的。
当看到 cmp dword ptr [rcx+1C],00 ;jne xxx ,说明这段翻译的代码执行完了。正常会跳走做一些事情,然后回到这里下面的代码继续执行。所以直接在jne下面的代码上 run untill继续跟就行了。
就像一个人翻译了一篇文章,念一句要喘口气,然后继续念下一句。只要看到段落标记直接执行到下一句就行了。
跟踪过程中能发现有一块频繁读写的地址其实是当作ps3寄存器来用的。可以理解为一块地址作为ps3游戏内存,一块地址作为ps3寄存器。通过翻译ps3游戏代码模拟ps3的cpu操作数据。
这就是模拟器。。。
[翻译出来的实际代码可读性极低]
轻松修改是不可能的,花很多时间修改确实很不值得。但不修改很可能要花更多时间去玩游戏,这就值得吗。。。修改和不修改,在省时间和游戏乐趣中权衡,这是个答案因人而异的问题。
跟踪的时候如果把一小段一小段的代码都记录下来,就能得到一大段模拟器翻译出来的代码了。但还是很难直接看出来代码的作用。本来mips汇编就比较难读,模拟器翻译过来的更是难以看懂。
相比起来一般基于pc平台开发的游戏,汇编代码里经常找到很多文本提示。加上pc平台游戏的调试器ollydbg什么的很丰富强大,所以pc游戏容易修改。
为了方便理解,放几个机战Z时狱篇的修改脚本。
就是用上面的修改方法找到地址的。
狙击10
[ENABLE]
10E732E6:
add qword ptr [rcx+00000108],0a
[DISABLE]
10E732E6:
add qword ptr [rcx+00000108],02
加速10
[ENABLE]
10EBC026:
add qword ptr [rcx+00000118],0a
[DISABLE]
10EBC026:
add qword ptr [rcx+00000118],03
全机4个强化插槽a
[ENABLE]
10D09A79:
mov eax,4
[DISABLE]
10D09A79:
movzx eax,byte ptr [rax+rdx+47]
队伍击破点数无限a
[ENABLE]
10EB5041:
mov eax,2
[DISABLE]
10EB5041:
movzx eax,byte ptr [rax+rdx+55]
弄明白套路就好改多了。再来一个。0改造也能选择满改奖励a [ENABLE]10D06C91:db eb [DISABLE]
找数值时,可以加个数值格式方便自动转换顺序。
新增方式,在4bytes上右键→选define new custom type (lua)分别把下面的2byte、4byte 格式贴上。
之后就选新增的格式来找数值就不用自己换算。
-----------------------------------------------------------------------------------------------
alloc(TypeName,256)
alloc(ByteSize,4)
alloc(ConvertRoutine,1024)
alloc(ConvertBackRoutine,1024)
TypeName:
db '2 Byte Big Endian',0
ByteSize:
dd 2
//The convert routine should hold a routine that converts the data to an integer (in eax)
//function declared as: stdcall int ConvertRoutine(unsigned char *input);
//Note: Keep in mind that this routine can be called by multiple threads at the same time.
ConvertRoutine:
//jmp dllname.functionname
[64-bit]
//or manual:
//parameters: (64-bit)
//rcx=address of input
xor eax,eax
mov ax,[rcx] //eax now contains the bytes 'input' pointed to
xchg ah,al //convert to big endian
ret
[/64-bit]
[32-bit]
//jmp dllname.functionname
//or manual:
//parameters: (32-bit)
push ebp
mov ebp,esp
//[ebp+8]=input
//example:
mov eax,[ebp+8] //place the address that contains the bytes into eax
mov ax,[eax] //place the bytes into eax so it's handled as a normal 4 byte value
and eax,ffff //cleanup
xchg ah,al //convert to big endian
pop ebp
ret 4
[/32-bit]
//The convert back routine should hold a routine that converts the given integer back to a row of bytes (e.g when the user wats to write a new value)
//function declared as: stdcall void ConvertBackRoutine(int i, unsigned char *output);
ConvertBackRoutine:
//jmp dllname.functionname
//or manual:
[64-bit]
//parameters: (64-bit)
//ecx=input
//rdx=address of output
//example:
xchg ch,cl //convert the little endian input into a big endian input
mov [rdx],cx //place the integer the 4 bytes pointed to by rdx
ret
[/64-bit]
[32-bit]
//parameters: (32-bit)
push ebp
mov ebp,esp
//[ebp+8]=input
//[ebp+c]=address of output
//example:
push eax
push ebx
mov eax,[ebp+8] //load the value into eax
mov ebx,[ebp+c] //load the address into ebx
//convert the value to big endian
xchg ah,al
mov [ebx],ax //write the value into the address
pop ebx
pop eax
pop ebp
ret 8
[/32-bit]
-----------------------------------------------------------------------------------------------
alloc(TypeName,256)
alloc(ByteSize,4)
alloc(ConvertRoutine,1024)
alloc(ConvertBackRoutine,1024)
TypeName:
db '4 Byte Big Endian',0
ByteSize:
dd 4
//The convert routine should hold a routine that converts the data to an integer (in eax)
//function declared as: stdcall int ConvertRoutine(unsigned char *input);
//Note: Keep in mind that this routine can be called by multiple threads at the same time.
ConvertRoutine:
//jmp dllname.functionname
[64-bit]
//or manual:
//parameters: (64-bit)
//rcx=address of input
xor eax,eax
mov eax,[rcx] //eax now contains the bytes 'input' pointed to
bswap eax //convert to big endian
ret
[/64-bit]
[32-bit]
//jmp dllname.functionname
//or manual:
//parameters: (32-bit)
push ebp
mov ebp,esp
//[ebp+8]=input
//example:
mov eax,[ebp+8] //place the address that contains the bytes into eax
mov eax,[eax] //place the bytes into eax so it's handled as a normal 4 byte value
bswap eax
pop ebp
ret 4
[/32-bit]
//The convert back routine should hold a routine that converts the given integer back to a row of bytes (e.g when the user wats to write a new value)
//function declared as: stdcall void ConvertBackRoutine(int i, unsigned char *output);
ConvertBackRoutine:
//jmp dllname.functionname
//or manual:
[64-bit]
//parameters: (64-bit)
//ecx=input
//rdx=address of output
//example:
bswap ecx //convert the little endian input into a big endian input
mov [rdx],ecx //place the integer the 4 bytes pointed to by rdx
ret
[/64-bit]
[32-bit]
//parameters: (32-bit)
push ebp
mov ebp,esp
//[ebp+8]=input
//[ebp+c]=address of output
//example:
push eax
push ebx
mov eax,[ebp+8] //load the value into eax
mov ebx,[ebp+c] //load the address into ebx
//convert the value to big endian
bswap eax
mov [ebx],eax //write the value into the address
pop ebx
pop eax
pop ebp
ret 8
[/32-bit] |
评分
-
1
查看全部评分
-
|