简单破解旅行青蛙

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

首先,得拿到这个小游戏的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方法,好了,还是一样,想办法不让程序扣就行了。

[csharp]  view plain  copy
  1. public void SetInfoPanelData(int shopIndex, Vector3 pos)  
  2.     {  
  3.         if (shopIndex == -1)  
  4.         {  
  5.             this.unsetCursor();  
  6.             this.InfoPanel.GetComponent<InfoPanel>().SetInfoPanel(-1);  
  7.             return;  
  8.         }  
  9.         if (Mathf.Abs(this.flickMove) > this.S_FlickChecker.flickMin / 3f)  
  10.         {  
  11.             return;  
  12.         }  
  13.         if (this.selectShopIndex != shopIndex)  
  14.         {  
  15.             this.InfoPanel.GetComponent<InfoPanel>().SetInfoPanel(shopIndex);  
  16.             this.selectShopIndex = shopIndex;  
  17.             this.setCursor(pos);  
  18.             SuperGameMaster.audioMgr.PlaySE(Define.SEDict["SE_Cursor"]);  
  19.         }  
  20.         else  
  21.         {  
  22.             ShopDataFormat shopDataFormat = SuperGameMaster.sDataBase.get_ShopDB(shopIndex);  
  23.             ItemDataFormat itemDataFormat = SuperGameMaster.sDataBase.get_ItemDB_forId(shopDataFormat.itemId);  
  24.             if (itemDataFormat == null)  
  25.             {  
  26.                 return;  
  27.             }  
  28.             if (!itemDataFormat.spend && SuperGameMaster.FindItemStock(itemDataFormat.id) != 0)  
  29.             {  
  30.                 SuperGameMaster.audioMgr.PlaySE(Define.SEDict["SE_Cancel"]);  
  31.                 return;  
  32.             }  
  33.             if (SuperGameMaster.CloverPointStock() >= itemDataFormat.price)  
  34.             {  
  35.                 if (SuperGameMaster.FindItemStock(shopDataFormat.itemId) < 99)  
  36.                 {  
  37.                     base.GetComponent<FlickCheaker>().stopFlick(true);  
  38.                     ConfilmPanel confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();  
  39.                     if (itemDataFormat.type == Item.Type.LunchBox)  
  40.                     {  
  41.                         confilm.OpenPanel_YesNo(string.Concat(new object[]  
  42.                         {  
  43.                             itemDataFormat.name,  
  44.                             "\nを買いますか?\n(所持数\u3000",  
  45.                             SuperGameMaster.FindItemStock(shopDataFormat.itemId),  
  46.                             ")"  
  47.                         }));  
  48.                     }  
  49.                     else  
  50.                     {  
  51.                         confilm.OpenPanel_YesNo(itemDataFormat.name + "\nを買いますか?");  
  52.                     }  
  53.                     confilm.ResetOnClick_Yes();  
  54.                     confilm.SetOnClick_Yes(delegate  
  55.                     {  
  56.                         confilm.ClosePanel();  
  57.                     });  
  58.                     confilm.SetOnClick_Yes(delegate  
  59.                     {  
  60.                         this.GetComponent<FlickCheaker>().stopFlick(false);  
  61.                     });  
  62.                     confilm.SetOnClick_Yes(delegate  
  63.                     {  
  64.                         this.BuyItem();  
  65.                     });  
  66.                     confilm.ResetOnClick_No();  
  67.                     confilm.SetOnClick_No(delegate  
  68.                     {  
  69.                         confilm.ClosePanel();  
  70.                     });  
  71.                     confilm.SetOnClick_No(delegate  
  72.                     {  
  73.                         this.GetComponent<FlickCheaker>().stopFlick(false);  
  74.                     });  
  75.                 }  
  76.                 else  
  77.                 {  
  78.                     base.GetComponent<FlickCheaker>().stopFlick(true);  
  79.                     ConfilmPanel confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();  
  80.                     confilm.OpenPanel("もちものがいっぱいです");  
  81.                     confilm.ResetOnClick_Screen();  
  82.                     confilm.SetOnClick_Screen(delegate  
  83.                     {  
  84.                         confilm.ClosePanel();  
  85.                     });  
  86.                     confilm.SetOnClick_Screen(delegate  
  87.                     {  
  88.                         this.GetComponent<FlickCheaker>().stopFlick(false);  
  89.                     });  
  90.                 }  
  91.             }  
  92.             else  
  93.             {  
  94.                 base.GetComponent<FlickCheaker>().stopFlick(true);  
  95.                 ConfilmPanel confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();  
  96.                 confilm.OpenPanel("みつ葉が足りません");  
  97.                 confilm.ResetOnClick_Screen();  
  98.                 confilm.SetOnClick_Screen(delegate  
  99.                 {  
  100.                     confilm.ClosePanel();  
  101.                 });  
  102.                 confilm.SetOnClick_Screen(delegate  
  103.                 {  
  104.                     this.GetComponent<FlickCheaker>().stopFlick(false);  
  105.                 });  
  106.             }  
  107.         }  
  108.     }  

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。照着之前的办法改吧,哈哈。


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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值