An example : ADD 10 MORE BLACK BIRDS!

  1. Bookmark following bookmarklet.
  2. Go to http://chrome.angrybirds.com/
  3. Wait for loading page. (Do not select a level here...)
  4. Click the bookmarklet from your Bookmarks.
  5. Select a level to play!

Original source code

(function () {
    var RealXMLHttpRequest = XMLHttpRequest;
    var FakeXMLHttpRequest = function () {
        this._req = new RealXMLHttpRequest();
    };
    FakeXMLHttpRequest.prototype = {
        abort: function () {
            this._req.abort.apply(this._req, arguments);
        },
        getAllResponseHeaders: function () {
            return this._req.getAllResponseHeaders.apply(this._req, arguments);
        },
        open: function () {
            this._url = arguments[1];
            this._req.open.apply(this._req, arguments);
        },
        overrideMimeType: function () {
            this._req.overrideMimeType.apply(this._req, arguments);
        },
        send: function () {
            this._req.send.apply(this._req, arguments);
        },
        sendAsBinary: function () {
            this._req.sendAsBinary.apply(this._req, arguments);
        },
        setRequestHeader: function () {
            this._req.setRequestHeader.apply(this._req, arguments);
        },

        get multipart() {
            return this._req.multipart;
        },
        set multipart(value) {
            this._req.multipart = value;
        },
        get onreadystatechange() {
            return this._req.onreadystatechange;
        },
        set onreadystatechange(value) {
            this._req.onreadystatechange = value;
        },
        get readyState() {
            return this._req.readyState;
        },
        get response() {
            return this._req.response;
        },
        get responseText() {
            return this._hookResponseText(this._req.responseText);
        },
        get responseXML() {
            return this._req.responseXML;
        },
        get status() {
            return this._req.status;
        },
        get statusText() {
            return this._req.statusText;
        },
        get upload() {
            return this._req.upload;
        },
        get withCredentials() {
            return this._req.withCredentials;
        },

        _hookResponseText: function (responseText) {
            try {
                if (/Level[0-9]+.json$/.test(this._url)) {
                    var jsonData = JSON.parse(responseText);
                    jsonData = this._modifyAngryBirdLevel(jsonData);
                    responseText = JSON.stringify(jsonData);
                }
            } catch (ex) {
            }
            return responseText;
        },
        _modifyAngryBirdLevel: function(jsonData) {
            // Add 10 more BIRD_BLACK.
            for (var i = 0; i < 10; i++) {
                jsonData.counts.birds ++;
                jsonData.world["bird_" + jsonData.counts.birds] = {
                    "angle": 0,
                    "id": "BIRD_BLACK",
                    "x": jsonData.world["bird_1"].x,
                    "y": jsonData.world["bird_1"].y
                };
            }
            return jsonData;
        },

        dummy: null
    };
    window.XMLHttpRequest = FakeXMLHttpRequest;
})();

Principle: Hooking XMLHttpRequest

Angry Birds levels (Stage data) are defined with JSON file. For example, "http://chrome.angrybirds.com/angrybirds/json/Level47.json"

The web app loads the JSON file when you start playing a level. XMLHttpRequest is used for loading the JSON file.

So, I hooked XMLHttpRequest class to modify the level data. XMLHttpRequest#responseText returns modified JSON text data by my "_modifyAngryBirdLevel()" method.

Recipes

You can also modify blocks. Following example converts stone and wood blocks to ice blocks.

        _modifyAngryBirdLevel: function(jsonData) {
            // ...
            
            // Add 10 more BIRD_BLACK.
            for (var i = 1; i < jsonData.counts.blocks; i++) {
                jsonData.world["block_" + i].id = jsonData.world["block_" + i].id.replace(/(STONE|WOOD)_BLOCK/, "ICE_BLOCK");
            }
            return jsonData;
        },

You can also make your own custom levels!!

        _modifyAngryBirdLevel: function(jsonData) {
            // A very very simple level: 1 black bird vs 1 pig.
            return {
              "camera": [
                 {
                    "bottom": -45,
                    "id": "Slingshot",
                    "left": -60,
                    "right": 4,
                    "top": -45,
                    "x": 30,
                    "y": -4
                 },
                 {
                    "bottom": -45,
                    "id": "Castle",
                    "left": 10,
                    "right": 60,
                    "top": -45,
                    "x": 30,
                    "y": -4
                 }
              ],
              "counts": {
                 "birds": 1,
                 "blocks": 1
              },
              "id": jsonData.id,
              "scoreEagle": 40000,
              "scoreGold": 60000,
              "scoreSilver": 50000,
              "theme": "BACKGROUND_BLUE_GRASS",
              "world": {
                "bird_1": {
                  "angle": 0,
                  "id": "BIRD_BLACK",
                  "x": 10,
                  "y": -1
                },
                "block_1": {
                  "angle": 0,
                  "id": "PIG_BASIC_MEDIUM",
                  "x": 50,
                  "y": -1
                }
              }
            };
        },

Note

I think ROVIO will encrypt all level files to avoid this hack ...

See also