旅行青蛙(旅かえる)逆向筆記

舒適提示:閱讀本文你的電腦須要安裝好apktool、signapk、.NET Reflector、dnSpy。他們均可以在github或吾愛雲盤上獲取。git

1、APK結構

  • 旅行青蛙是個Unity的遊戲。簡單說下Unity:Unity是一個用於製做3D遊戲的C#框架,能夠跨平臺。也就是說旅行青蛙的核心遊戲邏輯在Android和iOS上面是同樣的代碼。顯然Android更容易讓咱們分析,本文先從APK的結構開始。
  • 使用apktool反編譯APK,發現Unity遊戲的smali代碼並無太多的信息,基本都是調用Google的Ad接口之類的,或者是Google Play的應用內購買,就不須要太關心了。
  • lib文件夾中主要都是Unity、Mono等的支持動態庫so文件,也不是咱們關心的對象。
  • 經查閱資料能夠得知,Unity遊戲的主要邏輯代碼存放於assets/bin/Data/Managed下的Assembly-CSharp.dll動態庫文件中,C#的dll文件不難分析,咱們使用.NET Reflector和dnSpy進行分析和修改。

2、Assembly-CSharp.dll修改

  • 使用.NET Reflector打開Assembly-CSharp.dll文件,觀察整個dll的結構。發現幾乎全部邏輯代碼都位於「-」下面。
  • 咱們運行遊戲,在商店點擊購買昂貴的商品,或者在抽獎區抽獎,遊戲會提示「みつ葉が足りません」和「ふくびき券が足りません」和。
  • 雖然不懂日語,可是大概知道是提醒你不夠的意思,由於電腦沒有日文輸入法,因此在.NET Reflector中嘗試搜索漢字「足」,看看有什麼結果。
  • 結果找到了兩個方法中說起了「足」字,分別是SetInfoPanelData方法和PushRollButton方法。首先查看SetInfoPanelData方法,發現是進行商品購買的邏輯代碼,代碼以下:
public void SetInfoPanelData(int shopIndex, Vector3 pos)
{
    if (shopIndex == -1)
    {
        this.unsetCursor();
        this.InfoPanel.GetComponent<InfoPanel>().SetInfoPanel(-1);
    }
    else if (Mathf.Abs(this.flickMove) <= (this.S_FlickChecker.flickMin / 3f))
    {
        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 format = SuperGameMaster.sDataBase.get_ShopDB(shopIndex);
            ItemDataFormat format2 = SuperGameMaster.sDataBase.get_ItemDB_forId(format.itemId);
            if (format2 != null)
            {
                if (!format2.spend && (SuperGameMaster.FindItemStock(format2.id) != 0))
                {
                    SuperGameMaster.audioMgr.PlaySE(Define.SEDict["SE_Cancel"]);
                }
                else if (SuperGameMaster.CloverPointStock() >= format2.price)
                {
                    if (SuperGameMaster.FindItemStock(format.itemId) < 0x63)
                    {
                        <SetInfoPanelData>c__AnonStorey1 storey = new <SetInfoPanelData>c__AnonStorey1 {
                            $this = this
                        };
                        base.GetComponent<FlickCheaker>().stopFlick(true);
                        storey.confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();
                        if (format2.type == Type.LunchBox)
                        {
                            storey.confilm.OpenPanel_YesNo(string.Concat(new object[] { format2.name, "\nを買いますか?\n(所持數 ", SuperGameMaster.FindItemStock(format.itemId), ")" }));
                        }
                        else
                        {
                            storey.confilm.OpenPanel_YesNo(format2.name + "\nを買いますか?");
                        }
                        storey.confilm.ResetOnClick_Yes();
                        storey.confilm.SetOnClick_Yes(new UnityAction(storey, (IntPtr) this.<>m__0));
                        storey.confilm.SetOnClick_Yes(new UnityAction(storey, (IntPtr) this.<>m__1));
                        storey.confilm.SetOnClick_Yes(new UnityAction(storey, (IntPtr) this.<>m__2));
                        storey.confilm.ResetOnClick_No();
                        storey.confilm.SetOnClick_No(new UnityAction(storey, (IntPtr) this.<>m__3));
                        storey.confilm.SetOnClick_No(new UnityAction(storey, (IntPtr) this.<>m__4));
                    }
                    else
                    {
                        <SetInfoPanelData>c__AnonStorey2 storey2 = new <SetInfoPanelData>c__AnonStorey2 {
                            $this = this
                        };
                        base.GetComponent<FlickCheaker>().stopFlick(true);
                        storey2.confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();
                        storey2.confilm.OpenPanel("もちものがいっぱいです");
                        storey2.confilm.ResetOnClick_Screen();
                        storey2.confilm.SetOnClick_Screen(new UnityAction(storey2, (IntPtr) this.<>m__0));
                        storey2.confilm.SetOnClick_Screen(new UnityAction(storey2, (IntPtr) this.<>m__1));
                    }
                }
                else
                {
                    <SetInfoPanelData>c__AnonStorey3 storey3 = new <SetInfoPanelData>c__AnonStorey3 {
                        $this = this
                    };
                    base.GetComponent<FlickCheaker>().stopFlick(true);
                    storey3.confilm = this.ConfilmUI.GetComponent<ConfilmPanel>();
                    storey3.confilm.OpenPanel("みつ葉が足りません");
                    storey3.confilm.ResetOnClick_Screen();
                    storey3.confilm.SetOnClick_Screen(new UnityAction(storey3, (IntPtr) this.<>m__0));
                    storey3.confilm.SetOnClick_Screen(new UnityAction(storey3, (IntPtr) this.<>m__1));
                }
            }
        }
    }
}
  • 定位到關鍵代碼:
SuperGameMaster.CloverPointStock() >= format2.price
  • 猜想SuperGameMasterCloverPointStock方法是得到三葉草數量的方法,進入查看該方法:
public static int CloverPointStock()
{
    return SuperGameMaster.saveData.CloverPoint;
}
  • 顯然直接修改該函數就能夠實現固定數量的三葉草,使用64位的dnSpy修改代碼,定位到該方法,右擊鼠標單擊「編輯IL指令」,刪去前兩句指令中的一句,再修改第一句指令爲ldc.i4 9876,保存後函數變爲:
public static int CloverPointStock()
{
    return 9876;
}
  • 按照一樣的方法分析PushRollButton方法,獲得代碼:
public void PushRollButton()
{
    if (SuperGameMaster.TicketStock() < 5)
    {
        <PushRollButton>c__AnonStorey0 storey = new <PushRollButton>c__AnonStorey0 {
            confilm = this.ConfilmUI.GetComponent<ConfilmPanel>()
        };
        storey.confilm.OpenPanel("ふくびき券が足りません");
        storey.confilm.ResetOnClick_Screen();
        storey.confilm.SetOnClick_Screen(new UnityAction(storey, (IntPtr) this.<>m__0));
    }
    else
    {
        SuperGameMaster.GetTicket(-5);
        SuperGameMaster.set_FlagAdd(Type.ROLL_NUM, 1);
        base.GetComponentInParent<UIMaster>().freezeObject(true);
        base.GetComponentInParent<UIMaster>().blockUI(true, new Color(0f, 0f, 0f, 0.3f));
        this.LotteryCheck();
        this.ResultButton.GetComponent<RollResultButton>().CngImage((int) this.result);
        this.ResultButton.GetComponent<RollResultButton>().CngResultText(Define.PrizeBallName[this.result] + "がでました");
        this.LotteryWheelPanel.GetComponent<LotteryWheelPanel>().OpenPanel(this.result);
        SuperGameMaster.SetTmpRaffleResult((int) this.result);
        SuperGameMaster.SaveData();
        SuperGameMaster.audioMgr.PlaySE(Define.SEDict["SE_Raffle"]);
        this.BackFunc();
    }
}
  • 定位到關鍵代碼:
if (SuperGameMaster.TicketStock() < 5)
  • 以及
SuperGameMaster.GetTicket(-5);
SuperGameMaster.set_FlagAdd(Type.ROLL_NUM, 1);
  • 修改任意一處均可以,顯然修改TicketStock方法的返回值更省事,使用dnSpy按一樣的方法修改代碼,原來方法爲:
public static int TicketStock()
{
    return SuperGameMaster.saveData.ticket;
}
  • 修改成:
public static int TicketStock()
{
    return 5;
}

3、APK重打包和簽名

  • 通過以上的修改,能夠實現無限抽獎券和無限三葉草,將APK從新打包便可。
  • 將修改後的dll文件保存,替換本來的Assembly-CSharp.dll,而後使用apktool從新打包,再進行簽名,就可使用了。

4、總結和未完待續

  • 有時間的話會繼續分析這個代碼。除此以外也發現,Unity遊戲若是不進行任何保護的話,是很容易被篡改的,網上有不少流傳的「漢化版」以及「破解版」基本都是這樣的原理。小路不會在APK中添加其餘東西,可是網絡上其餘人就不必定了。在這種APK中添加廣告,收集信息也是不難的,因此你們在下載應用的時候仍是應該注意啊!
相關文章
相關標籤/搜索