The Implementer

This commit is contained in:
IzzelAliz 2020-07-06 23:37:46 +08:00
parent 1f73ca02e9
commit 64ced609cd
6 changed files with 309 additions and 3 deletions

View File

@ -4,21 +4,27 @@ import java.util.Objects;
public class ArclightVersion {
public static final ArclightVersion v1_14 = new ArclightVersion("1.14.4", 1140);
public static final ArclightVersion v1_15 = new ArclightVersion("1.15.2", 1152);
public static final ArclightVersion v1_14 = new ArclightVersion("1.14.4", 1140, "v1_14_R1");
public static final ArclightVersion v1_15 = new ArclightVersion("1.15.2", 1152, "v1_15_R1");
private final String name;
private final int num;
private final String pkg;
public ArclightVersion(String name, int num) {
public ArclightVersion(String name, int num, String pkg) {
this.name = name;
this.num = num;
this.pkg = pkg;
}
public String getName() {
return name;
}
public String packageName() {
return pkg;
}
@Override
public String toString() {
return "ArclightVersion{" +
@ -43,6 +49,11 @@ public class ArclightVersion {
private static ArclightVersion version;
public static ArclightVersion current() {
if (ArclightVersion.version == null) throw new IllegalStateException("Version is not set!");
return version;
}
public static void setVersion(ArclightVersion version) {
if (ArclightVersion.version != null) throw new IllegalStateException("Version is already set!");
if (version == null) throw new IllegalArgumentException("Version cannot be null!");

View File

@ -0,0 +1,81 @@
package io.izzel.arclight.common.asm;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import io.izzel.arclight.common.mod.util.log.ArclightI18nLogger;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.launch.MixinLaunchPlugin;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class ArclightImplementer implements ILaunchPluginService {
static final Logger LOGGER = ArclightI18nLogger.getLogger("Implementer");
private static final EnumSet<Phase> OH_YES_SIR = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NOT_TODAY = EnumSet.noneOf(Phase.class);
private final Map<String, Implementer> implementers = new HashMap<>();
private volatile Consumer<String[]> auditAcceptor;
private ITransformerLoader transformerLoader;
@Override
public String name() {
return "arclight_implementer";
}
@Override
public void initializeLaunch(ITransformerLoader transformerLoader, Path[] specialPaths) {
this.transformerLoader = transformerLoader;
this.implementers.put("inventory", new InventoryImplementer());
}
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty, String reason) {
if (MixinLaunchPlugin.NAME.equals(reason)) {
return NOT_TODAY;
}
return isEmpty ? NOT_TODAY : OH_YES_SIR;
}
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
throw new IllegalStateException("Outdated ModLauncher");
}
@Override
public void customAuditConsumer(String className, Consumer<String[]> auditDataAcceptor) {
auditAcceptor = auditDataAcceptor;
}
@Override
public boolean processClass(Phase phase, ClassNode classNode, Type classType, String reason) {
if (MixinLaunchPlugin.NAME.equals(reason)) {
return false;
}
List<String> trails = new ArrayList<>();
for (Map.Entry<String, Implementer> entry : implementers.entrySet()) {
String key = entry.getKey();
Implementer implementer = entry.getValue();
if (implementer.processClass(classNode, transformerLoader)) {
trails.add(key);
}
}
if (!trails.isEmpty()) {
this.auditAcceptor.accept(new String[]{String.join(",", trails)});
}
return !trails.isEmpty();
}
@Override
public boolean processClass(Phase phase, ClassNode classNode, Type classType) {
throw new IllegalStateException("Outdated ModLauncher");
}
}

View File

@ -0,0 +1,9 @@
package io.izzel.arclight.common.asm;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import org.objectweb.asm.tree.ClassNode;
public interface Implementer {
boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader);
}

View File

@ -0,0 +1,200 @@
package io.izzel.arclight.common.asm;
import com.google.common.io.ByteStreams;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import io.izzel.arclight.api.ArclightVersion;
import io.izzel.arclight.i18n.LocalizedException;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import java.io.InputStream;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class InventoryImplementer implements Implementer {
private static final Marker MARKER = MarkerManager.getMarker("INVENTORY");
private static final String INV_TYPE = "net/minecraft/inventory/IInventory";
private static final String BRIDGE_TYPE = "io/izzel/arclight/common/bridge/inventory/IInventoryBridge";
private final Map<String, Boolean> map = new ConcurrentHashMap<>();
public InventoryImplementer() {
map.put(INV_TYPE, true);
map.put("java/lang/Object", false);
}
@Override
public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) {
try {
if (node.interfaces.contains(BRIDGE_TYPE)) {
return false;
}
if (isInventoryClass(node, transformerLoader)) {
tryImplement(node);
return true;
} else return false;
} catch (Throwable t) {
if (t instanceof LocalizedException) {
ArclightImplementer.LOGGER.error(MARKER, ((LocalizedException) t).node(), ((LocalizedException) t).args());
} else {
ArclightImplementer.LOGGER.error(t);
}
return false;
}
}
private boolean isInventoryClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) {
if (node.interfaces.contains(INV_TYPE)) {
map.put(node.name, true);
return true;
} else {
Boolean b = map.get(node.superName);
if (b != null) {
map.put(node.name, b);
return b;
} else {
try {
return isInventoryClass(findClass(node.superName, transformerLoader), transformerLoader);
} catch (Throwable t) {
throw LocalizedException.unchecked("implementer.error", t);
}
}
}
}
private ClassNode findClass(String typeName, ILaunchPluginService.ITransformerLoader transformerLoader) throws Exception {
try {
byte[] bytes = transformerLoader.buildTransformedClassNodeFor(Type.getObjectType(typeName).getClassName());
ClassNode node = new ClassNode();
new ClassReader(bytes).accept(node, ClassReader.EXPAND_FRAMES);
return node;
} catch (ClassNotFoundException e) {
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(typeName + ".class");
if (stream == null) throw LocalizedException.checked("implementer.not-found", typeName);
byte[] array = ByteStreams.toByteArray(stream);
ClassNode node = new ClassNode();
new ClassReader(array).accept(node, ClassReader.EXPAND_FRAMES);
return node;
}
}
private void tryImplement(ClassNode node) {
Set<String> methods = new HashSet<>();
for (MethodNode method : node.methods) {
methods.add(method.name + method.desc);
}
if (methods.contains("getViewers()Ljava/util/List;")) {
ArclightImplementer.LOGGER.debug(MARKER, "Found implemented class {}", node.name);
node.interfaces.add(BRIDGE_TYPE);
} else {
List<FieldNode> list = findPossibleList(node);
if (list.size() != 1) {
if (list.size() > 1) {
ArclightImplementer.LOGGER.warn(MARKER, "Found multiple possible fields in class {}: {}", node.name, list.stream().map(it -> it.name + it.desc).collect(Collectors.joining(", ")));
} else return;
}
ArclightImplementer.LOGGER.debug(MARKER, "Implementing inventory for class {}", node.name);
FieldNode stackList = list.get(0);
FieldNode transaction = new FieldNode(Opcodes.ACC_PRIVATE, "transaction", Type.getType(List.class).getDescriptor(), null, null);
FieldNode maxStack = new FieldNode(Opcodes.ACC_PRIVATE, "maxStack", "I", null, 64);
node.fields.add(transaction);
node.fields.add(maxStack);
node.interfaces.add(BRIDGE_TYPE);
{
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "getContents", Type.getMethodDescriptor(Type.getType(List.class)), null, null);
InsnList insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, stackList.name, stackList.desc));
insnList.add(new InsnNode(Opcodes.ARETURN));
methodNode.instructions = insnList;
node.methods.add(methodNode);
}
{
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "onOpen", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("org/bukkit/craftbukkit/" + ArclightVersion.current().packageName() + "/entity/CraftHumanEntity")), null, null);
InsnList insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, transaction.name, transaction.desc));
insnList.add(new VarInsnNode(Opcodes.ALOAD, 1));
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "add", "(Ljava/lang/Object;)Z", false));
insnList.add(new InsnNode(Opcodes.POP));
insnList.add(new InsnNode(Opcodes.RETURN));
methodNode.instructions = insnList;
node.methods.add(methodNode);
}
{
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "onClose", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("org/bukkit/craftbukkit/" + ArclightVersion.current().packageName() + "/entity/CraftHumanEntity")), null, null);
InsnList insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, transaction.name, transaction.desc));
insnList.add(new VarInsnNode(Opcodes.ALOAD, 1));
insnList.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(List.class), "remove", "(Ljava/lang/Object;)Z", false));
insnList.add(new InsnNode(Opcodes.POP));
insnList.add(new InsnNode(Opcodes.RETURN));
methodNode.instructions = insnList;
node.methods.add(methodNode);
}
{
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "getViewers", Type.getMethodDescriptor(Type.getType(List.class)), null, null);
InsnList insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, transaction.name, transaction.desc));
insnList.add(new InsnNode(Opcodes.ARETURN));
methodNode.instructions = insnList;
node.methods.add(methodNode);
}
if (!methods.contains("func_70297_j_()I")) {
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "func_70297_j_", "()I", null, null);
InsnList insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, maxStack.name, maxStack.desc));
insnList.add(new InsnNode(Opcodes.IRETURN));
methodNode.instructions = insnList;
node.methods.add(methodNode);
}
{
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "setMaxStackSize", "(I)V", null, null);
InsnList insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new VarInsnNode(Opcodes.ILOAD, 1));
insnList.add(new FieldInsnNode(Opcodes.PUTFIELD, node.name, maxStack.name, maxStack.desc));
insnList.add(new InsnNode(Opcodes.RETURN));
methodNode.instructions = insnList;
node.methods.add(methodNode);
}
}
}
private List<FieldNode> findPossibleList(ClassNode node) {
LinkedList<FieldNode> list = new LinkedList<>();
for (FieldNode fieldNode : node.fields) {
boolean nonNullList = fieldNode.desc.equals("Lnet/minecraft/util/NonNullList;");
if (nonNullList || fieldNode.desc.equals("Ljava/util/List;")) {
if (fieldNode.signature != null && fieldNode.signature.contains("<Lnet/minecraft/item/ItemStack;>")) {
if (nonNullList) {
list.addFirst(fieldNode);
} else {
list.addLast(fieldNode);
}
}
}
}
return list;
}
}

View File

@ -0,0 +1 @@
io.izzel.arclight.common.asm.ArclightImplementer

View File

@ -30,6 +30,10 @@ downloader {
access-denied = "没有对 {0} 操作的权限: {1}"
}
implementer {
not-found = "找不到类 {}"
error = "发生错误 {}"
}
i18n {
current-not-available = "选择的语言 {0} 不可用"
using-language = "正在使用 {0} 语言,{1} 作为备选语言"