HookLib with anchors

1,111
47
420
Можно сделать следующий дикий костыль:
Из Thread.currentThread().getStackTrace() определить, кто вызывает targetMethod
И возвращать свое значение только если вызывающий someMethod
Есть одна чутка более интересная штука jdk.internal.reflect.Reflection#getCallerClass()
 
Жаль правда, что она работает лишь для классов стандартной библиотеки
 
1,111
47
420
Yes but actually no
Во первых да я перепутал имячко класса. В жаве 8 оно выглядит как sun.reflect.Reflection#getCallerClass
И что примечательно там есть 2 метода getCallerClass
один принимает int другой нет
первый ругается на CallerSensitive(лол) второй нет
 
В общем, мне хотелось все-таки сделать это как-то более красиво. Поэтому пришлось немного вникнуть в asm, и хотя идеально сделать не получилось, все-таки такой вариант мне кажется более, скажем так, изящным. Собсно, сабж - сделал якорь на присвоение локальной переменной. В идеале, конечно, хотелось, чтобы хук работал, как писали выше
Ааа, это цель для якоря.
Типо есть
player.inventory.currentItem = x;
А применение хука делает
player.inventory.currentItem = hook(other, arguments... , x);
Но, как я понял, реализовать это сложнее, чем в итоге сделал я. В моем варианте мы просто вставляем новое присваивание с хуком сразу после целевого, т.е.:
Было
player.inventory.currentItem = x;
Стало
player.inventory.currentItem = x;
player.inventory.currentItem = hook(...);
Упс, заметил, что в примере используется присвоение полю класса, но на самом деле работает только с локальными переменными.

в enum InjectionPoint добавляем VAR_ASSIGNMENT
в @interface At добавляем
public int targetVar() default -1;
в class AsmHook добавляем методы
Java:
public Type getHookMethodReturnType() {
    return hookMethodReturnType;
}

public int getAnchorTargetVar() {
    return (int) anchor.get("targetVar");
}
в class ByAnchor (который наследуется от HookInjectorMethodVisitor) добавляем:
а) поле private Type varType;
б) в конструктор varType = hook.getHookMethodReturnType();
в) метод

Java:
        @Override
        public void visitVarInsn(int opcode, int var) {
            super.visitVarInsn(opcode, var);

            if(hook.getAnchorPoint() == VAR_ASSIGNMENT && opcode == varType.getOpcode(54) && var == hook.getAnchorTargetVar()) {
                if(visitOrderedHook()) {
                    super.visitVarInsn(opcode, var);
                }
            }
        }

Вроде ничего не забыл

Например, хотим высчитывать урон от падения по своей формуле
Java:
public void fall(float distance, float damageMultiplier)
{
    float[] ret = net.minecraftforge.common.ForgeHooks.onLivingFall(this, distance, damageMultiplier);
    if (ret == null) return;
    distance = ret[0]; damageMultiplier = ret[1];
    super.fall(distance, damageMultiplier);
    PotionEffect potioneffect = this.getActivePotionEffect(MobEffects.JUMP_BOOST);
    float f = potioneffect == null ? 0.0F : (float)(potioneffect.getAmplifier() + 1);
    int i = MathHelper.ceil((distance - 3.0F - f) * damageMultiplier);

    if (i > 0)
    {
        this.playSound(this.getFallSound(i), 1.0F, 1.0F);
        this.attackEntityFrom(DamageSource.FALL, (float)i);
        int j = MathHelper.floor(this.posX);
        int k = MathHelper.floor(this.posY - 0.20000000298023224D);
        int l = MathHelper.floor(this.posZ);
        IBlockState iblockstate = this.world.getBlockState(new BlockPos(j, k, l));

        if (iblockstate.getMaterial() != Material.AIR)
        {
            SoundType soundtype = iblockstate.getBlock().getSoundType(iblockstate, world, new BlockPos(j, k, l), this);
            this.playSound(soundtype.getFallSound(), soundtype.getVolume() * 0.5F, soundtype.getPitch() * 0.75F);
        }
    }
}
Нам нужна переменная int i = MathHelper.ceil((distance - 3.0F - f) * damageMultiplier);
Для примера просто уменьшим вдвое + 1.


Java:
    @Hook(at = @At(point = InjectionPoint.VAR_ASSIGNMENT, targetVar = 6, ordinal = 0))
    public static int fall(EntityLivingBase entity, float distance, float damageMultiplier, @Hook.LocalVariable(6) int var) {
        System.out.println("previous value = " + var);

        return MathHelper.ceil(var / 2) + 1;
    }

Тип переменной берется из возвращаемого типа хука (в данном примере - int)
targetVar - номер перехватываемой переменной (по аналогии с @LocalVariable)
Кроме аннотации @ReturnValue есть аналогичная @LocalVariable(номер_переменной). Названия переменных в коде могут не сохраняться, так что вот так вот. Чтобы узнать номер, рекомендую использовать методы в классе VariableIdHelper.

Честно говоря, особо не тестил, так что мб могут быть баги, но в моем случае работает.
 
Последнее редактирование:
7,103
324
1,510
Круто и похвально)
 
1,111
47
420
Я хз поддерживается ли еще эта штука, но я тут чот захотел ревью сделать. Надо еще или похер?
 
7,103
324
1,510
Всегда надо, посмотри ветки public-hooklib и tree-api-2(первое стабильное, второе недоделанное)
 
Что делать если при наличии в сборке какого-то определенного мода, мой хук полностью исчезает (при этом в логах написано, что инъекция прошла)? В начале думал, что причина в том, что я полностью заменил метод, но проблема сохранилась и тогда, когда хук был локализирован.

Так-же я обнаружил, что при наличии такого мода, хуки не вставляются вообще в этот класс.
 
Последнее редактирование:
7,103
324
1,510
Причина скорее всего в том, что тот мод заменяет метод полностью. В туторе есть опция jvm, позволяющая дампить классы, при помощи нее можно посмотреть че там получается после всех трансформеров
 
7,103
324
1,510
Вообще, хуклиба должна применяться последней. Вероятно, тот мод тоже пытается применить свой трансформер последним.
Че за мод то и целевой класс?
 
7,103
324
1,510
Готова новая версия!
Существенно переработано апи.
Добавлены javadoc и вики.
Добавлены новые фичи:
  • теперь работает как мод-лоадер для кормодов с хуками
  • линзы для доступа к полям(аналог AT, но можно дотянуться до всех классов в classpath)
  • вывод в лог возможных аргументов хука с аннотацией @LocalVariable
  • точка вставки по выражению(можно ставлять вызов хука в середину метода вокруг куска кода)
Опубликовано на курсе(GloomyFolken разрешил): HookLib
 
Сверху