Client.JClassPatcher.java Source code

Java tutorial

Introduction

Here is the source code for Client.JClassPatcher.java

Source

/**
 *   rscplus
 *
 *   This file is part of rscplus.
 *
 *   rscplus is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   rscplus is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with rscplus.  If not, see <http://www.gnu.org/licenses/>.
 *
 *   Authors: see <https://github.com/OrN/rscplus>
 */

package Client;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;

/**
 * Singleton class which hooks variables and patches classes.
 */
public class JClassPatcher {

    // Singleton
    private static JClassPatcher instance = null;

    private Printer printer = new Textifier();
    private TraceMethodVisitor mp = new TraceMethodVisitor(printer);

    private JClassPatcher() {
        // Empty private constructor to prevent extra instances from being created.
    }

    public byte[] patch(byte[] data) {
        ClassReader reader = new ClassReader(data);
        ClassNode node = new ClassNode();
        reader.accept(node, ClassReader.SKIP_DEBUG);

        if (node.name.equals("ua"))
            patchRenderer(node);
        else if (node.name.equals("e"))
            patchApplet(node);
        else if (node.name.equals("qa"))
            patchMenu(node);
        else if (node.name.equals("m"))
            patchData(node);
        else if (node.name.equals("client"))
            patchClient(node);
        else if (node.name.equals("f"))
            patchRandom(node);

        // Patch applied to all classes
        patchGeneric(node);

        if (Settings.DISASSEMBLE) {
            Logger.Info("Disassembling file: " + node.name + ".class");
            dumpClass(node);
        }

        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        node.accept(writer);
        return writer.toByteArray();
    }

    private void patchGeneric(ClassNode node) {
        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            hookClassVariable(methodNode, "ua", "fb", "Ljava/awt/image/ImageConsumer;", "Game/Renderer",
                    "image_consumer", "Ljava/awt/image/ImageConsumer;", true, true);
            hookClassVariable(methodNode, "ua", "u", "I", "Game/Renderer", "width", "I", false, true);
            hookClassVariable(methodNode, "ua", "k", "I", "Game/Renderer", "height", "I", false, true);
            hookClassVariable(methodNode, "ua", "rb", "[I", "Game/Renderer", "pixels", "[I", true, true);

            hookClassVariable(methodNode, "e", "Ob", "Ljava/lang/String;", "Game/Client", "pm_enteredText",
                    "Ljava/lang/String;", true, true);
            hookClassVariable(methodNode, "client", "Ob", "Ljava/lang/String;", "Game/Client", "pm_enteredText",
                    "Ljava/lang/String;", true, true);
            hookClassVariable(methodNode, "e", "x", "Ljava/lang/String;", "Game/Client", "pm_text",
                    "Ljava/lang/String;", true, true);
            hookClassVariable(methodNode, "client", "x", "Ljava/lang/String;", "Game/Client", "pm_text",
                    "Ljava/lang/String;", true, true);

            hookClassVariable(methodNode, "client", "li", "Lba;", "Game/Renderer", "instance", "Ljava/lang/Object;",
                    true, false);

            hookClassVariable(methodNode, "ba", "u", "I", "Game/Renderer", "width", "I", false, true);
            hookClassVariable(methodNode, "ba", "k", "I", "Game/Renderer", "height", "I", false, true);
            hookClassVariable(methodNode, "ba", "rb", "[I", "Game/Renderer", "pixels", "[I", true, true);

            hookStaticVariable(methodNode, "ua", "h", "[Ljava/lang/String;", "Game/Client", "friends",
                    "[Ljava/lang/String;");
            hookStaticVariable(methodNode, "n", "g", "I", "Game/Client", "friends_count", "I");

            hookClassVariable(methodNode, "client", "Wd", "I", "Game/Renderer", "width", "I", false, true);
            hookClassVariable(methodNode, "client", "Oi", "I", "Game/Renderer", "height_client", "I", false, true);

            hookClassVariable(methodNode, "e", "m", "I", "Game/Renderer", "width", "I", false, true);
            hookClassVariable(methodNode, "e", "a", "I", "Game/Renderer", "height", "I", false, true);

            hookClassVariable(methodNode, "lb", "pb", "[I", "Game/Renderer", "pixels", "[I", true, true);

            hookStaticVariable(methodNode, "client", "il", "[Ljava/lang/String;", "Game/Client", "strings",
                    "[Ljava/lang/String;");

            hookStaticVariable(methodNode, "ac", "x", "[Ljava/lang/String;", "Game/Item", "item_name",
                    "[Ljava/lang/String;");

            hookClassVariable(methodNode, "lb", "Mb", "I", "Game/Camera", "distance1", "I", false, true);
            hookClassVariable(methodNode, "lb", "X", "I", "Game/Camera", "distance2", "I", false, true);
            hookClassVariable(methodNode, "lb", "P", "I", "Game/Camera", "distance3", "I", false, true);
            hookClassVariable(methodNode, "lb", "G", "I", "Game/Camera", "distance4", "I", false, true);

            hookClassVariable(methodNode, "client", "cl", "I", "Game/Client", "max_inventory", "I", true, false);
            hookClassVariable(methodNode, "client", "oh", "[I", "Game/Client", "current_level", "[I", true, false);
            hookClassVariable(methodNode, "client", "cg", "[I", "Game/Client", "base_level", "[I", true, false);
            hookClassVariable(methodNode, "client", "Vk", "[Ljava/lang/String;", "Game/Client", "skill_name",
                    "[Ljava/lang/String;", true, false);
            hookClassVariable(methodNode, "client", "Ak", "[I", "Game/Client", "xp", "[I", true, false);
            hookClassVariable(methodNode, "client", "vg", "I", "Game/Client", "fatigue", "I", true, false);
            hookClassVariable(methodNode, "client", "Fg", "I", "Game/Client", "combat_style", "I", true, true);
            if (Settings.SAVE_LOGININFO)
                hookClassVariable(methodNode, "client", "Xd", "I", "Game/Client", "login_screen", "I", false, true);

            hookClassVariable(methodNode, "client", "Ek", "Llb;", "Game/Camera", "instance", "Ljava/lang/Object;",
                    true, false);
            hookClassVariable(methodNode, "client", "qd", "I", "Game/Camera", "fov", "I", false, true);

            hookClassVariable(methodNode, "client", "ai", "I", "Game/Client", "combat_timer", "I", true, false);
            hookClassVariable(methodNode, "client", "Fe", "Z", "Game/Client", "show_bank", "Z", true, false);
            hookClassVariable(methodNode, "client", "dd", "Z", "Game/Client", "show_duel", "Z", true, false);
            hookClassVariable(methodNode, "client", "Pj", "Z", "Game/Client", "show_duelconfirm", "Z", true, false);
            hookClassVariable(methodNode, "client", "Bj", "I", "Game/Client", "show_friends", "I", true, true);
            hookClassVariable(methodNode, "client", "qc", "I", "Game/Client", "show_menu", "I", true, false);
            hookClassVariable(methodNode, "client", "Ph", "Z", "Game/Client", "show_questionmenu", "Z", true,
                    false);
            hookClassVariable(methodNode, "client", "Vf", "I", "Game/Client", "show_report", "I", true, false);
            hookClassVariable(methodNode, "client", "uk", "Z", "Game/Client", "show_shop", "Z", true, false);
            hookClassVariable(methodNode, "client", "Qk", "Z", "Game/Client", "show_sleeping", "Z", true, false);
            hookClassVariable(methodNode, "client", "Hk", "Z", "Game/Client", "show_trade", "Z", true, false);
            hookClassVariable(methodNode, "client", "Xj", "Z", "Game/Client", "show_tradeconfirm", "Z", true,
                    false);
            hookClassVariable(methodNode, "client", "Oh", "Z", "Game/Client", "show_welcome", "Z", true, false);

            hookClassVariable(methodNode, "client", "Qd", "Ljava/lang/String;", "Game/Client", "pm_username",
                    "Ljava/lang/String;", true, true);

            hookClassVariable(methodNode, "client", "wh", "Ljava/lang/String;", "Game/Client", "username_login",
                    "Ljava/lang/String;", true, true);

            hookClassVariable(methodNode, "client", "lc", "I", "Game/Client", "inventory_count", "I", true, false);
            hookClassVariable(methodNode, "client", "vf", "[I", "Game/Client", "inventory_items", "[I", true,
                    false);

            hookClassVariable(methodNode, "client", "ug", "I", "Game/Camera", "rotation", "I", true, true);
            hookClassVariable(methodNode, "client", "ac", "I", "Game/Camera", "zoom", "I", false, true);

            // Chat menu
            hookClassVariable(methodNode, "client", "yd", "Lqa;", "Game/Menu", "chat_menu", "Ljava/lang/Object;",
                    true, false);
            hookClassVariable(methodNode, "client", "Fh", "I", "Game/Menu", "chat_type1", "I", true, false);
            hookClassVariable(methodNode, "client", "bh", "I", "Game/Menu", "chat_input", "I", true, false);
            hookClassVariable(methodNode, "client", "ud", "I", "Game/Menu", "chat_type2", "I", true, false);
            hookClassVariable(methodNode, "client", "mc", "I", "Game/Menu", "chat_type3", "I", true, false);

            // Quest menu
            hookClassVariable(methodNode, "client", "fe", "Lqa;", "Game/Menu", "quest_menu", "Ljava/lang/Object;",
                    true, false);
            hookClassVariable(methodNode, "client", "lk", "I", "Game/Menu", "quest_handle", "I", true, false);

            // Friends menu
            hookClassVariable(methodNode, "client", "zk", "Lqa;", "Game/Menu", "friend_menu", "Ljava/lang/Object;",
                    true, false);
            hookClassVariable(methodNode, "client", "Hi", "I", "Game/Menu", "friend_handle", "I", true, false);

            // Spell menu
            hookClassVariable(methodNode, "client", "Mc", "Lqa;", "Game/Menu", "spell_menu", "Ljava/lang/Object;",
                    true, false);
            hookClassVariable(methodNode, "client", "Ud", "I", "Game/Menu", "spell_handle", "I", true, false);

            // Player name
            hookClassVariable(methodNode, "client", "wi", "Lta;", "Game/Client", "player_object",
                    "Ljava/lang/Object;", true, false);
            hookClassVariable(methodNode, "client", "Qg", "I", "Game/Client", "regionX", "I", true, false);
            hookClassVariable(methodNode, "client", "zg", "I", "Game/Client", "regionY", "I", true, false);

            // Client version
            hookStaticVariable(methodNode, "fa", "d", "I", "Game/Client", "version", "I");
        }
    }

    private void patchMenu(ClassNode node) {
        Logger.Info("Patching menu (" + node.name + ".class)");

        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            // Menu swap hook
            if (methodNode.name.equals("e") && methodNode.desc.equals("(II)V")) {
                AbstractInsnNode first = methodNode.instructions.getFirst();

                LabelNode label = new LabelNode();
                methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 0));
                methodNode.instructions.insertBefore(first, new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Menu",
                        "switchList", "(Ljava/lang/Object;)Z"));
                methodNode.instructions.insertBefore(first, new JumpInsnNode(Opcodes.IFGT, label));
                methodNode.instructions.insertBefore(first, new InsnNode(Opcodes.RETURN));
                methodNode.instructions.insertBefore(first, label);
            }
        }
    }

    private void patchData(ClassNode node) {
        Logger.Info("Patching data (" + node.name + ".class)");

        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            if (methodNode.name.equals("a") && methodNode.desc.equals("([BBZ)V")) {
                // Data hook patches
                AbstractInsnNode lastNode = methodNode.instructions.getLast();
                methodNode.instructions.insertBefore(lastNode,
                        new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Item", "patchItemNames", "()V", false));
            }
        }
    }

    private void patchApplet(ClassNode node) {
        Logger.Info("Patching applet (" + node.name + ".class)");

        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            if (methodNode.name.equals("run") && methodNode.desc.equals("()V")) {
                // Mouse and keyboard listener hooks
                AbstractInsnNode findNode = methodNode.instructions.getFirst();
                for (;;) {
                    AbstractInsnNode next = findNode.getNext();

                    if (next == null)
                        break;

                    if (findNode.getOpcode() == Opcodes.ALOAD && next.getOpcode() == Opcodes.ALOAD) {
                        AbstractInsnNode invokeNode = next.getNext();
                        MethodInsnNode invoke = (MethodInsnNode) invokeNode;
                        methodNode.instructions.remove(next);
                        methodNode.instructions.remove(invokeNode);
                        if (invoke.name.equals("addMouseListener"))
                            methodNode.instructions.insert(findNode, new FieldInsnNode(Opcodes.PUTSTATIC,
                                    "Game/MouseHandler", "listener_mouse", "Ljava/awt/event/MouseListener;"));
                        else if (invoke.name.equals("addMouseMotionListener"))
                            methodNode.instructions.insert(findNode,
                                    new FieldInsnNode(Opcodes.PUTSTATIC, "Game/MouseHandler",
                                            "listener_mouse_motion", "Ljava/awt/event/MouseMotionListener;"));
                        else if (invoke.name.equals("addKeyListener"))
                            methodNode.instructions.insert(findNode, new FieldInsnNode(Opcodes.PUTSTATIC,
                                    "Game/KeyboardHandler", "listener_key", "Ljava/awt/event/KeyListener;"));
                    }
                    findNode = findNode.getNext();
                }
            }
        }
    }

    private void patchClient(ClassNode node) {
        Logger.Info("Patching client (" + node.name + ".class)");

        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            // I (I)V is where most of the interface is processed
            if (methodNode.name.equals("I") && methodNode.desc.equals("(I)V")) {
                // Show combat menu
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    if (insnNode.getOpcode() == Opcodes.BIPUSH) {
                        IntInsnNode bipush = (IntInsnNode) insnNode;

                        if (bipush.operand == -9) {
                            AbstractInsnNode findNode = insnNode;
                            while (findNode.getOpcode() != Opcodes.IF_ICMPEQ)
                                findNode = findNode.getNext();

                            LabelNode label = ((JumpInsnNode) findNode).label;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Client/Settings", "COMBAT_MENU", "Z"));
                            methodNode.instructions.insertBefore(insnNode, new JumpInsnNode(Opcodes.IFGT, label));
                            break;
                        }
                    }
                }
            } else if (methodNode.name.equals("J") && methodNode.desc.equals("(I)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Chat command patch
                    if (insnNode.getOpcode() == Opcodes.SIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 627) {
                            AbstractInsnNode jmpNode = insnNode;
                            while (jmpNode.getOpcode() != Opcodes.IFEQ)
                                jmpNode = jmpNode.getNext();

                            AbstractInsnNode insertNode = insnNode;
                            while (insertNode.getOpcode() != Opcodes.INVOKEVIRTUAL)
                                insertNode = insertNode.getPrevious();

                            JumpInsnNode jmp = (JumpInsnNode) jmpNode;
                            methodNode.instructions.insert(insertNode, new VarInsnNode(Opcodes.ASTORE, 2));
                            methodNode.instructions.insert(insertNode, new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "Game/Client", "processChatCommand", "(Ljava/lang/String;)Ljava/lang/String;"));
                            methodNode.instructions.insert(insertNode, new VarInsnNode(Opcodes.ALOAD, 2));
                        }
                    }
                }
            } else if (methodNode.name.equals("h") && methodNode.desc.equals("(B)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Private chat command patch
                    if (insnNode.getOpcode() == Opcodes.GETFIELD) {
                        FieldInsnNode field = (FieldInsnNode) insnNode;
                        if (field.owner.equals("client") && field.name.equals("Ob")
                                && insnNode.getPrevious().getPrevious().getOpcode() != Opcodes.INVOKEVIRTUAL) {
                            insnNode = insnNode.getPrevious().getPrevious();
                            methodNode.instructions.insert(insnNode,
                                    new FieldInsnNode(Opcodes.PUTFIELD, "client", "Ob", "Ljava/lang/String;"));
                            methodNode.instructions.insert(insnNode,
                                    new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client", "processPrivateCommand",
                                            "(Ljava/lang/String;)Ljava/lang/String;"));
                            methodNode.instructions.insert(insnNode,
                                    new FieldInsnNode(Opcodes.GETFIELD, "client", "Ob", "Ljava/lang/String;"));
                            methodNode.instructions.insert(insnNode, new VarInsnNode(Opcodes.ALOAD, 0));
                            methodNode.instructions.insert(insnNode, new VarInsnNode(Opcodes.ALOAD, 0));
                            break;
                        }
                    }
                }
            } else if (methodNode.name.equals("a") && methodNode.desc.equals("(IIIIIIII)V")) {
                // Draw NPC hook
                AbstractInsnNode insnNode = methodNode.instructions.getLast();
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 8));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 1));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 7));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 4));

                methodNode.instructions.insertBefore(insnNode,
                        new FieldInsnNode(Opcodes.GETSTATIC, "e", "Mb", "[Ljava/lang/String;"));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ALOAD, 0));
                methodNode.instructions.insertBefore(insnNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Tb", "[Lta;"));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 6));
                methodNode.instructions.insertBefore(insnNode, new InsnNode(Opcodes.AALOAD));
                methodNode.instructions.insertBefore(insnNode, new FieldInsnNode(Opcodes.GETFIELD, "ta", "t", "I"));
                methodNode.instructions.insertBefore(insnNode, new InsnNode(Opcodes.AALOAD));
                methodNode.instructions.insertBefore(insnNode, new MethodInsnNode(Opcodes.INVOKESTATIC,
                        "Game/Client", "drawNPC", "(IIIILjava/lang/String;)V"));
            } else if (methodNode.name.equals("b") && methodNode.desc.equals("(IIIIIIII)V")) {
                // Draw Player hook
                AbstractInsnNode insnNode = methodNode.instructions.getLast();
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 5));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 6));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 2));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 7));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ALOAD, 0));
                methodNode.instructions.insertBefore(insnNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "rg", "[Lta;"));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 8));
                methodNode.instructions.insertBefore(insnNode, new InsnNode(Opcodes.AALOAD));
                methodNode.instructions.insertBefore(insnNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "ta", "C", "Ljava/lang/String;"));
                methodNode.instructions.insertBefore(insnNode, new MethodInsnNode(Opcodes.INVOKESTATIC,
                        "Game/Client", "drawPlayer", "(IIIILjava/lang/String;)V"));
            } else if (methodNode.name.equals("b") && methodNode.desc.equals("(IIIIIII)V")) {
                // Draw Item hook
                // ILOAD 4 is item id
                AbstractInsnNode insnNode = methodNode.instructions.getLast();
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 3));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 7));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 5));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 1));
                methodNode.instructions.insertBefore(insnNode, new VarInsnNode(Opcodes.ILOAD, 4));
                methodNode.instructions.insertBefore(insnNode,
                        new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client", "drawItem", "(IIIII)V"));
            } else if (methodNode.name.equals("L") && methodNode.desc.equals("(I)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Right click bounds fix
                    if (insnNode.getOpcode() == Opcodes.SIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        AbstractInsnNode nextNode = insnNode.getNext();

                        if (call.operand == 510) {
                            call.operand = 512 - call.operand;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                        } else if (call.operand == 315) {
                            call.operand = 334 - call.operand;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "height_client", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                        } else if (call.operand == -316) {
                            call.operand = 334 - (call.operand * -1);
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "height_client", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.INEG));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                        }
                    }
                }
            } else if (methodNode.name.equals("a") && methodNode.desc.equals("(ZZ)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Friends chat mouse fix
                    if (insnNode.getOpcode() == Opcodes.SIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 489 || call.operand == 429) {
                            call.operand = 512 - call.operand;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                        }
                        if (call.operand == -430) {
                            call.operand = 512 - (call.operand * -1);
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.INEG));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                        }
                    }
                }
            } else if (methodNode.name.equals("i") && methodNode.desc.equals("(I)V")) {
                AbstractInsnNode lastNode = methodNode.instructions.getLast().getPrevious();

                // Send combat style option
                LabelNode label = new LabelNode();

                methodNode.instructions.insert(lastNode, label);

                // Format
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "da", "b", "(I)V", false));
                methodNode.instructions.insert(lastNode, new IntInsnNode(Opcodes.SIPUSH, 21294));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Jh", "Lda;"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Write byte
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "ja", "c", "(II)V", false));
                methodNode.instructions.insert(lastNode, new IntInsnNode(Opcodes.BIPUSH, -80));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETSTATIC, "Client/Settings", "COMBAT_STYLE", "I"));
                methodNode.instructions.insert(lastNode, new FieldInsnNode(Opcodes.GETFIELD, "da", "f", "Lja;"));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Jh", "Lda;"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Create Packet
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "da", "b", "(II)V", false));
                methodNode.instructions.insert(lastNode, new InsnNode(Opcodes.ICONST_0));
                methodNode.instructions.insert(lastNode, new IntInsnNode(Opcodes.BIPUSH, 29));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Jh", "Lda;"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Skip combat packet if style is already controlled
                methodNode.instructions.insert(lastNode, new JumpInsnNode(Opcodes.IF_ICMPLE, label));
                methodNode.instructions.insert(lastNode, new InsnNode(Opcodes.ICONST_0));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETSTATIC, "Client/Settings", "COMBAT_STYLE", "I"));

                // Client init_game
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client", "init_game", "()V", false));
            } else if (methodNode.name.equals("o") && methodNode.desc.equals("(I)V")) {
                // Client.init_login patch
                AbstractInsnNode findNode = methodNode.instructions.getLast();
                methodNode.instructions.insertBefore(findNode,
                        new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client", "init_login", "()V", false));
            } else if (methodNode.name.equals("a") && methodNode.desc.equals("(B)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Camera view distance crash fix
                    if (insnNode.getOpcode() == Opcodes.SIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 15000) {
                            call.operand = 32767;
                        }
                    }
                }

                // Client.init patch
                AbstractInsnNode findNode = methodNode.instructions.getFirst();
                methodNode.instructions.insertBefore(findNode, new VarInsnNode(Opcodes.ALOAD, 0));
                methodNode.instructions.insertBefore(findNode,
                        new FieldInsnNode(Opcodes.PUTSTATIC, "Game/Client", "instance", "Ljava/lang/Object;"));
                methodNode.instructions.insertBefore(findNode,
                        new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client", "init", "()V", false));
            } else if (methodNode.name.equals("G") && methodNode.desc.equals("(I)V")) {
                // TODO: This can be shortened, I'll fix it another time

                // NPC Dialogue keyboard
                AbstractInsnNode lastNode = methodNode.instructions.getLast().getPrevious();

                LabelNode label = new LabelNode();

                methodNode.instructions.insert(lastNode, label);

                // Hide dialogue
                methodNode.instructions.insert(lastNode, new FieldInsnNode(Opcodes.PUTFIELD, "client", "Ph", "Z"));
                methodNode.instructions.insert(lastNode, new InsnNode(Opcodes.ICONST_0));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Format
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "da", "b", "(I)V", false));
                methodNode.instructions.insert(lastNode, new IntInsnNode(Opcodes.SIPUSH, 21294));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Jh", "Lda;"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Write byte
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "ja", "c", "(II)V", false));
                methodNode.instructions.insert(lastNode, new IntInsnNode(Opcodes.BIPUSH, 115));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETSTATIC, "Game/KeyboardHandler", "dialogue_option", "I"));
                methodNode.instructions.insert(lastNode, new FieldInsnNode(Opcodes.GETFIELD, "da", "f", "Lja;"));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Jh", "Lda;"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Create Packet
                methodNode.instructions.insert(lastNode,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "da", "b", "(II)V", false));
                methodNode.instructions.insert(lastNode, new InsnNode(Opcodes.ICONST_0));
                methodNode.instructions.insert(lastNode, new IntInsnNode(Opcodes.BIPUSH, 116));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETFIELD, "client", "Jh", "Lda;"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));

                // Check if dialogue option is pressed
                methodNode.instructions.insert(lastNode, new JumpInsnNode(Opcodes.IF_ICMPGE, label));
                // Menu option count
                methodNode.instructions.insert(lastNode, new FieldInsnNode(Opcodes.GETFIELD, "client", "Id", "I"));
                methodNode.instructions.insert(lastNode, new VarInsnNode(Opcodes.ALOAD, 0));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETSTATIC, "Game/KeyboardHandler", "dialogue_option", "I"));
                methodNode.instructions.insert(lastNode, new JumpInsnNode(Opcodes.IFLT, label));
                methodNode.instructions.insert(lastNode,
                        new FieldInsnNode(Opcodes.GETSTATIC, "Game/KeyboardHandler", "dialogue_option", "I"));
            } else if (methodNode.name.equals("f") && methodNode.desc.equals("(I)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Hide Roof option
                    if (insnNode.getOpcode() == Opcodes.GETFIELD) {
                        FieldInsnNode field = (FieldInsnNode) insnNode;

                        if (field.owner.equals("client") && field.name.equals("yj")) {
                            AbstractInsnNode nextNode = insnNode.getNext();
                            if (nextNode.getOpcode() == Opcodes.IFNE) {
                                LabelNode label = ((JumpInsnNode) nextNode).label;
                                methodNode.instructions.insert(nextNode, new JumpInsnNode(Opcodes.IFGT, label));
                                methodNode.instructions.insert(nextNode,
                                        new FieldInsnNode(Opcodes.GETSTATIC, "Client/Settings", "HIDE_ROOFS", "Z"));
                            }
                        }
                    }

                    // Move wilderness skull
                    if (insnNode.getOpcode() == Opcodes.SIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 465 || call.operand == 453) {
                            call.operand = 512 - call.operand;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                        }
                    }
                }
            } else if (methodNode.name.equals("a") && methodNode.desc.equals("(IIZ)Z")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Move the load screen text dialogue
                    if (insnNode.getOpcode() == Opcodes.SIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 256) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 192) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "height", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IADD));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 19));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        }
                    }
                }
            } else if (methodNode.name.equals("d") && methodNode.desc.equals("(B)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Center logout dialogue
                    if (insnNode.getOpcode() == Opcodes.SIPUSH || insnNode.getOpcode() == Opcodes.BIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 256) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 173) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "height", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 126) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 130));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 137) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "height", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 36));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        }
                    }
                }
            } else if (methodNode.name.equals("j") && methodNode.desc.equals("(I)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Center welcome box
                    if (insnNode.getOpcode() == Opcodes.SIPUSH || insnNode.getOpcode() == Opcodes.BIPUSH) {
                        IntInsnNode call = (IntInsnNode) insnNode;
                        if (call.operand == 256) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 167) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "height", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 6));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 56) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 200));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == -87) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.INEG));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 169));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 426) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IADD));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 170));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 106) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.ISUB));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 150));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        } else if (call.operand == 406) {
                            call.operand = 2;
                            methodNode.instructions.insertBefore(insnNode,
                                    new FieldInsnNode(Opcodes.GETSTATIC, "Game/Renderer", "width", "I"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IADD));
                            methodNode.instructions.insert(insnNode, new IntInsnNode(Opcodes.SIPUSH, 150));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.IDIV));
                        }
                    }
                }
            } else if (methodNode.name.equals("k") && methodNode.desc.equals("(B)V")) {
                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();

                    // Save settings from combat menu
                    if (insnNode.getOpcode() == Opcodes.PUTFIELD) {
                        FieldInsnNode field = (FieldInsnNode) insnNode;

                        if (field.owner.equals("client") && field.name.equals("Fg")) {
                            methodNode.instructions.insert(insnNode, new MethodInsnNode(Opcodes.INVOKESTATIC,
                                    "Client/Settings", "save", "()V", false));
                            methodNode.instructions.insert(insnNode,
                                    new FieldInsnNode(Opcodes.PUTSTATIC, "Client/Settings", "COMBAT_STYLE", "I"));
                            methodNode.instructions.insert(insnNode,
                                    new FieldInsnNode(Opcodes.GETFIELD, "client", "Fg", "I"));
                            methodNode.instructions.insert(insnNode, new VarInsnNode(Opcodes.ALOAD, 0));
                        }
                    }
                }
            } else if (methodNode.name.equals("a") && methodNode.desc
                    .equals("(ZLjava/lang/String;ILjava/lang/String;IILjava/lang/String;Ljava/lang/String;)V")) {
                AbstractInsnNode first = methodNode.instructions.getFirst();
                methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 7));
                methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ALOAD, 4));
                methodNode.instructions.insertBefore(first, new VarInsnNode(Opcodes.ILOAD, 5));
                methodNode.instructions.insertBefore(first, new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Client",
                        "messageHook", "(Ljava/lang/String;Ljava/lang/String;I)V"));
            }
        }
    }

    private void patchRenderer(ClassNode node) {
        Logger.Info("Patching renderer (" + node.name + ".class)");

        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            // Renderer present hook
            if (methodNode.desc.equals("(Ljava/awt/Graphics;III)V")) {
                AbstractInsnNode findNode = methodNode.instructions.getFirst();
                FieldInsnNode imageNode = null;

                while (findNode.getOpcode() != Opcodes.POP) {
                    findNode = findNode.getNext();
                    if (findNode == null) {
                        Logger.Error("Unable to find present hook");
                        break;
                    }
                }

                while (findNode.getOpcode() != Opcodes.INVOKESPECIAL) {
                    if (findNode.getOpcode() == Opcodes.GETFIELD)
                        imageNode = (FieldInsnNode) findNode;

                    AbstractInsnNode prev = findNode.getPrevious();
                    methodNode.instructions.remove(findNode);
                    findNode = prev;
                }

                methodNode.instructions.insert(findNode, new MethodInsnNode(Opcodes.INVOKESTATIC, "Game/Renderer",
                        "present", "(Ljava/awt/Graphics;Ljava/awt/Image;)V", false));
                methodNode.instructions.insert(findNode,
                        new FieldInsnNode(Opcodes.GETFIELD, node.name, imageNode.name, imageNode.desc));
                methodNode.instructions.insert(findNode, new VarInsnNode(Opcodes.ALOAD, 0));
                methodNode.instructions.insert(findNode, new VarInsnNode(Opcodes.ALOAD, 1));
            } else if (methodNode.name.equals("a") && methodNode.desc.equals("(IILjava/lang/String;IIBI)V")) {
                AbstractInsnNode start = methodNode.instructions.getFirst();
                while (start != null) {
                    if (start.getOpcode() == Opcodes.ALOAD && start.getNext().getOpcode() == Opcodes.ILOAD
                            && start.getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL
                            && start.getNext().getNext().getNext().getOpcode() == Opcodes.ISTORE) {
                        break;
                    }

                    start = start.getNext();
                }
                start = start.getPrevious();

                LabelNode finishLabel = ((JumpInsnNode) start.getPrevious().getPrevious()).label;
                LabelNode failLabel = new LabelNode();

                methodNode.instructions.insertBefore(start, new IntInsnNode(Opcodes.BIPUSH, 126));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ALOAD, 3));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ILOAD, 10));
                methodNode.instructions.insertBefore(start,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C"));
                methodNode.instructions.insertBefore(start, new JumpInsnNode(Opcodes.IF_ICMPNE, failLabel));

                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ILOAD, 10));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.ICONST_5));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.IADD));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ALOAD, 3));
                methodNode.instructions.insertBefore(start,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I"));
                methodNode.instructions.insertBefore(start, new JumpInsnNode(Opcodes.IF_ICMPGE, failLabel));

                methodNode.instructions.insertBefore(start, new IntInsnNode(Opcodes.BIPUSH, 126));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ALOAD, 3));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ILOAD, 10));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.ICONST_5));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.IADD));
                methodNode.instructions.insertBefore(start,
                        new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C"));
                methodNode.instructions.insertBefore(start, new JumpInsnNode(Opcodes.IF_ICMPNE, failLabel));

                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ALOAD, 3));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ILOAD, 10));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.ICONST_1));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.IADD));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ILOAD, 10));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.ICONST_5));
                methodNode.instructions.insertBefore(start, new InsnNode(Opcodes.IADD));
                methodNode.instructions.insertBefore(start, new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                        "java/lang/String", "substring", "(II)Ljava/lang/String;"));
                methodNode.instructions.insertBefore(start, new MethodInsnNode(Opcodes.INVOKESTATIC,
                        "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I"));
                methodNode.instructions.insertBefore(start, new VarInsnNode(Opcodes.ISTORE, 4));
                methodNode.instructions.insertBefore(start, new IincInsnNode(10, 5));

                methodNode.instructions.insertBefore(start, new JumpInsnNode(Opcodes.GOTO, finishLabel));

                methodNode.instructions.insertBefore(start, failLabel);
            }
        }
    }

    private void patchRandom(ClassNode node) {
        Logger.Info("Patching random (" + node.name + ".class)");

        Iterator<MethodNode> methodNodeList = node.methods.iterator();
        while (methodNodeList.hasNext()) {
            MethodNode methodNode = methodNodeList.next();

            if (methodNode.name.equals("a")) {
                // System.out.println(methodNode.desc);
                if (methodNode.desc.equals("(ILtb;)V")) {
                    Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                    while (insnNodeList.hasNext()) {
                        AbstractInsnNode insnNode = insnNodeList.next();
                        AbstractInsnNode nextNode = insnNode.getNext();

                        if (nextNode == null)
                            break;

                        if (insnNode.getOpcode() == Opcodes.ALOAD && nextNode.getOpcode() == Opcodes.ICONST_0) {
                            VarInsnNode call = (VarInsnNode) insnNode;
                            System.out.println("Patching validation...");

                            methodNode.instructions.insert(insnNode, new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                    "java/util/Random", "nextBytes", "([B)V"));
                            methodNode.instructions.insert(insnNode, new VarInsnNode(Opcodes.ALOAD, 2));
                            methodNode.instructions.insert(insnNode,
                                    new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/util/Random", "<init>", "()V"));
                            methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.DUP));
                            methodNode.instructions.insert(insnNode,
                                    new TypeInsnNode(Opcodes.NEW, "java/util/Random"));
                        }
                    }
                }
            }
        }
    }

    /**
     * TODO: Complete JavaDoc
     * 
     * @param methodNode
     * @param owner The class of the variable to be hooked
     * @param var The variable to be hooked
     * @param desc
     * @param newClass The class the hooked variable will be stored in
     * @param newVar The variable name the hooked variable will be stored in
     * @param newDesc
     * @param canRead Specifies if the hooked variable should be readable
     * @param canWrite Specifies if the hooked variable should be writable
     */
    private void hookClassVariable(MethodNode methodNode, String owner, String var, String desc, String newClass,
            String newVar, String newDesc, boolean canRead, boolean canWrite) {
        Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
        while (insnNodeList.hasNext()) {
            AbstractInsnNode insnNode = insnNodeList.next();

            int opcode = insnNode.getOpcode();
            if (opcode == Opcodes.GETFIELD || opcode == Opcodes.PUTFIELD) {
                FieldInsnNode field = (FieldInsnNode) insnNode;
                if (field.owner.equals(owner) && field.name.equals(var) && field.desc.equals(desc)) {
                    if (opcode == Opcodes.GETFIELD && canWrite) {
                        methodNode.instructions.insert(insnNode,
                                new FieldInsnNode(Opcodes.GETSTATIC, newClass, newVar, newDesc));
                        methodNode.instructions.insert(insnNode, new InsnNode(Opcodes.POP));
                    } else if (opcode == Opcodes.PUTFIELD && canRead) {
                        methodNode.instructions.insertBefore(insnNode, new InsnNode(Opcodes.DUP_X1));
                        methodNode.instructions.insert(insnNode,
                                new FieldInsnNode(Opcodes.PUTSTATIC, newClass, newVar, newDesc));
                    }
                }
            }
        }
    }

    /**
     * TODO: Complete JavaDoc
     * 
     * @param methodNode
     * @param owner The class of the variable to be hooked
     * @param var The variable to be hooked
     * @param desc
     * @param newClass The class the hooked variable will be stored in
     * @param newVar The variable name the hooked variable will be stored in
     * @param newDesc
     */
    private void hookStaticVariable(MethodNode methodNode, String owner, String var, String desc, String newClass,
            String newVar, String newDesc) {
        Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
        while (insnNodeList.hasNext()) {
            AbstractInsnNode insnNode = insnNodeList.next();

            int opcode = insnNode.getOpcode();
            if (opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC) {
                FieldInsnNode field = (FieldInsnNode) insnNode;
                if (field.owner.equals(owner) && field.name.equals(var) && field.desc.equals(desc)) {
                    field.owner = newClass;
                    field.name = newVar;
                    field.desc = newDesc;
                }
            }
        }
    }

    private void dumpClass(ClassNode node) {
        BufferedWriter writer = null;

        try {
            File file = new File(Settings.Dir.DUMP + "/" + node.name + ".dump");
            writer = new BufferedWriter(new FileWriter(file));

            writer.write(decodeAccess(node.access) + node.name + " extends " + node.superName + ";\n");
            writer.write("\n");

            Iterator<FieldNode> fieldNodeList = node.fields.iterator();
            while (fieldNodeList.hasNext()) {
                FieldNode fieldNode = fieldNodeList.next();
                writer.write(decodeAccess(fieldNode.access) + fieldNode.desc + " " + fieldNode.name + ";\n");
            }

            writer.write("\n");

            Iterator<MethodNode> methodNodeList = node.methods.iterator();
            while (methodNodeList.hasNext()) {
                MethodNode methodNode = methodNodeList.next();
                writer.write(decodeAccess(methodNode.access) + methodNode.name + " " + methodNode.desc + ":\n");

                Iterator<AbstractInsnNode> insnNodeList = methodNode.instructions.iterator();
                while (insnNodeList.hasNext()) {
                    AbstractInsnNode insnNode = insnNodeList.next();
                    String instruction = decodeInstruction(insnNode);
                    writer.write(instruction);
                }
                writer.write("\n");
            }

            writer.close();
        } catch (Exception e) {
            try {
                writer.close();
            } catch (Exception e2) {
            }
        }
    }

    private String decodeAccess(int access) {
        String res = "";

        if ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC)
            res += "public ";
        if ((access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE)
            res += "private ";
        if ((access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED)
            res += "protected ";

        if ((access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC)
            res += "static ";
        if ((access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL)
            res += "final ";
        if ((access & Opcodes.ACC_VOLATILE) == Opcodes.ACC_VOLATILE)
            res += "protected ";
        if ((access & Opcodes.ACC_SYNCHRONIZED) == Opcodes.ACC_SYNCHRONIZED)
            res += "synchronized ";
        if ((access & Opcodes.ACC_ABSTRACT) == Opcodes.ACC_ABSTRACT)
            res += "abstract ";
        if ((access & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE)
            res += "interface ";

        return res;
    }

    private String decodeInstruction(AbstractInsnNode insnNode) {
        insnNode.accept(mp);
        StringWriter sw = new StringWriter();
        printer.print(new PrintWriter(sw));
        printer.getText().clear();
        return sw.toString();
    }

    public static JClassPatcher getInstance() {
        if (instance == null) {
            synchronized (JClassPatcher.class) {
                instance = new JClassPatcher();
            }
        }
        return instance;
    }

}