@Override
  protected void onApply(CtBehavior behavior, Bytecode bytecode) throws BadBytecode {

    bytecode = BytecodeTools.prepareMethodForBytecode(behavior, bytecode);

    // loop through the opcodes and change any GT/GE opcodes to ICMPGT/ICMPGE
    CodeIterator iterator = behavior.getMethodInfo().getCodeAttribute().iterator();
    while (iterator.hasNext()) {
      int index = iterator.next();
      int opcode = iterator.byteAt(index);
      switch (opcode) {
        case Opcode.IFGT:

          // overwrite the opcode
          iterator.writeByte(Opcode.IF_ICMPGT, index);

          // insert the method call
          iterator.insertAt(index, bytecode.get());
          behavior.getMethodInfo().getCodeAttribute().computeMaxStack();

          break;

        case Opcode.IFGE:

          // overwrite the opcode
          iterator.writeByte(Opcode.IF_ICMPGE, index);

          // insert the method call
          iterator.insertAt(index, bytecode.get());
          behavior.getMethodInfo().getCodeAttribute().computeMaxStack();

          break;
      }
    }
  }
    public static void rewriteFakeMethod(CodeIterator methodBody, String methodDescriptor) {
      String ret = DescriptorUtils.getReturnType(methodDescriptor);
      // if the return type is larger than one then it is not a primitive
      // so it does not need to be boxed
      if (ret.length() != 1) {
        return;
      }
      byte ar = (byte) Opcode.ARETURN;
      byte[] areturn = {ar};
      // void methods are special
      if (ret.equals("V")) {

        while (methodBody.hasNext()) {
          try {
            int index = methodBody.next();
            int opcode = methodBody.byteAt(index);
            // replace a RETURN opcode with
            // ACONST_NULL
            // ARETURN
            // to return a null value
            if (opcode == Opcode.RETURN) {
              Bytecode code = new Bytecode(methodBody.get().getConstPool());
              code.add(Opcode.ACONST_NULL);
              code.add(Opcode.ARETURN);
              methodBody.insertAt(index, code.get());
            }
          } catch (BadBytecode e) {
            throw new RuntimeException(e);
          }
        }
      } else {
        while (methodBody.hasNext()) {
          try {
            int index = methodBody.next();
            int opcode = methodBody.byteAt(index);

            switch (opcode) {
              case Opcode.IRETURN:
              case Opcode.LRETURN:
              case Opcode.DRETURN:
              case Opcode.FRETURN:
                // write a NOP over the old return instruction
                // insert the boxing code to get an object on the stack
                Bytecode b = new Bytecode(methodBody.get().getConstPool());
                Boxing.box(b, ret.charAt(0));
                b.addOpcode(Opcode.ARETURN);
                methodBody.insertAt(index, b.get());
            }
          } catch (BadBytecode e) {
            throw new RuntimeException(e);
          }
        }
      }
    }
  private void initExtraHarvest() {
    try {
      CtClass terraForming =
          HookManager.getInstance()
              .getClassPool()
              .get("com.wurmonline.server.behaviours.Terraforming");

      CtClass[] paramTypes = {
        HookManager.getInstance().getClassPool().get("com.wurmonline.server.creatures.Creature"),
        CtPrimitiveType.intType,
        CtPrimitiveType.intType,
        CtPrimitiveType.booleanType,
        CtPrimitiveType.intType,
        CtPrimitiveType.floatType,
        HookManager.getInstance().getClassPool().get("com.wurmonline.server.items.Item")
      };

      CtMethod method =
          terraForming.getMethod(
              "harvest", Descriptor.ofMethod(CtPrimitiveType.booleanType, paramTypes));
      MethodInfo methodInfo = method.getMethodInfo();
      CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
      CodeIterator codeIterator = codeAttribute.iterator();

      LocalVariableAttribute attr =
          (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
      int quantityIndex = -1;
      for (int i = 0; i < attr.tableLength(); i++) {
        if ("quantity".equals(attr.variableName(i))) {
          quantityIndex = attr.index(i);
        }
      }

      if (quantityIndex == -1) {
        throw new HookException("Quantity variable can not be resolved");
      }

      while (codeIterator.hasNext()) {
        int pos = codeIterator.next();
        int op = codeIterator.byteAt(pos);
        if (op == CodeIterator.ISTORE) {
          int fieldRefIdx = codeIterator.byteAt(pos + 1);
          if (quantityIndex == fieldRefIdx) {
            Bytecode bytecode = new Bytecode(codeIterator.get().getConstPool());
            bytecode.addIconst(extraHarvest);
            bytecode.add(Bytecode.IADD);
            codeIterator.insertAt(pos, bytecode.get());
            break;
          }
        }
      }
    } catch (NotFoundException | BadBytecode e) {
      throw new HookException(e);
    }
  }
  @Override
  public boolean transform(
      ClassLoader loader,
      String className,
      Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain,
      ClassFile file)
      throws IllegalClassFormatException, BadBytecode {

    /**
     * Hack up the proxy factory so it stores the proxy ClassFile. We need this to regenerate
     * proxies.
     */
    if (file.getName().equals("org.jboss.weld.bean.proxy.ProxyFactory")) {
      for (final MethodInfo method : (List<MethodInfo>) file.getMethods()) {
        if (method.getName().equals("createProxyClass")) {
          final MethodInvokationManipulator methodInvokationManipulator =
              new MethodInvokationManipulator();
          methodInvokationManipulator.replaceVirtualMethodInvokationWithStatic(
              ClassLoader.class.getName(),
              WeldProxyClassLoadingDelegate.class.getName(),
              "loadClass",
              "(Ljava/lang/String;)Ljava/lang/Class;",
              "(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/Class;",
              loader);
          methodInvokationManipulator.replaceVirtualMethodInvokationWithStatic(
              "org.jboss.weld.util.bytecode.ClassFileUtils",
              WeldProxyClassLoadingDelegate.class.getName(),
              "toClass",
              "(Ljavassist/bytecode/ClassFile;Ljava/lang/ClassLoader;Ljava/security/ProtectionDomain;)Ljava/lang/Class;",
              "(Ljavassist/bytecode/ClassFile;Ljava/lang/ClassLoader;Ljava/security/ProtectionDomain;)Ljava/lang/Class;",
              loader);
          HashSet<MethodInfo> modifiedMethods = new HashSet<MethodInfo>();
          methodInvokationManipulator.transformClass(file, loader, true, modifiedMethods);
          for (MethodInfo m : modifiedMethods) {
            m.rebuildStackMap(ClassPool.getDefault());
          }
          return true;
        } else if (method.getName().equals("<init>")) {

          Integer beanArgument = null;
          int count = 0;
          for (final String paramType :
              DescriptorUtils.descriptorStringToParameterArray(method.getDescriptor())) {
            if (paramType.equals("javax/enterprise/inject/spi/Bean")) {
              beanArgument = count;
              break;
            } else if (paramType.equals("D") || paramType.equals("J")) {
              count += 2;
            } else {
              count++;
            }
          }
          if (beanArgument == null) {
            log.error(
                "Constructor org.jboss.weld.bean.proxy.ProxyFactory.<init>"
                    + method.getDescriptor()
                    + " does not have a bean parameter, proxies produced by this factory will not be reloadable");
            continue;
          }

          // similar to other tracked instances
          // but we need a strong ref
          Bytecode code = new Bytecode(file.getConstPool());
          code.addAload(0);
          code.addAload(beanArgument);
          code.addInvokestatic(
              WeldClassChangeAware.class.getName(),
              "addProxyFactory",
              "(Lorg/jboss/weld/bean/proxy/ProxyFactory;)V");
          CodeIterator it = method.getCodeAttribute().iterator();
          it.skipConstructor();
          it.insert(code.get());
        }
      }
    }
    return false;
  }
  public boolean transformClass(ClassFile file, ClassLoader loader, boolean modifiableClass) {
    Set<Integer> methodCallLocations = new HashSet<Integer>();
    Integer newCallLocation = null;
    Integer methodReflectionLocation = null;
    Integer fakeCallRequiredLocation = null;
    // first we need to scan the constant pool looking for
    // CONSTANT_method_info_ref structures
    ConstPool pool = file.getConstPool();
    for (int i = 1; i < pool.getSize(); ++i) {
      // we have a method call
      if (pool.getTag(i) == ConstPool.CONST_Methodref) {
        String className = pool.getMethodrefClassName(i);
        String methodName = pool.getMethodrefName(i);

        if (className.equals(Method.class.getName())) {
          if (methodName.equals("invoke")) {
            // store the location in the const pool of the method ref
            methodCallLocations.add(i);
            // we have found a method call

            // if we have not already stored a reference to our new
            // method in the const pool
            if (newCallLocation == null) {
              methodReflectionLocation =
                  pool.addClassInfo("org.fakereplace.reflection.MethodReflection");
              int nt = pool.addNameAndTypeInfo("fakeCallRequired", "(Ljava/lang/reflect/Method;)Z");
              fakeCallRequiredLocation = pool.addMethodrefInfo(methodReflectionLocation, nt);
              newCallLocation = pool.addNameAndTypeInfo(METHOD_NAME, REPLACED_METHOD_DESCRIPTOR);
            }
          }
        }
      }
    }

    // this means we found an instance of the call, now we have to iterate
    // through the methods and replace instances of the call
    if (newCallLocation != null) {
      List<MethodInfo> methods = file.getMethods();
      for (MethodInfo m : methods) {
        try {
          // ignore abstract methods
          if (m.getCodeAttribute() == null) {
            continue;
          }
          CodeIterator it = m.getCodeAttribute().iterator();
          while (it.hasNext()) {
            // loop through the bytecode
            int index = it.next();
            int op = it.byteAt(index);
            // if the bytecode is a method invocation
            if (op == CodeIterator.INVOKEVIRTUAL) {
              int val = it.s16bitAt(index + 1);
              // if the method call is one of the methods we are
              // replacing
              if (methodCallLocations.contains(val)) {
                Bytecode b = new Bytecode(file.getConstPool());
                // our stack looks like Method, instance,params
                // we need Method, instance, params , Method
                b.add(Opcode.DUP_X2);
                b.add(Opcode.POP);
                b.add(Opcode.DUP_X2);
                b.add(Opcode.POP);
                b.add(Opcode.DUP_X2);
                b.addInvokestatic(
                    methodReflectionLocation, "fakeCallRequired", "(Ljava/lang/reflect/Method;)Z");
                b.add(Opcode.IFEQ);
                JumpMarker performRealCall = JumpUtils.addJumpInstruction(b);
                // now perform the fake call
                b.addInvokestatic(methodReflectionLocation, "invoke", REPLACED_METHOD_DESCRIPTOR);
                b.add(Opcode.GOTO);
                JumpMarker finish = JumpUtils.addJumpInstruction(b);
                performRealCall.mark();
                b.addInvokevirtual(Method.class.getName(), METHOD_NAME, METHOD_DESCRIPTOR);
                finish.mark();
                it.writeByte(CodeIterator.NOP, index);
                it.writeByte(CodeIterator.NOP, index + 1);
                it.writeByte(CodeIterator.NOP, index + 2);
                it.insert(b.get());
              }
            }
          }
          m.getCodeAttribute().computeMaxStack();
        } catch (Exception e) {
          log.error("Bad byte code transforming " + file.getName());
          e.printStackTrace();
        }
      }
      return true;
    } else {
      return false;
    }
  }