Sunday, 1 March 2020

How can i deal with Animal Restaurant - Cocos2d engine game?

How can i deal with Animal Restaurant - Cocos2d engine game?


This Topic is made for the section how to Mod Android Application

The way bring me to Animal Restaurant:

As the mentioned title, recently i was modding animal restaurant and find it really cute and very interesting. The game started off without much effort. Low-level items weren’t that expensive and didn’t took that much time to earn. But as the game progresses, it was close to impossible to earn as much fish without actually introducing some external inputs.







The naive method was to use an auto clicker to bot. I used “Auto Clicker” which can be downloaded from the Google Playstore. Setting up the automated taps wasn’t difficult. Create your required taps, moving them into position and setting the delay of each tap after another.

But there is some anti-cheating mechanism in the game but they were easy to bypass with another additional tap. The delay of 1ms is enough to click the orders and 50ms for tapping the Promo button. I concluded that it was the best configuration and have tested it running the auto clicker a few nights.

It ran well, seeing the fishes increase by a magnitude in the morning felt good. Until I purchase a few items from the shop and it is empty again. Was it worth it? Leaving my phone running continuously (almost 8hrs straight). No, there should be an easier way to earn the fishes.


And i know that, so you are. You will not want to spend lot of times for doing that. And the easier way is modding it to make lot of things faster.







And the decide to Modify the game:

There will nothing special if i make the mod as the way i usually do. Just made and shared. But in this time, i tried mods created by others in the past before where official game files were modified to change the game mechanics be it increasing gold, experience points, etc. With the same mindset, I tried searching online if any have already create a mod for this game. There was none (excluding all the sketchy sites).





And i feel that, it should be something abnormal with this game. Or maybe it is so hard to mod. And my premonition was right. So, It is time for some DIY.

The 1st step, you have to download the game from Official Page. Because if you do not have it, how can you mod it. Am i Correct?

The approach I took can be summarised in the following steps.

  1. Download original APK onto computer
  2. Decompile APK
  3. Explore and understand decompiled files and directories
  4. Modify files to suit my needs (multiply fishes earned for example)
  5. Recompile APK
  6. Install recompiled APK on Android
  7. Test


Step 1 — Downloading Original APK



I used the website https://apkcombo.com/apk-downloader/ and inserted the link of Animal Restaurant and pressed the Download button. A link will be generated and I saved the file to my computer as animal_restaurant.apk.



Step 2 — Decompile APK



With the APK, I used apktool to decompile the file.





apktool d animal_restaurant.apk

A folder animal_restaurant will be created and its directory should look like this.

|-- AndroidManifest.xml
|-- apktool.yml
|-- assets
|-- build
|-- dist
|-- lib
|-- original
|-- res
|-- smali
|-- smali_assets
|-- smali_classes2
`-- unknown


Step 3 — Explore and understand decompiled files and directories

As this is a game, most likely it is using some game engine. At the back of my mind, I guess it could be one of the widely used game engines — Unity or Cocos2d. Entering the lib folder, there is a file libcocos2djs.so . This confirms that it is using the game engine Cocos2d . And as usual, maybe you will drop the binary .so file to IDA Pro and waiting for it until the disassembly process completed. And then? Nothing can do with that file. 

Yes, i can say that. You can try to search something like that: fish, goods, money, gold, gems... bla and bla.... but the result is nothing.

What should i do now? Give up????



I don’t have any game programming background so everything is based on my guess and inference.

I remembered reading a little about cocos2d in the past and that it has supports javascript bindings and the library file libcocos2djs.so has the letters js at the back. Hence I guessed that the source code of the game is mainly in javascript. With that, I went looking for files with the extensions.js .

Having no clue with the directory structure, I entered everyone of them and came across something interesting within assets/subpackages/Script/index.js . The file size was 1.2MB and it look like a compiled source of the game. Below is a short snippet of what is inside.

window.__require = function e(t, i, n) {
function a(s, r) {
if (!i[s]) {
if (!t[s]) {
var c = s.split("/");
c = c[c.length - 1];
if (!t[c]) {
var l = "function" == typeof __require && __require;
if (!r && l) return l(c, !0);
if (o) return o(c, !0);
throw new Error("Cannot find module '" + s + "'");
}
}
var h = i[s] = {
exports: {}
};
t[s][0].call(h.exports, function(e) {
return a(t[s][1][e] || e);
}, h, h.exports, e, t, i, n);
}
return i[s].exports;
}
for (var o = "function" == typeof __require && __require, s = 0; s < n.length; s++) a(n[s]);
return a;
}({
BadGuyTree: [ function(e, t, i) {
"use strict";
cc._RF.push(t, "58e96Ka3atHYZILGluICrpu", "BadGuyTree");
Object.defineProperty(i, "__esModule", {
value: !0
});
i.default = {
name: "Root",
children: [ {
name: "Selector",
children: [ {
name: "isComing",
children: [ {
name: "Parallel",
children: [ {
name: "Action",
children: [],
type: "Action",
func: "randomPoint"
}, {
name: "Action",
children: [],
type: "Action",
func: "coming"
} ],
type: "Parallel",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "isLeaving",
children: [ {
name: "Parallel",
children: [ {
name: "Action",
children: [],
type: "Action",
func: "randomPoint"
}, {
name: "Action",
children: [],
type: "Action",
func: "leaving"
} ],
type: "Parallel",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "isHostility",
children: [ {
name: "Selector",
children: [ {
name: "isEmptyHP",
children: [ {
name: "Selector",
children: [ {
name: "isPlot",
children: [],
type: "ConditionAction",
func: "cry"
}, {
name: "Parallel",
children: [ {
name: "Action",
children: [],
type: "Action",
func: "randomPoint"
}, {
name: "Action",
children: [],
type: "Action",
func: "leaving"
} ],
type: "Parallel",
func: ""
} ],
type: "Selector",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "Selector",
children: [ {
name: "isTranslateScene",
children: [ {
name: "Selector",
children: [ {
name: "isToDiningRoom",
children: [],
type: "ConditionAction",
func: "walkToDining"
}, {
name: "isToKitchenRoom",
children: [],
type: "ConditionAction",
func: "walkToKitchen"
}, {
name: "isToGarden",
children: [],
type: "ConditionAction",
func: "walkToGarden"
}, {
name: "isToBuffetRoom",
children: [],
type: "ConditionAction",
func: "walkToBuffet"
}, {
name: "isToPond",
children: [],
type: "ConditionAction",
func: "walkToPond"
} ],
type: "Selector",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "Selector",
children: [ {
name: "isHaveTarget",
children: [ {
name: "Parallel",
children: [ {
name: "Action",
children: [],
type: "Action",
func: "randomPoint"
}, {
name: "Action",
children: [],
type: "Action",
func: "walk"
} ],
type: "Parallel",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "Selector",
children: [ {
name: "isEmployee",
children: [ {
name: "Selector",
children: [ {
name: "isArrive",
children: [],
type: "ConditionAction",
func: "kick"
}, {
name: "Action",
children: [],
type: "Action",
func: "walk"
} ],
type: "Selector",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "isPot",
children: [ {
name: "Selector",
children: [ {
name: "isArrive",
children: [],
type: "ConditionAction",
func: "spit"
}, {
name: "Action",
children: [],
type: "Action",
func: "walk"
} ],
type: "Selector",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "isTargetNone",
children: [],
type: "ConditionAction",
func: "targetNone"
} ],
type: "Selector",
func: ""
} ],
type: "Selector",
func: ""
} ],
type: "Selector",
func: ""
} ],
type: "Selector",
func: ""
} ],
type: "Filter",
func: ""
}, {
name: "isWelfare",
children: [],
type: "ConditionAction",
func: "welfare"
} ],
type: "Selector",
func: ""
} ],
func: ""
};


The code is not beautiful for us, but luckily the object and variable names were comprehendible. If you are not good at javascript, you can make the script more beautiful by using a tool with name "Online JavaScript Beautifier". Just simply copy and paste the index.js code and click the "beautify code" button. You can see it like this


The code will look like this

window.__require = function e(t, i, n) {
    function a(s, r) {
        if (!i[s]) {
            if (!t[s]) {
                var c = s.split("/");
                c = c[c.length - 1];
                if (!t[c]) {
                    var l = "function" == typeof __require && __require;
                    if (!r && l) return l(c, !0);
                    if (o) return o(c, !0);
                    throw new Error("Cannot find module '" + s + "'");
                }
            }
            var h = i[s] = {
                exports: {}
            };
            t[s][0].call(h.exports, function(e) {
                return a(t[s][1][e] || e);
            }, h, h.exports, e, t, i, n);
        }
        return i[s].exports;
    }
    for (var o = "function" == typeof __require && __require, s = 0; s < n.length; s++) a(n[s]);
    return a;
}({
    BadGuyTree: [function(e, t, i) {
        "use strict";
        cc._RF.push(t, "58e96Ka3atHYZILGluICrpu", "BadGuyTree");
        Object.defineProperty(i, "__esModule", {
            value: !0
        });
        i.default = {
            name: "Root",
            children: [{
                name: "Selector",
                children: [{
                    name: "isComing",
                    children: [{
                        name: "Parallel",
                        children: [{
                            name: "Action",
                            children: [],
                            type: "Action",
                            func: "randomPoint"
                        }, {
                            name: "Action",
                            children: [],
                            type: "Action",
                            func: "coming"
                        }],
                        type: "Parallel",
                        func: ""
                    }],
                    type: "Filter",
                    func: ""
                }, {
                    name: "isLeaving",
                    children: [{
                        name: "Parallel",
                        children: [{
                            name: "Action",
                            children: [],
                            type: "Action",
                            func: "randomPoint"
                        }, {
                            name: "Action",
                            children: [],
                            type: "Action",
                            func: "leaving"
                        }],
                        type: "Parallel",
                        func: ""
                    }],
                    type: "Filter",
                    func: ""
                }, {
                    name: "isHostility",
                    children: [{
                        name: "Selector",
                        children: [{
                            name: "isEmptyHP",
                            children: [{
                                name: "Selector",
                                children: [{
                                    name: "isPlot",
                                    children: [],
                                    type: "ConditionAction",
                                    func: "cry"
                                }, {
                                    name: "Parallel",
                                    children: [{
                                        name: "Action",
                                        children: [],
                                        type: "Action",
                                        func: "randomPoint"
                                    }, {
                                        name: "Action",
                                        children: [],
                                        type: "Action",
                                        func: "leaving"
                                    }],
                                    type: "Parallel",
                                    func: ""
                                }],
                                type: "Selector",
                                func: ""
                            }],
                            type: "Filter",
                            func: ""
                        }, {
                            name: "Selector",
                            children: [{
                                name: "isTranslateScene",
                                children: [{
                                    name: "Selector",
                                    children: [{
                                        name: "isToDiningRoom",
                                        children: [],
                                        type: "ConditionAction",
                                        func: "walkToDining"
                                    }, {
                                        name: "isToKitchenRoom",
                                        children: [],
                                        type: "ConditionAction",
                                        func: "walkToKitchen"
                                    }, {
                                        name: "isToGarden",
                                        children: [],
                                        type: "ConditionAction",
                                        func: "walkToGarden"
                                    }, {
                                        name: "isToBuffetRoom",
                                        children: [],
                                        type: "ConditionAction",
                                        func: "walkToBuffet"
                                    }, {
                                        name: "isToPond",
                                        children: [],
                                        type: "ConditionAction",
                                        func: "walkToPond"
                                    }],
                                    type: "Selector",
                                    func: ""
                                }],
                                type: "Filter",
                                func: ""
                            }, {
                                name: "Selector",
                                children: [{
                                    name: "isHaveTarget",
                                    children: [{
                                        name: "Parallel",
                                        children: [{
                                            name: "Action",
                                            children: [],
                                            type: "Action",
                                            func: "randomPoint"
                                        }, {
                                            name: "Action",
                                            children: [],
                                            type: "Action",
                                            func: "walk"
                                        }],
                                        type: "Parallel",
                                        func: ""
                                    }],
                                    type: "Filter",
                                    func: ""
                                }, {
                                    name: "Selector",
                                    children: [{
                                        name: "isEmployee",
                                        children: [{
                                            name: "Selector",
                                            children: [{
                                                name: "isArrive",
                                                children: [],
                                                type: "ConditionAction",
                                                func: "kick"
                                            }, {
                                                name: "Action",
                                                children: [],
                                                type: "Action",
                                                func: "walk"
                                            }],
                                            type: "Selector",
                                            func: ""
                                        }],
                                        type: "Filter",
                                        func: ""
                                    }, {
                                        name: "isPot",
                                        children: [{
                                            name: "Selector",
                                            children: [{
                                                name: "isArrive",
                                                children: [],
                                                type: "ConditionAction",
                                                func: "spit"
                                            }, {
                                                name: "Action",
                                                children: [],
                                                type: "Action",
                                                func: "walk"
                                            }],
                                            type: "Selector",
                                            func: ""
                                        }],
                                        type: "Filter",
                                        func: ""
                                    }, {
                                        name: "isTargetNone",
                                        children: [],
                                        type: "ConditionAction",
                                        func: "targetNone"
                                    }],
                                    type: "Selector",
                                    func: ""
                                }],
                                type: "Selector",
                                func: ""
                            }],
                            type: "Selector",
                            func: ""
                        }],
                        type: "Selector",
                        func: ""
                    }],
                    type: "Filter",
                    func: ""
                }, {
                    name: "isWelfare",
                    children: [],
                    type: "ConditionAction",
                    func: "welfare"
                }],
                type: "Selector",
                func: ""
            }],
            func: ""
        };
        t.exports = i.default;
        cc._RF.pop();
    }, 

Scrolling through the file, it I was certain that it is indeed the source files which handles the game logic. Hence I searched for money , gold , coins, Fish, Gems ... and found a function createCoins which is used by multiple objects.

Step 4 — Modify files

With the known function which I suppose create the fish when the customer is done eating, I searched for the function declaration and saw the following snippet. Guessing that the itemValue is what the customer pays, I tried to modify the line to t.itemValue = e + 9999; . Guest that If this modification works, I should be able to see the effect when I collect the fish in the game.

createCoins: function(e, t) {
t.itemType = "coin";
t.itemValue = e * 100;
t.callback && t.target && (t.callback = t.callback.bind(t.target));
return this.createItem(t);
},
createCandys: function(e, t) {
t.itemType = "candy";
t.itemValue = e * 1000;
t.callback && t.target && (t.callback = t.callback.bind(t.target));
return this.createItem(t);
},
createLoves: function(e, t) {
t.itemValue = e * 1000;
t.callback && t.target && (t.callback = t.callback.bind(t.target));
return this.createItem(t);
},
createStars: function(e, t) {
if (o.banCheating || o.palyTimeUpFlag) return cc.v2(0, 0);
t.itemType = "star";
t.itemValue = e * 1000;
t.callback && t.target && (t.callback = t.callback.bind(t.target));
return this.createItem(t);
},
createMemorial: function(e, t) {
e.itemType = "memorial";
e.callback && e.target && (e.callback = e.callback.bind(e.target));
var i = this.poolManager.getItemNode(e.itemType), n = i.getComponent("memorialItem").init(e, this.gardenManager, t, this);
if (e.parent) i.parent = e.parent; else {
e.garden ? i.parent = this.garden : i.parent = this.diningRoom;
i.x = n.x;
i.y = n.y;
}

I will try to make the fish * 100, star * 1000, candy * 1000 and love * 1000.

But it seems some thing is missing here. Check the game and i found that there is still miss a thing with name "Plate". So, i tried to find createPlates: function(e, t)  and found it:


createPlates: function(e, t) {
if (!c.banCheating && !c.palyTimeUpFlag) {
(t = c.deepClone(t)).itemType || (t.itemType = "plate");
t.itemValue = e * 1000;
o.sendMessage("createItem", t);
}
},

Change the value to e*1000. Spending sometime to find more and i found lot of interesting function. Here are examples:

dailyTaskData: [ function(e, t, i) {: This code contain data for daily task.




BoothOwner: {: This code contain data for hiring staff.

claimTip: function() { This code contain data for Tip from customer (Include tips and Buffet)

achieveData: [ function(e, t, i) { This code contain data for rewards from Achivement.

broadcastData: [ function(e, t, i) { This code contain data for Broadcast promotion. In this Function, change these information:

NEED_BD_COUNT: 1,
needStar: 1
It will allow you upgrade broadcast promotion without need star, and just 1 click to promote a new customer.

Here are the keyword for searching

needStar:
businessHours:
needCustomer:
unlockMoney:
unlockTime:

And the keyword to make the rewards:

rewardType: "plate",
rewardType: "star"
rewardType: "meal"

You can control the data form game. Just change what you want to get the needed resource.

When you change everything, it will be perfect to imagine the the code will work. But there is not simple like that. I suddenly remembered that the game went through some hot updates and that could have cause an override in the files I modified. With that though in mind, I searched for cocos2d hot update and came across this page.

It states that there is a manifest which used to compare local and remote assets for any updates. Doing a fresh install, I noticed that the version I have is v6.2.4.g . After an update it became v6.2.8.g .

The manifest contains the following values and the key remoteVersionUrl caught my attention.

{
    "packageUrl": The local cache root path of the remote asset
    "remoteVersionUrl": [optional] the path of the remote version of the file used to determine whether the server has a new version of the assets
    "remoteManifestUrl": Path of the remote asset manifest file, including version information and all asset information
    "version": the version of the asset
    "engineVersion": engine version
    "assets": all asset lists
        "key": the relative path of the asset (relative to the asset root)
        "md5": The md5 value represents the version information of the asset file
        "compressed": [optional] If the value is true, the file is automatically downloaded after being extracted, currently only supports zip compression format
        "size": [optional] The byte size of the file used to quickly get progress information
    "searchPaths": A list of search paths that need to be added to FileUtils
}
So, what does it mean? The game will auto update anytime i run it. If the js file is different than the server, it will auto download the new script and BANG, nothing happen and the modificated APK mean Nothing.

To prevent Hot update, we have to find the manifest file and change it. I tried to find the keyword "remoteVersionUrl" in project and found it

99447321-8016-4672-a8c4-ba13c7346ec2.manifest
The file contained the path of the remote server and the path of all assets alongside with the file signature. If I can modify the file and point the update to download from my own server I can effectively upload my own modified index.js in the game.



With that direction, changed packageUrl , remoteManifestUrl and remoteVersionUrl .

Open that file and you can see that




Yes, that is it. Just try to edit them and it will look like:

{"version":"6.4.10","packageUrl":"https://only4free.net/appAndroidGlobal","remoteManifestUrl":"https://only4free.net/appAndroidGlobal/project.manifest","remoteVersionUrl":"https://only4free.net/appAndroidGlobal/version.manifest



Step 5 — Recompile APK

Saving my modifications, the next step is to recompile the apk. I used apktool to recompile.

apk b -f animal_restaurant> I: Using Apktool 2.4.1
> I: Smaling smali folder into classes.dex...
> I: Smaling smali_assets folder into assets.dex...
> I: Smaling smali_classes2 folder into classes2.dex...
> I: Building resources...
> I: Copying libs... (/lib)
> I: Building apk file...
> I: Copying unknown files/dir...
> I: Built apk...
Once done, the recompiled apk can be found in the folder animal_restaurant/dist/animal_restaurant.apk . Before we can install it on our Android device, we have to sign the APK.

1. Generate our cert.

keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000

2. Sign apk

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore animal_restaurant/dist/animal_restaurant.apk alias_name

Step 6 — Install recompiled APK on Android

There are different ways to install an APK on android. As I have Developer Options enabled, I can use adb to install the apk.

adb install animal_restaurant/dist/animal_restaurant.apk

You can just easy to copy it to android device and install it using a file manager. Both works fine.

Step 7 — Test

After finishing all edit, i tried the game again. The fish paid from customer was multiple 100 times. Just tried to buy some furniture to get 25 stars. And BANG, the daily task was unlocked, also with achivement. I can get unlimited resource from achivement as i want.






Here are summary information which i did:

Fish * 10
Candy * 1000
Love * 1000
Star * 1000
Tips * 1000
Tips Buffet = 90000. Can collect anytime you want.
Plates * 1000
remove limit star to hire staff, upgrade promottion
Number of touch to promote new customer = 1
Cheat Achivement Rewards: 999999999
Disable Anti Cheat & Auto Ban

You can watch the gameplay in this video

2 comments: