您的位置:首页 > 其它

【原创】植物大战僵尸全解密---存档篇

2010-05-07 12:29 465 查看
2010-04-13

更新了一点,尽量把demo做成program

2010-04-12

原来的程序在删除用户时,没有删除对应用户的游戏进度。现在修正

另外告诉大家一个关于游戏的小密秘。在帖子的最后。

植物大战僵尸全解密---存档篇

内存修改器游侠网上已经有了,再做也没意思了,我来做一个存档编辑工具吧


这是个很赞的游戏,最近沉迷上了.

由于本人水平太菜.老是徘徊在第4关.于是便拿出家伙把它修理了一番.

和游戏相关的,都分析出来了,有些不太常用到的,就不分析了。

没想到还挖出了不少好东西,不敢独享.

我分析的是它的存档文件数据,能分析的都分析出来了,整理成了结构体.方便使用.

存档文件对应的结构体内容如下:

users.dat

/************************************************************************/

/* userdata/users.dat 用户信息文件数据结构 */

/************************************************************************/

//文件头

typedef struct _FILEHEADER

{

DWORD dwValidFlag; /*值必须为0xE,文件有效性标识*/

DWORD dwNum; /*保存用户的数量,十六进制值*/

}FILE_HEADER, *PFILE_HEADER;

typedef struct _USERINFO

{

WORD wNameLen; /*用户名长度*/

LPSTR pszName; /*用户名,实际存放在文件中的是用户名的ASCII字符,不包含末尾的NULL字符*/

DWORD dwReserveed; /*保留,经测试,没有发现其它用途*/

DWORD dwIndex; /*用户ID, 对应生成相应的UserN.dat文件,N的值便是dwIndex/

}USER_INFO, *PUSER_INFO;

userN.dat(N代表用户ID)

/************************************************************************

* userdata/userN.dat 用户存档数据结构, N的值和dwIndex相对应,即每个用户自己的存档文件.

************************************************************************/

typedef struct _USERDATA

{

DWORD dwValidFlag; /*offset:0x0 值必须为0xC,文件有效性标识*/

DWORD dwGuan; /*offset:0x4 当前关卡 */

DWORD dwMoney; /*offset:0x8 金钱数*/

DWORD dwSilverCup; /*offset:0xc 获取银向日葵奖杯,得到金杯后,该位上的数字表示完成了n次冒险*/

/* 更多游戏通关 全胜条件:低0~4位和高24~28位都为1 */

DWORD dwSC_Game[10]; /*offset:0x10~0x30+0x8 生存模式通关*/

DWORD dwReserved_9[5];

DWORD dwXYX_Game[20]; /*offset:0x40+0xc~0x90+0x8 小游戏通关*/

DWORD dwReserved_10[15];

DWORD dwJM_1_9_Game[9]; /*offset:0xd0+0x8~0xf0+0x8 解迷游戏1-9关*/

DWORD dwReserved_11; /*这个是无限任务,无法通关, 记录的是通过该关的次数, 类似的偏移量还有几个,我全部保留了*/

DWORD dwJM_10_18_Game[9];/*offset:0x100~0x120 解迷游戏10-18关*/

DWORD dwReserved_1[31];

/* 游戏道具 */

DWORD dwBuy_JQSS; /*offset:0x1a0 购买 机枪射手,BOOL值*/

DWORD dwBuy_SSXRK; /*offset:0x1a0+0x4 购买 双生向日葵,BOOL值*/

DWORD dwBuy_YYG; /*offset:0x1a0+0x8 购买 忧郁茹,BOOL值*/

DWORD dwBuy_XP; /*offset:0x1a0+0xc 购买 香蒲,BOOL值*/

DWORD dwBuy_BG; /*offset:0x1b0 购买冰瓜*/

DWORD dwBuy_XJC; /*offset:0x1b0+0x4 购买 吸金磁,BOOL值*/

DWORD dwBuy_DCW; /*offset:0x1b0+0x8 购买 地刺王,BOOL值*/

DWORD dwBuy_YMJNP; /*offset:0x1b0+0xc 购买 玉米加农炮,BOOL值*/

DWORD dwBuy_MFZ; /*offset:0x1c0 购买模仿者,BOOL值*/

DWORD dwReserved_3[1];

DWORD dwReserved_12[3]; // 金盏花芽种,共三个,每个变量的值为0x0ea6,但是要在文件尾添加花的生长数据,先保留。

/* 禅境花园道具 */

DWORD dwBuy_HJSH; /*offset:0x1d0+0x4 购买 黄金水壶,BOOL值*/

DWORD dwBuy_FL; /*offset:0x1d0+0x8 购买 肥料,低8字节为数量ED,低9~16字节为购买标识0x3*/

DWORD dwBuy_SCJ; /*offset:0x1d0+0xc 购买 杀虫剂,低8字节为数量ED,低9~16字节为购买标识0x3*/

DWORD dwBuy_CPJ; /*offset:0x1e0 购买 唱片机,BOOL值*/

DWORD dwBuy_YYST; /*offset:0x1e0+0x4 购买 园艺手套,BOOL值*/

DWORD dwBuy_MGY; /*offset:0x1e0+0x8 购买 蘑菇园,BOOL值*/

DWORD dwBuy_TC; /*offset:0x1e0+0xc 购买 推车,BOOL值*/

/* 游戏辅助道具 */

DWORD dwBuy_CWGN; /*offset:0x1f0 购买宠物蜗牛值为, 0x04bbda2fe */

DWORD dwAddCardNum; /*offset:0x1f0+0x4 增加的道具槽,有效范围:0x1~0x4*/

DWORD dwBuy_CTQJC; /*offset:0x1f0+0x8 购买 池塘清洁车,BOOL值*/

DWORD dwBuy_WDTC; /*offset:0x1f0+0xc 购买 屋顶推车,BOOL值*/

DWORD dwBuy_SB; /*offset:0x200 购买 扫把, 已购买:0x3,未购买:0*/

DWORD dwBuy_SZG; /*offset:0x200+0x4 购买 水族馆,BOOL值*/

DWORD dwReserved_8[1];

DWORD dwBuy_ZHS; /*offset:0x200+0xc 购买 智慧树,BOOL值*/

DWORD dwBuy_ZHS_FL; /*offset:210 购买 智慧树肥料, 值为0x03e8时状态为可买,买一个加一,最大为10个*/

DWORD dwBuy_JGBZS; /*offset:0x210+0x4 购买 坚果包扎术,BOOL值*/

DWORD dwReserved_5[58];

/* 开启小游戏 */

DWORD dwUnlock_XYX; /*offset:0x300 开启 小游戏,BOOL值*/

DWORD dwUnlock_JM; /*offset:0x300+0x4 开启 解迷模式,BOOL值*/

DWORD dwReserved_6[4];

DWORD dwUnlock_SC; /*offset:0x310+0x8 开启 生存模式,BOOL值*/

DWORD dwReserved_7[6];

}USER_DATA, *PUSER_DATA;

在游戏中修改阳光数的代码如下:

DWORD dwMemAddr = 0x002a9f38; //指针地址offset,通过偏移的方式读取地址比较保险.

DWORD dwOffset1 = 0x00000768; //这是个二级指针,即指向指针的指针,这是二级指针指向的地址的偏移量

DWORD dwOffset2 = 0x00005560; //这是二级指针指向的指针的地址,保存的就是冒险模式中的阳光数,正是我们的目标地址

__try

{

hWnd = FindWindow(_T("MainWindow"), NULL);//查找游戏的窗口,如果不存在则说明游戏没有运行

if (INVALID_HANDLE_VALUE == hWnd)

{

__leave;

}

dwMemBase = GetWindowLongPtr(hWnd, GWL_HINSTANCE); //先获取游戏进程的基址

dwMemAddr += dwMemBase; //加上偏移,就是我们要读取的地址了.

DWORD dwPid = GetDlgItemInt(hDlg, IDC_EDT_PID, NULL, FALSE);

hProc = OpenProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION, FALSE, dwPid); //我们仅仅需要VirtualProtectEx,ReadProcessMemory,WriteProcessMemory这三个操作而已,

//只需要对进程有 PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION 的权限就够了。

//MSDN上说PROCESS_VM_OPERATION权限就可以进行WriteProcessMemory操作了,实际过程中却是

//句柄无效,为了保险。我把三个属性都加上~~~

if (hProc == NULL)

{

g_bLocked = FALSE;

ShowErrorInfo("获取进程出错");

__leave;

}

if (!VirtualProtectEx(hProc, (LPVOID)dwMemAddr, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect)) //改变内存地址属性为可读写。

{

ShowErrorInfo("修改内存属性出错");

__leave;

}

DWORD dwReaded = 0;

if (!ReadProcessMemory(hProc, (LPVOID)dwMemAddr, &dwSunny, sizeof(DWORD), &dwReaded)) //先把0x001224d4的地址内容读出来,由于游戏使用的是二级地址即指向指针的指针,

{ //所以现在读出来的内容其实是一个地址,我们还要继续读,直到读取到数据为止。

ShowErrorInfo("读取内存数据出错");

__leave;

}

dwMemAddr = dwSunny + dwOffset1; //the address offset is 0x768 ^_^~~;

if (!ReadProcessMemory(hProc, (LPVOID)dwMemAddr, &dwSunny, sizeof(DWORD), &dwReaded)) //先把0x001224d4的地址内容读出来,由于游戏使用的是二级地址即指向指针的指针,

{ //所以现在读出来的内容其实是一个地址,我们还要继续读,直到读取到数据为止。

ShowErrorInfo("读取内存数据出错");

__leave;

}

dwMemAddr = dwSunny + dwOffset2; //the address offset is 0x5560 ^_^~~;

if (!ReadProcessMemory(hProc, (LPVOID)dwMemAddr, &dwSunny, sizeof(DWORD), &dwReaded)) //由于游戏使用的是二级地址,所以只要再读一次就可以得到我们需要的数据了。

{

ShowErrorInfo("读取内存数据出错");

__leave;

}

if (bIsRead)

{

SetDlgItemInt(hDlg, IDC_EDT_SUNNY, dwSunny, FALSE); //成功读出来以后把数据显示到界面。

//如果能成功获取数据,设置控件状态可用,防止误写入错误的内存地址

EnableWindow(GetDlgItem(hDlg, IDC_BTN_CHEAT), TRUE);

EnableWindow(GetDlgItem(hDlg, IDC_EDT_SUNNY), TRUE);

EnableWindow(GetDlgItem(hDlg, IDC_BTN_LOCK), TRUE);

}

else

{

DWORD dwWritten = 0;

DWORD dwNewSunny = GetDlgItemInt(hDlg, IDC_EDT_SUNNY, NULL, FALSE);

if (dwReaded == dwNewSunny) //如果不等于我们设定的值才进行修改。

{

__leave;

}

if (!WriteProcessMemory(hProc, (LPVOID)dwMemAddr, &dwNewSunny, sizeof(DWORD), &dwWritten)) //如果是要改变数据的值,只进行写操作。

{

ShowErrorInfo("写入内存数据出错");

__leave;

}

}

}

__finally

{

if (dwOldProtect != 0)

{

if (!VirtualProtectEx(hProc, (LPVOID)dwMemAddr, sizeof(DWORD), dwOldProtect, &dwOldProtect)) //做完要做的事以后,再把属性设置回去。这一步也可以省略.

{

ShowErrorInfo("恢复内存属性出错");

}

}

CloseHandle(hProc);

}

}

[2010-04-12]

国内有汉化版,汉化的版本已经是无限制版本了,而且汉化的质量很棒.

这个游戏本身就是绿色免费的小游戏,只不过官方自己加上了一个loader来显示试用信息而已.

下面教大家一个魔术,把正式版从试用版里变出来,不使用任何修改工具.

从官方网站下载游戏安装程序.原版是英文的,默认安装路径是C:/Program Files/PopCap Games/Plants vs. Zombies 如果你更改了安装路径,那么就到对应的路径下.运行PlantsVsZombies.exe,会弹出来一个对话框,选择 "Play Trial Game" 来启动游戏,在游戏显示loading画面的时候,再到游戏的安装目录看一下,会发现一个新的文件popcapgame1.exe,文件大小是2.86MB,属性为隐藏.这个不是木马,也不是恶意文件,而是正式版的游戏主程序.该文件是用独占方式创建的,不能直接复制.我们使用强大的磁盘管理工具Disk Genius来提取它.我选择的是复制到桌面.OK了,退出游戏.把桌面上的popcapgame1.exe重命名为PlantsVsZombies.exe,然后覆盖游戏安装目录下的同名文件,再次运行游戏.怎么样?是不是什么提示信息也没有了,而且游戏一切正常,没有任何限制.

为什么一个商业休闲游戏如此脆弱?或者这正是它的卖点所在,想通过让玩家发现所谓的技巧来提升自己的人气么?亦或是游戏发现自己运行在中国境内,知道俺们china ren不好惹,自动就把程序贡献出来了?





附件中的PlantsVsZombies_en.rar是我提取出来的主程序,解压到游戏安装目录并选择覆盖原来的文件就行了,版本必须是官方的英文原版.

附件中是我写的游戏助手,用来演示修改阳光数,定制用户存档文件.

界面如下





游戏中效果

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: