[ASM] Создание своего отменяемого ивента

Версия Minecraft
1.7.10
API
Forge
236
4
22
Всем привет. Нужна помощь по редактированию байт-кода через ASM. Вообщем, предположим есть класс майнкрафта с нужным мне методом. Мне нужно "пропатчить" этот метод. Как зарегать ASM plugin, перегнать byte[] в ClassNode и вернуть обратно в byte[] - знаю, особо объяснений не требуется. Как вставить, например вызова метода в инструкции метода - тоже знаю и уже делал.

Но в этот раз мне нужно кое-что сложнее - отменяемое событие. Конкретно в случае, из-за которого пишу сейчас - мне достаточно будет вызвать кастом событие в самом начале реализации метода и просто return`уть, если ивент отменён. Как и всегда, я использую в помощь ASM ByteCode Viewer плагин IDEA, и именно тут загвостка: как я понял, ветки if-else; try-catch; циклы и подобные конструкции используют Labels (метки), которые показываются плагинов в виде L0, L1, Ln. Я попробовал скомпилить класс, где есть простенький if - и как раз, в ASM Viewer появились те самые L0 L1 и IFEQ(который, как я понимаю, отвечает за то, какую ветку нужно выбрать в зависимости от результата проверки if).

Для реализации того, что мне нужно (вставить свой отменяемый ивент в метод из майнкрафта), мне кажется нужно создать InsnList(), вставить в начало этого листа создание и пост ивента в MinecraftForge.EVENT_BUS, а затем как-то (КАК???) создать L1, где будет InsnNode(Opcodes.RETURN) и L2, куда я скопирую все инструкции метода (то есть ивент не отменился - делаем то, что в этом методе есть), затем очистить инструкции в методе через method.instructions.clear() и вставить те инструкции, которые у меня получились в InsnList(). Но вот, увы, весь вечер просидел в гугле и не смог найти, как вставить if() { } else {} с помощью ASM в код... Помогите, пожалуйста!
 
Решение
А, вижу, что там как раз нет отменяемых событий, ладно.
Вот рандомный кусок кода, который я нарыл на просторах интернета. Он вроде показывает как вставить if-else с return.
Java:
        if (transformedName.equals("net.minecraft.block.BlockDynamicLiquid"))
        {
            boolean isObfuscated = !name.equals(transformedName);
 
            ClassNode classNode = readClassFromBytes(bytes);
 
            MethodNode method = findMethodNodeOfClass(classNode, isObfuscated ? "a" : "tryFlowInto", isObfuscated ? "(Laid;Lcm;Lars;I)V" : "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;I)V");
 
            /*
            if (Hooks.onFlowIntoBlock(world, pos, state, flowDecay))...
154
17
96
А, вижу, что там как раз нет отменяемых событий, ладно.
Вот рандомный кусок кода, который я нарыл на просторах интернета. Он вроде показывает как вставить if-else с return.
Java:
        if (transformedName.equals("net.minecraft.block.BlockDynamicLiquid"))
        {
            boolean isObfuscated = !name.equals(transformedName);
 
            ClassNode classNode = readClassFromBytes(bytes);
 
            MethodNode method = findMethodNodeOfClass(classNode, isObfuscated ? "a" : "tryFlowInto", isObfuscated ? "(Laid;Lcm;Lars;I)V" : "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;I)V");
 
            /*
            if (Hooks.onFlowIntoBlock(world, pos, state, flowDecay))
                return;
            */
            InsnList toInject = new InsnList();
            LabelNode ifNotCanceled = new LabelNode();
            toInject.add(new VarInsnNode(Opcodes.ALOAD, 1));
            toInject.add(new VarInsnNode(Opcodes.ALOAD, 2));
            toInject.add(new VarInsnNode(Opcodes.ALOAD, 3));
            toInject.add(new VarInsnNode(Opcodes.ILOAD, 4));
            toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(Hooks.class), "onFlowIntoBlock", "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;I)Z", false));
            toInject.add(new JumpInsnNode(Opcodes.IFEQ, ifNotCanceled));
            toInject.add(new InsnNode(Opcodes.RETURN));
            toInject.add(ifNotCanceled);
 
            method.instructions.insertBefore(findFirstInstruction(method), toInject);
 
            return writeClassToBytes(classNode);
        }
 
236
4
22
Похвально, что ты пошел в асм, но не проще ли будет использовать миксин?
Мне ASM нужен в моём случае, т.к. я патч применяю ко всем классам, которые наследуют класс N (миксины не могут так, на сколько я знаю). Также миксины очень сильно увеличивают время загрузки сервера - по этому я не использую их.
 
236
4
22
А, вижу, что там как раз нет отменяемых событий, ладно.
Вот рандомный кусок кода, который я нарыл на просторах интернета. Он вроде показывает как вставить if-else с return.
В том и дело, что не было, и да, я уже читал (правда уже после того, как сам додумался как это реализовать [без ветвления]). А вот твой код - именно то, что мне нужно! Спасибо ;)
 
Последнее редактирование:
7,099
324
1,510
Мне ASM нужен в моём случае, т.к. я патч применяю ко всем классам, которые наследуют класс N (миксины не могут так, на сколько я знаю). Также миксины очень сильно увеличивают время загрузки сервера - по этому я не использую их.
А ты не думаешь, что твой код будет не быстрее? Долгое время загрузки, скорее всего, обусловлено тем, что нужно для каждого класса проверить всю цепочку наследования. Тебе также нужно будет делать это
 
154
17
96
Представь, что тебе нужно собрать ВСЕ классы, наследованные от условного ItemCoolSword, или еще хуже, имплементирующие интерфейс ItemMagicContainer.
У всех них есть какой-либо метод, но в каждом разная реализация, причем super вызывается далеко не всегда (не забываем про интерфейс).
Парню, как я понял, нужно во все эти классы вставить вызов ивента и возврат из метода при неудаче.
Самый эффективный для этого способ - как раз форжевые трансформеры.
 
7,099
324
1,510
А что, если кто-то вызывает не метод интерфейса, а метод одного из наследников?
 
236
4
22
Нет, это не интерфейс, это нестатический метод из класса ванилы. И да, мне нужно было пропатчить переопределённые методы во всех классах-наследников, а там почти ни 1 из них не вызывал super(), а если даже и вызывал бы - это не меняло бы никакой роли (к тому же этот метод много модов оверрайдят)
 
236
4
22
Пожалуй не стану создавать новую тему, спрошу тут, к тому же тоже про ASM. Вообщем добавил я свой ивент, всё гуд. Было... ...пока не нашёлся мод который тоже трансформирует тот же метод, что и я. Только там это делается через mixins, а у меня ASM. Вообщем, получился краш.
[01:23:49] [main/ERROR] [LaunchWrapper/]: Unable to launch
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_292]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_292]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_292]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_292]
at net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.12.jar:?]
at net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]
Caused by: java.lang.VerifyError: Expecting a stackmap frame at branch target 103
Exception Details:
Location:
net/minecraft/block/BlockSapling.func_149674_a(Lnet/minecraft/world/World;IIILjava/util/Random;)V @42: if_acmpeq
Reason:
Expected stackmap frame at this location.
Bytecode:
0x0000000: b200 2fbb 0031 592a 2b1c 1d15 042b 1c1d
0x0000010: 1504 b600 37b7 003a 2a2b 1c1d 1504 1905
0x0000020: b800 403a 0619 06b2 0046 a500 3d19 06b2
0x0000030: 0049 a600 40b6 004f 9900 04b1 2bb4 0053
0x0000040: 9a00 322a 2b1c 1d15 0419 05b7 0055 2b1c
0x0000050: 1d04 6015 04b6 0058 1009 a100 1819 0510
0x0000060: 07b6 005e 9a00 0e2a 2b1c 1d15 0419 05b6
0x0000070: 0061 b1
Stackmap Table:
same_frame_extended(@114)

at net.minecraft.block.Block.func_149671_p(Block.java:210) ~[aji.class:?]
at net.minecraft.init.Bootstrap.func_151354_b(SourceFile:355) ~[kl.class:?]
at net.minecraft.client.Minecraft.<init>(Minecraft.java:287) ~[bao.class:?]
at net.minecraft.client.main.Main.main(SourceFile:129) ~[Main.class:?]
... 6 more
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable[imath]WrappedPrintStream:println:749]: cpw.mods.fml.relauncher.FMLSecurityManager[/imath]ExitTrappedException
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable$WrappedPrintStream:println:749]: at cpw.mods.fml.relauncher.FMLSecurityManager.checkPermission(FMLSecurityManager.java:25)
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable$WrappedPrintStream:println:749]: at java.lang.SecurityManager.checkExit(SecurityManager.java:761)
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable$WrappedPrintStream:println:749]: at java.lang.Runtime.exit(Runtime.java:107)
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable$WrappedPrintStream:println:749]: at java.lang.System.exit(System.java:973)
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable$WrappedPrintStream:println:749]: at net.minecraft.launchwrapper.Launch.launch(Launch.java:138)
[01:23:49] [main/INFO] [STDERR/]: [java.lang.Throwable$WrappedPrintStream:println:749]: at net.minecraft.launchwrapper.Launch.main(Launch.java:28)
Есть ли возможность как-то задать приоритет обработки хуков (чтобы мой трансформер был самый последний в сборке), возможно бы помогло это..? Или как решать подобные проблемы, ведь я не могу предугадать в каких сборках будет мод и какие там будут миксины/ASM.
 
236
4
22
Именно поэтому я и писал про юз миксинов. они сами друг-на-друга при правильном написании накладываются.
Уже давно понятно должно быть, что в моём случаи они просто были не возможны. Лучше подскажите как решить этот краш :(
 
154
17
96
Ну, тут уже реально более продуктивнее и правильнее будет не влезать в методы абсолютно всех классов, а отследить в форже/майне места, где вызывается этот метод и вставить хук перед этим вызовом. Ибо чем больше кода ты программно меняешь, тем больше вероятность что что-то потенциально может послать тебя куда подальше.
Именно по твоей проблеме, возможно ты как-то криво ищешь точку входа или перезаписываешь класс, посмотри второй ресурс из тех, которых я кидал и попробуй сделать как там.
А чтобы твой мод загружался раньше миксинов - нужно чтобы имя его jar файла стояло раньше, чем любой мод, юзающий миксины, так что это довольно ненадежный способ.
 
Последнее редактирование:
236
4
22
Подскажите, пожалуйста, как правильно определить, какие флаги передавать в конструктор ClassWriter? COMPUTE_MAXS / COMPUTE_FRAMES / COMPUTE_MAXS | COMPUTE_FRAMES / 0 или что-то другое? Есть ли вообще "универсальный" ответ к этому вопросу? Да, немножечко не по теме топика вопрос, но тоже касается ASM.
 
Сверху