1. 程式人生 > >簡單破解旅行青蛙

簡單破解旅行青蛙

最近一款佛系養娃(誤)蛙的遊戲突然爆紅,我也試著玩了一下,結果被圈粉了。有蛙當然要晒,但是看朋友圈,別人的蛙出去拍的照片都是光鮮亮麗,呼朋喚友的,自家的蛙卻只能單人窮遊。說到底是當爹的沒錢害的,但是沒錢沒關係,咱喜歡瞎折騰。下面咱就來搞搞這個小遊戲。

首先,得拿到這個小遊戲的apk。我這是通過adb pull下來的,你也可以從應用市場下載。

改apk的字尾名為rar,用壓縮軟體開啟。大致看一下,可以看出來就是一個unity遊戲。那麼就把assets\bin\Data\Managed下的Assembly-CSharp.dll拿出來。咱們後面分析修改的就是這個dll檔案了。


網上找了一下,發現dnSpy(dnSpy 是一款針對 .NET 程式的逆向工程工具

)十分好用。開啟dnSpy,將上面的dll拖入空白介面。主介面如下:


接下來開始分析吧。玩過遊戲的知道,遊戲的主要貨幣是三葉草,還有抽獎券也比較常用。


大致就是這種感覺,啥也買不起。另外一點比較尷尬,就是這個遊戲不支援中文,不過好在我們還能看懂葉、券、足三個字。這兩段日語的意思大致就是三葉草不夠、抽獎券不夠。

跑題了,反正我們已經知道了分析的切入點(就是那三個字)。dnSpy中Ctrl+Shift+K,出現全域性搜尋框,選擇數字/字串,搜“足”字。正好出來兩個結果,1個對應抽獎券,1個對應三葉草。

首先看PushRollButton方法,雙擊定位到程式碼處(dnSpy的好處就在於此,反編譯的程式碼十分接近原始碼,很容易看懂)。PushRollButton很明顯指按下抽獎券那個轉輪按鈕的意思。如下就是該方法的反編譯程式碼,可以看到一段熟悉的日語,看來是找對地方了。接下來就是看程式碼了,這段程式碼的意思很明確。首先判斷券的數量是否小於5(玩過遊戲的知道,5張券才能抽1次):小於就彈券不夠的日語框並結束這個方法,後面的抽獎的操作就不做了;大於等於就給你扣5張券,接著給你抽獎。

簡單地修改的話就是讓判斷條件恆為假,不進入彈框步驟,並且抽獎不扣獎券。所以,我們可以將5和-5改為0和0。獎券數量最少為0,不會小於0,因此不會去彈框。每回抽獎前扣0張券。


那麼,如何改這個dll呢。dnSpy也提供修改dll檔案的功能。在要修改的方法中右鍵選擇編輯IL指令。將ldc.i4.5改為ldc.i4.0,將-5改為0,點選確定。再看程式碼,已經改成功了。



券已經改完了,接下來是三葉草了。定位到SetInfoPanelData方法。方法有點長,我直接copy下來了。前面一堆大致是操作選擇物品相關的程式碼,我們著重看if(SuperGameMaster.CloverPointStock()>=

itemDataFormat.price)後的程式碼(從該判斷開始代表已經確定好要買的物品了)。該判斷可以明顯地看出就是將三葉草的數量和物品價格比較,不夠就彈框,夠就扣三葉草,一個套路。繼續分析,看到1個BuyItem方法,好了,還是一樣,想辦法不讓程式扣就行了。

public void SetInfoPanelData(int shopIndex, Vector3 pos)
	{
		if (shopIndex == -1)
		{
			this.unsetCursor();
			this.InfoPanel.GetComponent<InfoPanel>().SetInfoPanel(-1);
			return;
		}
		if (Mathf.Abs(this.flickMove) > this.S_FlickChecker.flickMin / 3f)
		{
			return;
		}
		if (this.selectShopIndex != shopIndex)
		{
			this.InfoPanel.GetComponent<InfoPanel>().SetInfoPanel(shopIndex);
			this.selectShopIndex = shopIndex;
			this.setCursor(pos);
			SuperGameMaster.audioMgr.PlaySE(Define.SEDict["SE_Cursor"]);
		}
		else
		{
			ShopDataFormat shopDataFormat = SuperGameMaster.sDataBase.get_ShopDB(shopIndex);
			ItemDataFormat itemDataFormat = SuperGameMaster.sDataBase.get_ItemDB_forId(shopDataFormat.itemId);
			if (itemDataFormat == null)
			{
				return;
			}
			if (!itemDataFormat.spend && SuperGameMaster.FindItemStock(itemDataFormat.id) != 0)
			{
				SuperGameMaster.audioMgr.PlaySE(Define.SEDict["SE_Cancel"]);
				return;
			}
			if (SuperGameMaster.CloverPointStock() >= itemDataFormat.price)
			{
				if (SuperGameMaster.FindItemStock(shopDataFormat.itemId) < 99)
				{
					base.GetComponent<FlickCheaker>().stopFlick(true);
					ConfilmPanel confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();
					if (itemDataFormat.type == Item.Type.LunchBox)
					{
						confilm.OpenPanel_YesNo(string.Concat(new object[]
						{
							itemDataFormat.name,
							"\nを買いますか?\n(所持數\u3000",
							SuperGameMaster.FindItemStock(shopDataFormat.itemId),
							")"
						}));
					}
					else
					{
						confilm.OpenPanel_YesNo(itemDataFormat.name + "\nを買いますか?");
					}
					confilm.ResetOnClick_Yes();
					confilm.SetOnClick_Yes(delegate
					{
						confilm.ClosePanel();
					});
					confilm.SetOnClick_Yes(delegate
					{
						this.GetComponent<FlickCheaker>().stopFlick(false);
					});
					confilm.SetOnClick_Yes(delegate
					{
						this.BuyItem();
					});
					confilm.ResetOnClick_No();
					confilm.SetOnClick_No(delegate
					{
						confilm.ClosePanel();
					});
					confilm.SetOnClick_No(delegate
					{
						this.GetComponent<FlickCheaker>().stopFlick(false);
					});
				}
				else
				{
					base.GetComponent<FlickCheaker>().stopFlick(true);
					ConfilmPanel confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();
					confilm.OpenPanel("もちものがいっぱいです");
					confilm.ResetOnClick_Screen();
					confilm.SetOnClick_Screen(delegate
					{
						confilm.ClosePanel();
					});
					confilm.SetOnClick_Screen(delegate
					{
						this.GetComponent<FlickCheaker>().stopFlick(false);
					});
				}
			}
			else
			{
				base.GetComponent<FlickCheaker>().stopFlick(true);
				ConfilmPanel confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();
				confilm.OpenPanel("みつ葉が足りません");
				confilm.ResetOnClick_Screen();
				confilm.SetOnClick_Screen(delegate
				{
					confilm.ClosePanel();
				});
				confilm.SetOnClick_Screen(delegate
				{
					this.GetComponent<FlickCheaker>().stopFlick(false);
				});
			}
		}
	}

BuyItem方法:紅框是扣錢的操作,把price前面的負號去掉。買東西,程式給你三葉草,就是任性。

改的方法就是將neg給nop掉。

改完後:負號沒了。


Ctrl+Shift+S將更改後的dll檔案存起來。

開啟ApkIDE,將原始apk拖到APKIDE空白處開啟。開啟assets資料夾,將修改後的dll覆蓋原Assembly-CSharp.dll檔案。編譯生成APK,安裝編譯後的APK就可以了。


效果:


後記:文中涉及到的IL指令沒有細說,因為我也不會,自己查資料看著改吧。還有一些unity遊戲的漢化版可能也是通過這個方法來修改的吧。不過在這提醒一下大家,不要下載來歷不明的破解版或漢化版遊戲,很容易被人插入惡意程式碼,竊取個人隱私、惡意扣費等。

所用工具連結:

修改了一下APKIDE的連結地址,我用的是七少月大佬的3.3.3增強版,不過他之後又更新了,有興趣的看著下吧。

APKIDE:https://www.pd521.com/thread-818-1-2.html

dnSpy:https://down.52pojie.cn/Tools/NET/dnSpy.zip

=============華麗分界線===============

哈哈,更新一下,前面說到改無限抽獎,但實際抽的時候老是抽到白玉,很煩。找了一下,搜“白玉”,可以看到抽獎概率,下面是我改過的概率,之前的是白:青:綠:紅:金=60:27:9:3:1。照著之前的辦法改吧,哈哈。


還有就是蛙回家時間,我試著找了相關程式碼,太複雜了,改了幾次也沒成功。想玩快點的,照著網上說的修改系統時間來弄吧。

宣告:小白純屬瞎折騰蹭熱度。