這是我第一次分析和修改unity3d游戲。
之前玩了一款很好玩的海戰游戲,過程中遇到一個變態可以連續開炮,簡直無敵變態,嚴重的破壞了公平性。所以本著研究的態度,第一次開始接觸分析unity3d相關方面的知識。
簡單的介紹下,游戲中有各種類型軍艦可供使用,巡洋艦、驅逐艦、戰列艦和航母,武器有主炮、副炮、近防炮、防空炮、魚類、飛機等。炮彈是有填充cd時間的,發射完一次需要冷卻x秒。然而遇到開掛的那貨根本就沒cd,連續發射,騷的要死,打的我等屁滾尿流。。。
好吧進入正題,這里就不介紹怎么分辨是不是unity3d類型的游戲,因為這款游戲有再明顯不過的提示了。
第一步
解壓apk,dnSpy加載Assembly-CSharp.dll。我這里還是使用虛擬機,畢竟Mac上的相關軟件不好找。一打開程序,很陌生,不過工具的用法基本都大同小異。
展開目錄看看,這里沒有加密,類、方法還有一些成員反編譯后整體還是很清晰的,只有一部分類名處理成了AEAHEOPPMLM類似的不易閱讀的東西。
第二步 -- 定位代碼
因為是第一次接觸,所以沒有著急的先按照那些教程進行搜索,而是挨個看了看里面的內容。雖然有些類名被處理了,但有些仍然保留著非常容易識別的名字,通過名字可以大概猜出該類實現的功能。(一般的教程是提示按照什么coin、cash、attack、hit等等關鍵字來搜索)
看的差不多了就來了感覺,還是用通用的關鍵詞搜索方法試一下。
既然炮彈發射完后需要重新填充,那么必定有個地方來處理重新填充彈藥的邏輯,這時候腦海就突然蹦出reloading這詞。在FPS的游戲中,經常能聽到這,就是換彈夾時候。抱著試一試的態度搜索一下reload,發現了可疑的函數GetReloadProgress,并且這個函數是在名叫TurretInstance類中。還是不著急直接看GetReloadProgress的具體邏輯,因為TurretInstance這個名字太引人注目了,翻譯成傻白就是炮塔的實例。。。應該預覽下這個類都寫了哪些方法。
展開列表,發現了更可疑的關鍵字CanShotNow,并且還是布爾型的。
看字面意思就是問現在能不能射?等等不要停!!!跟進去看看再說
又跟進看了看IsReloaded和IsAimed方法,憑感覺應該是這里沒錯了。
public bool IsReloaded()
{
return this.GetReloadProgress() >= 1f;
}
public bool IsAimed()
{
return this.COKKPADBCCI;
}
這里我讓CanShotNow()和 IsReloaded()一直返回true就好了,期望結果是沒有reload,可以一直射。
第三步 修改
看教程是說比較常用的方式之一是編輯IL指令
IL指令是啥,沒接觸過啊。。。不過咱不怕,可以現學嘛。查了一下,發現其實和之前接觸過的匯編很像,也是各種指令集。先跟著代碼練一下
public bool IsReloaded()
{
return this.GetReloadProgress() >= 1f;
}
ldarg.0 # 將索引為 0 的參數加載到計算堆棧上
call instance folat32 TurretInstance::GetReloadProgress() # 調用方法
ldc.r4 1 # 將所提供的 float32 類型的值作為 F (float) 類型推送到計算堆棧上。這里提供的是1
clt.un # 比較無符號值,v1<v2則將1推送到計算堆棧上;反之將0推送到計算堆棧上
ldc.i4.0 # 將整數值 0 作為 int32 推送到計算堆棧上。
ceq # 比較值,相等將1推送到計算堆棧上;不相等將0推送到計算堆棧上
ret 從當前方法返回,并將返回值
邏輯少,所以很清晰,也很容易弄明白。我們需要讓它直接返回true,兩行就夠了,其他沒用的指令全都刪掉。
ldc.i4.1
ret
點擊確定后可以看到反編譯的結果直接是return true。
同樣,也把CanShotNow()改掉。然后生成新的dll,打包回原apk中。
第四步 實踐出真知
還是實踐出真知,把“解帶回原方程驗證”。改完后得實際上手試試,感覺有點小激動。進入游戲,選擇對戰,然后開炮。。。果然,沒有了重新填充彈藥的cd時間,可以連續開炮了。這feel也是騷的要死。
后續更新
1、修改耐久度
玩兒的時候發現戰艦還是有耐久度的,每打完一局需要用黃金來修復。還是關鍵詞搜索 -- durability
前面那個方法非常可疑。
public int GetDurability(string LENDAPGEFMF)
{
DateTime dateTime = SavedData.FHMGLEEKCKM.GetDateTime(CFPGGHAEJJK.CFNJBKJBIGJ(LENDAPGEFMF), default(DateTime));
int @int = SavedData.FHMGLEEKCKM.GetInt(CFPGGHAEJJK.PFCLKBDALLH(LENDAPGEFMF), 0);
int num = (int)((UnbiasedTime.Instance.Now() - dateTime).TotalSeconds * 0.013888888888888888);
return Mathf.Clamp(@int + num, 0, 100);
}
看這里基本確定就是我們要找的耐久度了,調用了c#的Mathf.Clamp方法,限定值是0到100之間,正好符合戰艦耐久度0%~100%。那么我們只需要讓@int + num的值大于100就好了。IL指令還不太熟,最后改成了這樣,不過也符合需求。
打包apk,重新安裝,再怎么玩發現耐久度都是100%了
2、升級武器時間
ShipData類中包含了許多可以修改的東西,其中就包括了武器升級時間。
public static readonly ObscuredInt[] sWeaponUpgradingTime = new ObscuredInt[]
還有這里,都是int型,直接改為return 0就好
3、完成一局后的系統獎勵
LevelEndScreenRewardPackage:可以看到變量igahmbihpph分別對應Gold、Dollards、Rank。而這三個值都是int型,隨便改
private void Start()
{
if (this.mIsPremium)
{
this.mPremiumPercents.text = "+" + 50 + "%";
}
string str = LanguageManager.Instance.GetTextValue("Gold").ToUpper();
string str2 = LanguageManager.Instance.GetTextValue("Dollars").ToUpper();
string str3 = LanguageManager.Instance.GetTextValue("Rank").ToUpper();
bool flag = GamePlaySceneController.FHMGLEEKCKM.GetGamePlayState() == NIGDGPAKCDD.Victory;
int igahmbihpph = AGDOBJHOIIP.PGBFJJGGCPJ(ickpehnnhba, this.mIsPremium);
int igahmbihpph2 = AGDOBJHOIIP.IHCCOBMBINK(ickpehnnhba, this.mIsPremium);
int igahmbihpph3 = AGDOBJHOIIP.IFBBNMJIKMI(ickpehnnhba, this.mIsPremium);
this.mGoldAmount.text = "<size=18><color=#FF7900FF>" + str + "</color></size>\n" + LGLPBMGKBCA.CCFBDCGHAPD(igahmbihpph);
this.mDollarsAmount.text = "<size=18><color=#FF7900FF>" + str2 + "</color></size>\n" + LGLPBMGKBCA.CCFBDCGHAPD(igahmbihpph2);
this.mRankAmount.text = "<size=18><color=#FF7900FF>" + str3 + "</color></size>\n" + LGLPBMGKBCA.CCFBDCGHAPD(igahmbihpph3);
}
然而這里并不管用,仔細分析了一下,這塊只是展示邏輯,并不負責更新數據,繼續查找。在LevelEndScreenRewardPage中找到了update()邏輯:還是一樣,那幾個int對應著gold、dollars、EXP。通過這里的修改后就實現了最終的修改
// LevelEndScreenRewardPage
// Token: 0x06002738 RID: 10040 RVA: 0x00116F14 File Offset: 0x00115114
private void Update()
{
this.IIDIAKCBILF += Time.deltaTime;
bool flag = CLIFCIJHHAF.FHMGLEEKCKM.OIDFGGEIMPJ().NNAINKAPABO();
if (!flag && this.IIDIAKCBILF > this.mRegularRewardStateAppearDelay && !this.mRegularRewardState.activeSelf)
{
this.mRegularRewardState.SetActive(true);
}
if (flag && this.IIDIAKCBILF > this.mPremiumRewardStateAppearDelay && !this.mPremiumRewardState.activeSelf)
{
this.mPremiumRewardState.SetActive(true);
}
this.mRegularReward.alpha = ((!flag) ? 1f : 0.3f);
this.mPremiumReward.alpha = ((!flag) ? 0.3f : 1f);
this.mGetPremiumButton.SetActive(!flag);
if (!flag && !this.ACFGGDLFKEA)
{
this.ACFGGDLFKEA = true;
bool ickpehnnhba = GamePlaySceneController.FHMGLEEKCKM.GetGamePlayState() == NIGDGPAKCDD.Victory;
int odjcphnbchd = AGDOBJHOIIP.PGBFJJGGCPJ(ickpehnnhba, false);
int fapkfekpjic = AGDOBJHOIIP.IHCCOBMBINK(ickpehnnhba, false);
int niljkdkplfa = AGDOBJHOIIP.IFBBNMJIKMI(ickpehnnhba, false);
this.BAKMLFAIIMI(odjcphnbchd, fapkfekpjic, niljkdkplfa);
}
if (flag && !this.DPNKFCEJPNL)
{
this.DPNKFCEJPNL = true;
bool ickpehnnhba2 = GamePlaySceneController.FHMGLEEKCKM.GetGamePlayState() == NIGDGPAKCDD.Victory;
int odjcphnbchd2 = AGDOBJHOIIP.PGBFJJGGCPJ(ickpehnnhba2, true);
int fapkfekpjic2 = AGDOBJHOIIP.IHCCOBMBINK(ickpehnnhba2, true);
int niljkdkplfa2 = AGDOBJHOIIP.IFBBNMJIKMI(ickpehnnhba2, true);
this.BAKMLFAIIMI(odjcphnbchd2, fapkfekpjic2, niljkdkplfa2);
}
}
更新:.dll文件,請自行打包回apk中
- 解鎖所有艦船
- 炮彈reload時間為1秒
- 艦船耐久度永遠100%
- 升級艦船時間為0
- 戰斗獎勵翻倍,包括金錢、黃金、勛章
- 魚雷數量增加到單側500,無限制發魚雷
(已更新數個版本,鏈接作廢)
已刪