diff --git a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java index 749b94001..3292317ba 100644 --- a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java +++ b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java @@ -1,23 +1,36 @@ package com.cleanroommc.modularui.core; +import java.util.function.Consumer; + +import com.cleanroommc.modularui.core.temp.ClassSplicer; import com.cleanroommc.modularui.core.visitor.PacketByteBufferVisitor; import net.minecraft.launchwrapper.IClassTransformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; public class ClassTransformer implements IClassTransformer { @Override public byte[] transform(String name, String transformedName, byte[] basicClass) { - if (!ModularUICore.stackUpLoaded && - (transformedName.equals(PacketByteBufferVisitor.PACKET_UTIL_CLASS) || - (transformedName.equals(PacketByteBufferVisitor.PACKET_BUFFER_CLASS) && !ModularUICore.ae2Loaded))) { - ClassWriter classWriter = new ClassWriter(0); - new ClassReader(basicClass).accept(new PacketByteBufferVisitor(classWriter), 0); - ModularUICore.LOGGER.info("Applied {} ASM from ModularUI", transformedName); - return classWriter.toByteArray(); + if (!ModularUICore.stackUpLoaded) { + // Temporarily use AE2's implementation if it's loaded + if (ModularUICore.ae2Loaded && transformedName.equals(PacketByteBufferVisitor.PACKET_UTIL_CLASS)) { + Consumer consumer = (node) -> { + ClassSplicer.spliceClasses(node, "com.cleanroommc.modularui.core.temp.PacketUtilPatch", + "writeItemStackFromClientToServer"); + }; + ModularUICore.LOGGER.info("Applied {} ASM, specific for AE2, from ModularUI", transformedName); + return ClassSplicer.processNode(basicClass, consumer); + } else if (transformedName.equals(PacketByteBufferVisitor.PACKET_UTIL_CLASS) || + transformedName.equals(PacketByteBufferVisitor.PACKET_BUFFER_CLASS)) { + ClassWriter classWriter = new ClassWriter(0); + new ClassReader(basicClass).accept(new PacketByteBufferVisitor(classWriter), 0); + ModularUICore.LOGGER.info("Applied {} ASM from ModularUI", transformedName); + return classWriter.toByteArray(); + } } return basicClass; } diff --git a/src/main/java/com/cleanroommc/modularui/core/temp/ClassSplicer.java b/src/main/java/com/cleanroommc/modularui/core/temp/ClassSplicer.java new file mode 100644 index 000000000..b255b7458 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/temp/ClassSplicer.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2018, 2020 Adrian Siekierka + * + * This file is part of StackUp. + * + * StackUp is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * StackUp 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with StackUp. If not, see . + */ + +package com.cleanroommc.modularui.core.temp; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.function.Consumer; + +import com.cleanroommc.modularui.core.ModularUICore; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.io.ByteStreams; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +public class ClassSplicer { + public static byte[] processNode(byte[] data, Consumer classNodeConsumer) { + ClassReader reader = new ClassReader(data); + ClassNode nodeOrig = new ClassNode(); + reader.accept(nodeOrig, 0); + classNodeConsumer.accept(nodeOrig); + ClassWriter writer = new ClassWriter(0); + nodeOrig.accept(writer); + return writer.toByteArray(); + } + + public static void spliceClasses(final ClassNode data, final String className, final String... methods) { + try (InputStream stream = ModularUICore.class.getClassLoader().getResourceAsStream(className.replace('.', '/') + ".class")) { + spliceClasses(data, ByteStreams.toByteArray(stream), className, methods); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public static void spliceClasses(final ClassNode nodeData, final byte[] dataSplice, final String className, final String... methods) { + // System.out.println("Splicing from " + className + " to " + targetClassName) + if (dataSplice == null) { + throw new RuntimeException("Class " + className + " not found! This is a ModularUI bug!"); + } + + final Set methodSet = Sets.newHashSet(methods); + final List methodList = Lists.newArrayList(methods); + + final ClassReader readerSplice = new ClassReader(dataSplice); + final String className2 = className.replace('.', '/'); + final String targetClassName2 = nodeData.name; + final String targetClassName = targetClassName2.replace('/', '.'); + final Remapper remapper = new Remapper() { + public String map(final String name) { + return className2.equals(name) ? targetClassName2 : name; + } + }; + + ClassNode nodeSplice = new ClassNode(); + readerSplice.accept(new ClassRemapper(nodeSplice, remapper), ClassReader.EXPAND_FRAMES); + for (String s : nodeSplice.interfaces) { + if (methodSet.contains(s)) { + nodeData.interfaces.add(s); + System.out.println("Added INTERFACE: " + s); + } + } + + for (int i = 0; i < nodeSplice.methods.size(); i++) { + if (methodSet.contains(nodeSplice.methods.get(i).name)) { + MethodNode mn = nodeSplice.methods.get(i); + boolean added = false; + + for (int j = 0; j < nodeData.methods.size(); j++) { + if (nodeData.methods.get(j).name.equals(mn.name) + && nodeData.methods.get(j).desc.equals(mn.desc)) { + MethodNode oldMn = nodeData.methods.get(j); + System.out.println("Spliced in METHOD: " + targetClassName + "." + mn.name); + nodeData.methods.set(j, mn); + if (nodeData.name.equals(nodeSplice.superName)) { + ListIterator nodeListIterator = mn.instructions.iterator(); + while (nodeListIterator.hasNext()) { + AbstractInsnNode node = nodeListIterator.next(); + if (node instanceof MethodInsnNode + && node.getOpcode() == Opcodes.INVOKESPECIAL) { + MethodInsnNode methodNode = (MethodInsnNode) node; + if (targetClassName2.equals(methodNode.owner)) { + methodNode.owner = nodeData.superName; + } + } + } + } + + oldMn.name = methodList.get((methodList.indexOf(oldMn.name)) & (~1)) + "_mui_old"; + nodeData.methods.add(oldMn); + added = true; + break; + } + } + + if (!added) { + System.out.println("Added METHOD: " + targetClassName + "." + mn.name); + nodeData.methods.add(mn); + added = true; + } + } + } + + for (int i = 0; i < nodeSplice.fields.size(); i++) { + if (methodSet.contains(nodeSplice.fields.get(i).name)) { + FieldNode mn = nodeSplice.fields.get(i); + boolean added = false; + + for (int j = 0; j < nodeData.fields.size(); j++) { + if (nodeData.fields.get(j).name.equals(mn.name) + && nodeData.fields.get(j).desc.equals(mn.desc)) { + System.out.println("Spliced in FIELD: " + targetClassName + "." + mn.name); + nodeData.fields.set(j, mn); + added = true; + break; + } + } + + if (!added) { + System.out.println("Added FIELD: " + targetClassName + "." + mn.name); + nodeData.fields.add(mn); + added = true; + } + } + } + + } +} diff --git a/src/main/java/com/cleanroommc/modularui/core/temp/PacketUtilPatch.java b/src/main/java/com/cleanroommc/modularui/core/temp/PacketUtilPatch.java new file mode 100644 index 000000000..74ef51c7b --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/temp/PacketUtilPatch.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018, 2020 Adrian Siekierka + * + * This file is part of StackUp. + * + * StackUp is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * StackUp 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with StackUp. If not, see . + */ + +package com.cleanroommc.modularui.core.temp; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; + +import io.netty.buffer.ByteBuf; + +public class PacketUtilPatch extends PacketBuffer { + + public PacketUtilPatch(ByteBuf wrapped) { + super(wrapped); + } + + public static void writeItemStackFromClientToServer(PacketBuffer buffer, ItemStack stack) { + if (stack.isEmpty()) { + buffer.writeShort(-1); + } else { + buffer.writeShort(Item.getIdFromItem(stack.getItem())); + if (stack.getCount() >= 0 && stack.getCount() <= 64) { + buffer.writeByte(stack.getCount()); + } else { + buffer.writeByte(-42); + buffer.writeInt(stack.getCount()); + } + buffer.writeShort(stack.getMetadata()); + NBTTagCompound tag = null; + + if (stack.getItem().isDamageable() || stack.getItem().getShareTag()) { + tag = stack.getTagCompound(); + } + + buffer.writeCompoundTag(tag); + } + } +}