2010-04-13
更新了一點,盡量把demo做成program
2010-04-12
原來的程序在刪除用戶時,沒有刪除對應(yīng)用戶的游戲進(jìn)度,F(xiàn)在修正
另外告訴大家一個關(guān)于游戲的小密秘。在帖子的最后。
植物大戰(zhàn)僵尸全解密---存檔篇
內(nèi)存修改器游俠網(wǎng)上已經(jīng)有了,再做也沒意思了,我來做一個存檔編輯工具吧
這是個很贊的游戲,最近沉迷上了.
由于本人水平太菜.老是徘徊在第4關(guān).于是便拿出家伙把它修理了一番.
和游戲相關(guān)的,都分析出來了,有些不太常用到的,就不分析了。
沒想到還挖出了不少好東西,不敢獨享.
我分析的是它的存檔文件數(shù)據(jù),能分析的都分析出來了,整理成了結(jié)構(gòu)體.方便使用.
存檔文件對應(yīng)的結(jié)構(gòu)體內(nèi)容如下:
users.dat
/************************************************************************/
/* userdata\users.dat 用戶信息文件數(shù)據(jù)結(jié)構(gòu) */
/************************************************************************/
//文件頭
typedef struct _FILEHEADER
{
DWORD dwValidFlag; /*值必須為0xE,文件有效性標(biāo)識*/
DWORD dwNum; /*保存用戶的數(shù)量,十六進(jìn)制值*/
}FILE_HEADER, *PFILE_HEADER;
typedef struct _USERINFO
{
WORD wNameLen; /*用戶名長度*/
LPSTR pszName; /*用戶名,實際存放在文件中的是用戶名的ASCII字符,不包含末尾的NULL字符*/
DWORD dwReserveed; /*保留,經(jīng)測試,沒有發(fā)現(xiàn)其它用途*/
DWORD dwIndex; /*用戶ID, 對應(yīng)生成相應(yīng)的UserN.dat文件,N的值便是dwIndex/
}USER_INFO, *PUSER_INFO;
userN.dat(N代表用戶ID)
/************************************************************************
* userdata\userN.dat 用戶存檔數(shù)據(jù)結(jié)構(gòu), N的值和dwIndex相對應(yīng),即每個用戶自己的存檔文件.
************************************************************************/
typedef struct _USERDATA
{
DWORD dwValidFlag; /*offset:0x0 值必須為0xC,文件有效性標(biāo)識*/
DWORD dwGuan; /*offset:0x4 當(dāng)前關(guān)卡 */
DWORD dwMoney; /*offset:0x8 金錢數(shù)*/
DWORD dwSilverCup; /*offset:0xc 獲取銀向日葵獎杯,得到金杯后,該位上的數(shù)字表示完成了n次冒險*/
/* 更多游戲通關(guān) 全勝條件:低0~4位和高24~28位都為1 */
DWORD dwSC_Game[10]; /*offset:0x10~0x30+0x8 生存模式通關(guān)*/
DWORD dwReserved_9[5];
DWORD dwXYX_Game[20]; /*offset:0x40+0xc~0x90+0x8 小游戲通關(guān)*/
DWORD dwReserved_10[15];
DWORD dwJM_1_9_Game[9]; /*offset:0xd0+0x8~0xf0+0x8 解迷游戲1-9關(guān)*/
DWORD dwReserved_11; /*這個是無限任務(wù),無法通關(guān), 記錄的是通過該關(guān)的次數(shù), 類似的偏移量還有幾個,我全部保留了*/
DWORD dwJM_10_18_Game[9];/*offset:0x100~0x120 解迷游戲10-18關(guān)*/
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 購買 玉米加農(nóng)炮,BOOL值*/
DWORD dwBuy_MFZ; /*offset:0x1c0 購買模仿者,BOOL值*/
DWORD dwReserved_3[1];
DWORD dwReserved_12[3]; // 金盞花芽種,共三個,每個變量的值為0x0ea6,但是要在文件尾添加花的生長數(shù)據(jù),先保留。
/* 禪境花園道具 */
DWORD dwBuy_HJSH; /*offset:0x1d0+0x4 購買 黃金水壺,BOOL值*/
DWORD dwBuy_FL; /*offset:0x1d0+0x8 購買 肥料,低8字節(jié)為數(shù)量ED,低9~16字節(jié)為購買標(biāo)識0x3*/
DWORD dwBuy_SCJ; /*offset:0x1d0+0xc 購買 殺蟲劑,低8字節(jié)為數(shù)量ED,低9~16字節(jié)為購買標(biāo)識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時狀態(tài)為可買,買一個加一,最大為10個*/
DWORD dwBuy_JGBZS; /*offset:0x210+0x4 購買 堅果包扎術(shù),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;
在游戲中修改陽光數(shù)的代碼如下:
DWORD dwMemAddr = 0x002a9f38; //指針地址offset,通過偏移的方式讀取地址比較保險.
DWORD dwOffset1 = 0x00000768; //這是個二級指針,即指向指針的指針,這是二級指針指向的地址的偏移量
DWORD dwOffset2 = 0x00005560; //這是二級指針指向的指針的地址,保存的就是冒險模式中的陽光數(shù),正是我們的目標(biāo)地址
__try
{
hWnd = FindWindow(_T("MainWindow"), NULL);//查找游戲的窗口,如果不存在則說明游戲沒有運行
if (INVALID_HANDLE_VALUE == hWnd)
{
__leave;
}
dwMemBase = GetWindowLongPtr(hWnd, GWL_HINSTANCE); //先獲取游戲進(jìn)程的基址
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這三個操作而已,
//只需要對進(jìn)程有 PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION 的權(quán)限就夠了。
//MSDN上說PROCESS_VM_OPERATION權(quán)限就可以進(jìn)行WriteProcessMemory操作了,實際過程中卻是
//句柄無效,為了保險。我把三個屬性都加上~~~
if (hProc == NULL)
{
g_bLocked = FALSE;
ShowErrorInfo("獲取進(jìn)程出錯");
__leave;
}
if (!VirtualProtectEx(hProc, (LPVOID)dwMemAddr, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect)) //改變內(nèi)存地址屬性為可讀寫。
{
ShowErrorInfo("修改內(nèi)存屬性出錯");
__leave;
}
DWORD dwReaded = 0;
if (!ReadProcessMemory(hProc, (LPVOID)dwMemAddr, &dwSunny, sizeof(DWORD), &dwReaded)) //先把0x001224d4的地址內(nèi)容讀出來,由于游戲使用的是二級地址即指向指針的指針,
{ //所以現(xiàn)在讀出來的內(nèi)容其實是一個地址,我們還要繼續(xù)讀,直到讀取到數(shù)據(jù)為止。
ShowErrorInfo("讀取內(nèi)存數(shù)據(jù)出錯");
__leave;
}
dwMemAddr = dwSunny + dwOffset1; //the address offset is 0x768 ^_^~~;
if (!ReadProcessMemory(hProc, (LPVOID)dwMemAddr, &dwSunny, sizeof(DWORD), &dwReaded)) //先把0x001224d4的地址內(nèi)容讀出來,由于游戲使用的是二級地址即指向指針的指針,
{ //所以現(xiàn)在讀出來的內(nèi)容其實是一個地址,我們還要繼續(xù)讀,直到讀取到數(shù)據(jù)為止。
ShowErrorInfo("讀取內(nèi)存數(shù)據(jù)出錯");
__leave;
}
dwMemAddr = dwSunny + dwOffset2; //the address offset is 0x5560 ^_^~~;
if (!ReadProcessMemory(hProc, (LPVOID)dwMemAddr, &dwSunny, sizeof(DWORD), &dwReaded)) //由于游戲使用的是二級地址,所以只要再讀一次就可以得到我們需要的數(shù)據(jù)了。
{
ShowErrorInfo("讀取內(nèi)存數(shù)據(jù)出錯");
__leave;
}
if (bIsRead)
{
SetDlgItemInt(hDlg, IDC_EDT_SUNNY, dwSunny, FALSE); //成功讀出來以后把數(shù)據(jù)顯示到界面。
//如果能成功獲取數(shù)據(jù),設(shè)置控件狀態(tài)可用,防止誤寫入錯誤的內(nèi)存地址
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) //如果不等于我們設(shè)定的值才進(jìn)行修改。
{
__leave;
}
if (!WriteProcessMemory(hProc, (LPVOID)dwMemAddr, &dwNewSunny, sizeof(DWORD), &dwWritten)) //如果是要改變數(shù)據(jù)的值,只進(jìn)行寫操作。
{
ShowErrorInfo("寫入內(nèi)存數(shù)據(jù)出錯");
__leave;
}
}
}
__finally
{
if (dwOldProtect != 0)
{
if (!VirtualProtectEx(hProc, (LPVOID)dwMemAddr, sizeof(DWORD), dwOldProtect, &dwOldProtect)) //做完要做的事以后,再把屬性設(shè)置回去。這一步也可以省略.
{
ShowErrorInfo("恢復(fù)內(nèi)存屬性出錯");
}
}
CloseHandle(hProc);
}
}
下載地址: http://bbs.pediy.com/showthread.php?t=115080&highlight=
國內(nèi)有漢化版,漢化的版本已經(jīng)是無限制版本了,而且漢化的質(zhì)量很棒.
這個游戲本身就是綠色免費的小游戲,只不過官方自己加上了一個loader來顯示試用信息而已.
下面教大家一個魔術(shù),把正式版從試用版里變出來,不使用任何修改工具.
從官方網(wǎng)站下載游戲安裝程序.原版是英文的,默認(rèn)安裝路徑是C:\Program Files\PopCap Games\Plants vs. Zombies 如果你更改了安裝路徑,那么就到對應(yīng)的路徑下.運行PlantsVsZombies.exe,會彈出來一個對話框,選擇 "Play Trial Game" 來啟動游戲,在游戲顯示loading畫面的時候,再到游戲的安裝目錄看一下,會發(fā)現(xiàn)一個新的文件popcapgame1.exe,文件大小是2.86MB,屬性為隱藏.這個不是木馬,也不是惡意文件,而是正式版的游戲主程序.該文件是用獨占方式創(chuàng)建的,不能直接復(fù)制.我們使用強大的磁盤管理工具Disk Genius來提取它.我選擇的是復(fù)制到桌面.OK了,退出游戲.把桌面上的popcapgame1.exe重命名為PlantsVsZombies.exe,然后覆蓋游戲安裝目錄下的同名文件,再次運行游戲.怎么樣?是不是什么提示信息也沒有了,而且游戲一切正常,沒有任何限制.
為什么一個商業(yè)休閑游戲如此脆弱?或者這正是它的賣點所在,想通過讓玩家發(fā)現(xiàn)所謂的技巧來提升自己的人氣么?亦或是游戲發(fā)現(xiàn)自己運行在中國境內(nèi),知道俺們china ren不好惹,自動就把程序貢獻(xiàn)出來了?
附件中的PlantsVsZombies_en.rar是我提取出來的主程序,解壓到游戲安裝目錄并選擇覆蓋原來的文件就行了,版本必須是官方的英文原版.
附件中是我寫的游戲助手,用來演示修改陽光數(shù),定制用戶存檔文件.
本文轉(zhuǎn)自 http://bbs.pediy.com/showthread.php?t=110690