The Implementer
This commit is contained in:
parent
1f73ca02e9
commit
64ced609cd
|
@ -4,21 +4,27 @@ import java.util.Objects;
|
||||||
|
|
||||||
public class ArclightVersion {
|
public class ArclightVersion {
|
||||||
|
|
||||||
public static final ArclightVersion v1_14 = new ArclightVersion("1.14.4", 1140);
|
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);
|
public static final ArclightVersion v1_15 = new ArclightVersion("1.15.2", 1152, "v1_15_R1");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final int num;
|
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.name = name;
|
||||||
this.num = num;
|
this.num = num;
|
||||||
|
this.pkg = pkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String packageName() {
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ArclightVersion{" +
|
return "ArclightVersion{" +
|
||||||
|
@ -43,6 +49,11 @@ public class ArclightVersion {
|
||||||
|
|
||||||
private static ArclightVersion version;
|
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) {
|
public static void setVersion(ArclightVersion version) {
|
||||||
if (ArclightVersion.version != null) throw new IllegalStateException("Version is already set!");
|
if (ArclightVersion.version != null) throw new IllegalStateException("Version is already set!");
|
||||||
if (version == null) throw new IllegalArgumentException("Version cannot be null!");
|
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}"
|
access-denied = "没有对 {0} 操作的权限: {1}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementer {
|
||||||
|
not-found = "找不到类 {}"
|
||||||
|
error = "发生错误 {}"
|
||||||
|
}
|
||||||
i18n {
|
i18n {
|
||||||
current-not-available = "选择的语言 {0} 不可用"
|
current-not-available = "选择的语言 {0} 不可用"
|
||||||
using-language = "正在使用 {0} 语言,{1} 作为备选语言"
|
using-language = "正在使用 {0} 语言,{1} 作为备选语言"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user