Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions src/main/java/com/cleanroommc/modularui/core/ClassTransformer.java
Original file line number Diff line number Diff line change
@@ -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<ClassNode> 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;
}
Expand Down
157 changes: 157 additions & 0 deletions src/main/java/com/cleanroommc/modularui/core/temp/ClassSplicer.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<ClassNode> 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<String> methodSet = Sets.newHashSet(methods);
final List<String> 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<AbstractInsnNode> 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;
}
}
}

}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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);
}
}
}