简单破解旅行青蛙

最近一款佛系养娃(误)蛙的游戏突然爆红,我也试着玩了一下,结果被圈粉了。有蛙当然要晒,但是看朋友圈,别人的蛙出去拍的照片都是光鲜亮丽,呼朋唤友的,自家的蛙却只能单人穷游。说到底是当爹的没钱害的,但是没钱没关系,咱喜欢瞎折腾。下面咱就来搞搞这个小游戏。

首先,得拿到这个小游戏的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。照着之前的办法改吧,哈哈。


还有就是蛙回家时间,我试着找了相关代码,太复杂了,改了几次也没成功。想玩快点的,照着网上说的修改系统时间来弄吧。

声明:小白纯属瞎折腾蹭热度。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章