From 26a06d3864955f6a5b9d1381ceff185be280a6c9 Mon Sep 17 00:00:00 2001 From: jchung01 Date: Fri, 4 Jul 2025 21:35:07 -0700 Subject: [PATCH 1/3] Temporarily replace transformer logic with AE2 ASM --- .../modularui/core/ClassTransformer.java | 20 ++- .../modularui/core/temp/ClassSplicer.java | 157 ++++++++++++++++++ .../modularui/core/temp/PacketUtilPatch.java | 56 +++++++ 3 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/core/temp/ClassSplicer.java create mode 100644 src/main/java/com/cleanroommc/modularui/core/temp/PacketUtilPatch.java diff --git a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java index 749b94001..29d3bd4a1 100644 --- a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java +++ b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java @@ -1,11 +1,15 @@ 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 { @@ -14,10 +18,18 @@ 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(); + // Temporarily use AE2's implementation + Consumer consumer = (n) -> {}; + consumer = consumer.andThen((node) -> { + ClassSplicer.spliceClasses(node, "com.cleanroommc.modularui.core.temp.PacketUtilPatch", + "writeItemStackFromClientToServer"); + }); + return ClassSplicer.processNode(basicClass, consumer); + +// 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); + } + } +} From c3256aff8a60b80fcd1d35264d154039d9692e97 Mon Sep 17 00:00:00 2001 From: jchung01 Date: Sat, 5 Jul 2025 14:28:48 -0700 Subject: [PATCH 2/3] Add PacketBuffer patch --- .../modularui/core/ClassTransformer.java | 28 +++++-- .../core/temp/PacketBufferPatch.java | 79 +++++++++++++++++++ 2 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java diff --git a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java index 29d3bd4a1..45a3bd0cf 100644 --- a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java +++ b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java @@ -15,17 +15,29 @@ 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))) { + if (!ModularUICore.stackUpLoaded) { // Temporarily use AE2's implementation Consumer consumer = (n) -> {}; - consumer = consumer.andThen((node) -> { - ClassSplicer.spliceClasses(node, "com.cleanroommc.modularui.core.temp.PacketUtilPatch", - "writeItemStackFromClientToServer"); - }); - return ClassSplicer.processNode(basicClass, consumer); + Consumer emptyConsumer = consumer; + if (transformedName.equals(PacketByteBufferVisitor.PACKET_UTIL_CLASS)) { + consumer = consumer.andThen((node) -> { + ClassSplicer.spliceClasses(node, "com.cleanroommc.modularui.core.temp.PacketUtilPatch", + "writeItemStackFromClientToServer"); + }); + } else if (!ModularUICore.ae2Loaded && transformedName.equals(PacketByteBufferVisitor.PACKET_BUFFER_CLASS)) { + consumer = consumer.andThen((node) -> { + ClassSplicer.spliceClasses(node, "com.cleanroommc.modularui.core.temp.PacketBufferPatch", + "readItemStack", "func_150791_c", + "writeItemStack", "func_150788_a"); + }); + } + + if (consumer != emptyConsumer) { + return ClassSplicer.processNode(basicClass, consumer); + } else { + return basicClass; + } // ClassWriter classWriter = new ClassWriter(0); // new ClassReader(basicClass).accept(new PacketByteBufferVisitor(classWriter), 0); // ModularUICore.LOGGER.info("Applied {} ASM from ModularUI", transformedName); diff --git a/src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java b/src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java new file mode 100644 index 000000000..cc07fbb81 --- /dev/null +++ b/src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java @@ -0,0 +1,79 @@ +/* + * 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; + +import java.io.IOException; + +public class PacketBufferPatch extends PacketBuffer { + + public PacketBufferPatch(ByteBuf wrapped) { + super(wrapped); + } + + @Override + public ItemStack readItemStack() throws IOException { + int id = this.readShort(); + + if (id < 0) { + return ItemStack.EMPTY; + } else { + int count = this.readByte(); + if (count == -42) { + count = this.readInt(); + } + int damage = this.readShort(); + ItemStack itemstack = new ItemStack(Item.getItemById(id), count, damage); + itemstack.getItem().readNBTShareTag(itemstack, this.readCompoundTag()); + return itemstack; + } + } + + @Override + public PacketBuffer writeItemStack(ItemStack stack) { + if (stack.isEmpty()) { + this.writeShort(-1); + } else { + this.writeShort(Item.getIdFromItem(stack.getItem())); + if (stack.getCount() >= 0 && stack.getCount() <= 64) { + this.writeByte(stack.getCount()); + } else { + this.writeByte(-42); + this.writeInt(stack.getCount()); + } + this.writeShort(stack.getMetadata()); + NBTTagCompound tag = null; + + if (stack.getItem().isDamageable() || stack.getItem().getShareTag()) { + tag = stack.getItem().getNBTShareTag(stack); + } + + this.writeCompoundTag(tag); + } + + return this; + } +} From fc5eb49607bc65b6e635950d94ec0ed374f6b57c Mon Sep 17 00:00:00 2001 From: jchung01 Date: Sat, 5 Jul 2025 14:53:15 -0700 Subject: [PATCH 3/3] Only use AE2's impl if the mod's loaded --- .../modularui/core/ClassTransformer.java | 33 +++----- .../core/temp/PacketBufferPatch.java | 79 ------------------- 2 files changed, 11 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java diff --git a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java index 45a3bd0cf..3292317ba 100644 --- a/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java +++ b/src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java @@ -16,32 +16,21 @@ public class ClassTransformer implements IClassTransformer { @Override public byte[] transform(String name, String transformedName, byte[] basicClass) { if (!ModularUICore.stackUpLoaded) { - // Temporarily use AE2's implementation - Consumer consumer = (n) -> {}; - Consumer emptyConsumer = consumer; - - if (transformedName.equals(PacketByteBufferVisitor.PACKET_UTIL_CLASS)) { - consumer = consumer.andThen((node) -> { + // 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"); - }); - } else if (!ModularUICore.ae2Loaded && transformedName.equals(PacketByteBufferVisitor.PACKET_BUFFER_CLASS)) { - consumer = consumer.andThen((node) -> { - ClassSplicer.spliceClasses(node, "com.cleanroommc.modularui.core.temp.PacketBufferPatch", - "readItemStack", "func_150791_c", - "writeItemStack", "func_150788_a"); - }); - } - - if (consumer != emptyConsumer) { + }; + ModularUICore.LOGGER.info("Applied {} ASM, specific for AE2, from ModularUI", transformedName); return ClassSplicer.processNode(basicClass, consumer); - } else { - return basicClass; + } 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(); } -// 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/PacketBufferPatch.java b/src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java deleted file mode 100644 index cc07fbb81..000000000 --- a/src/main/java/com/cleanroommc/modularui/core/temp/PacketBufferPatch.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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; - -import java.io.IOException; - -public class PacketBufferPatch extends PacketBuffer { - - public PacketBufferPatch(ByteBuf wrapped) { - super(wrapped); - } - - @Override - public ItemStack readItemStack() throws IOException { - int id = this.readShort(); - - if (id < 0) { - return ItemStack.EMPTY; - } else { - int count = this.readByte(); - if (count == -42) { - count = this.readInt(); - } - int damage = this.readShort(); - ItemStack itemstack = new ItemStack(Item.getItemById(id), count, damage); - itemstack.getItem().readNBTShareTag(itemstack, this.readCompoundTag()); - return itemstack; - } - } - - @Override - public PacketBuffer writeItemStack(ItemStack stack) { - if (stack.isEmpty()) { - this.writeShort(-1); - } else { - this.writeShort(Item.getIdFromItem(stack.getItem())); - if (stack.getCount() >= 0 && stack.getCount() <= 64) { - this.writeByte(stack.getCount()); - } else { - this.writeByte(-42); - this.writeInt(stack.getCount()); - } - this.writeShort(stack.getMetadata()); - NBTTagCompound tag = null; - - if (stack.getItem().isDamageable() || stack.getItem().getShareTag()) { - tag = stack.getItem().getNBTShareTag(stack); - } - - this.writeCompoundTag(tag); - } - - return this; - } -}