The Implementer
This commit is contained in:
parent
1f73ca02e9
commit
64ced609cd
|
@ -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!");
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.izzel.arclight.common.asm.ArclightImplementer
|
|
@ -30,6 +30,10 @@ downloader {
|
|||
access-denied = "没有对 {0} 操作的权限: {1}"
|
||||
}
|
||||
|
||||
implementer {
|
||||
not-found = "找不到类 {}"
|
||||
error = "发生错误 {}"
|
||||
}
|
||||
i18n {
|
||||
current-not-available = "选择的语言 {0} 不可用"
|
||||
using-language = "正在使用 {0} 语言,{1} 作为备选语言"
|
||||
|
|
Loading…
Reference in New Issue
Block a user