Implement API

This commit is contained in:
IzzelAliz 2021-06-13 22:06:33 +08:00
parent c7bb5dfbf7
commit a4f015fce6
13 changed files with 310 additions and 41 deletions

View File

@ -61,8 +61,8 @@ dependencies {
implementation 'net.md-5:bungeecord-chat:1.16-R0.4'
implementation 'mysql:mysql-connector-java:5.1.49'
implementation 'org.yaml:snakeyaml:1.27'
implementation 'io.izzel:tools:1.1.+'
implementation 'io.izzel.arclight:arclight-api:1.0.+'
implementation "io.izzel:tools:$toolsVersion"
implementation "io.izzel.arclight:arclight-api:$apiVersion"
implementation project(':i18n-config')
}

View File

@ -0,0 +1,26 @@
package io.izzel.arclight.common.mixin.api;
import io.izzel.arclight.api.Arclight;
import io.izzel.arclight.common.mod.util.PluginEventHandler;
import net.minecraftforge.eventbus.EventBus;
import net.minecraftforge.eventbus.api.IEventBus;
import org.bukkit.plugin.Plugin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(value = Arclight.class, remap = false)
public class Arclight_ForgeEventMixin {
/**
* @author IzzelAliz
* @reason
*/
@Overwrite
public static void registerForgeEvent(Plugin plugin, IEventBus bus, Object target) throws Throwable {
if (bus instanceof EventBus) {
PluginEventHandler.register(plugin, (EventBus) bus, target);
} else {
bus.register(target);
}
}
}

View File

@ -15,7 +15,7 @@ public class ArclightConnector implements IMixinConnector {
public static final Logger LOGGER = ArclightI18nLogger.getLogger("Arclight");
private static final List<String> FILTER_PACKAGE = Arrays.asList("com.google.common", "com.google.gson", "ninja.leaping.configurate",
"io.izzel.arclight.api", "io.izzel.arclight.i18n");
"io.izzel.arclight.i18n");
@Override
public void connect() {
@ -26,6 +26,7 @@ public class ArclightConnector implements IMixinConnector {
Mixins.addConfiguration("mixins.arclight.core.json");
Mixins.addConfiguration("mixins.arclight.bukkit.json");
Mixins.addConfiguration("mixins.arclight.forge.json");
Mixins.addConfiguration("mixins.arclight.api.json");
LOGGER.info("mixin-load.core");
}
}

View File

@ -1,5 +1,6 @@
package io.izzel.arclight.common.mod;
import io.izzel.arclight.api.ArclightVersion;
import io.izzel.arclight.common.mod.server.ArclightPermissionHandler;
import io.izzel.arclight.common.mod.server.event.ArclightEventDispatcherRegistry;
import io.izzel.arclight.common.mod.util.log.ArclightI18nLogger;
@ -18,6 +19,7 @@ public class ArclightMod {
public ArclightMod() {
LOGGER.info("mod-load");
ArclightVersion.setVersion(ArclightVersion.v1_16_4);
ArclightEventDispatcherRegistry.registerAllEventDispatchers();
ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.DISPLAYTEST, () -> Pair.of(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true));
PermissionAPI.setPermissionHandler(ArclightPermissionHandler.INSTANCE);

View File

@ -0,0 +1,195 @@
package io.izzel.arclight.common.mod.util;
import io.izzel.arclight.api.Unsafe;
import io.izzel.arclight.common.mod.ArclightMod;
import net.minecraftforge.eventbus.ASMEventHandler;
import net.minecraftforge.eventbus.EventBus;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.IEventListener;
import net.minecraftforge.eventbus.api.IGenericEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import org.bukkit.plugin.Plugin;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.objectweb.asm.Opcodes.*;
public class PluginEventHandler extends ASMEventHandler {
private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class);
private static final String HANDLER_FUNC_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Event.class));
private static final MethodHandle MH_GET_LISTENERS;
private static final MethodHandle MH_ADD_LISTENERS;
private static final MethodHandle MH_UNIQUE_NAME;
static {
try {
MH_GET_LISTENERS = Unsafe.lookup().findGetter(EventBus.class, "listeners", ConcurrentHashMap.class);
MH_ADD_LISTENERS = Unsafe.lookup().findVirtual(EventBus.class, "addToListeners", MethodType.methodType(void.class,
Object.class, Class.class, IEventListener.class, EventPriority.class));
MH_UNIQUE_NAME = Unsafe.lookup().findVirtual(ASMEventHandler.class, "getUniqueName", MethodType.methodType(String.class, Method.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private final Plugin plugin;
public PluginEventHandler(Plugin plugin, Object target, Method method, boolean isGeneric) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
super(target, method, isGeneric);
this.plugin = plugin;
}
@SuppressWarnings("unchecked")
public static void register(Plugin plugin, EventBus bus, Object target) throws Throwable {
ConcurrentHashMap<Object, ?> listeners = (ConcurrentHashMap<Object, ?>) MH_GET_LISTENERS.invokeExact(bus);
if (!listeners.containsKey(target)) {
if (target.getClass() == Class.class) {
registerClass((Class<?>) target, plugin, bus);
} else {
registerObject(target, plugin, bus);
}
}
}
private static void registerClass(final Class<?> clazz, Plugin plugin, EventBus bus) {
Arrays.stream(clazz.getMethods()).
filter(m -> Modifier.isStatic(m.getModifiers())).
filter(m -> m.isAnnotationPresent(SubscribeEvent.class)).
forEach(m -> registerListener(clazz, m, m, plugin, bus));
}
private static Optional<Method> getDeclMethod(final Class<?> clz, final Method in) {
try {
return Optional.of(clz.getDeclaredMethod(in.getName(), in.getParameterTypes()));
} catch (NoSuchMethodException nse) {
return Optional.empty();
}
}
private static void registerObject(final Object obj, Plugin plugin, EventBus bus) {
final HashSet<Class<?>> classes = new HashSet<>();
typesFor(obj.getClass(), classes);
Arrays.stream(obj.getClass().getMethods()).
filter(m -> !Modifier.isStatic(m.getModifiers())).
forEach(m -> classes.stream().
map(c -> getDeclMethod(c, m)).
filter(rm -> rm.isPresent() && rm.get().isAnnotationPresent(SubscribeEvent.class)).
findFirst().
ifPresent(rm -> registerListener(obj, m, rm.get(), plugin, bus)));
}
private static void typesFor(final Class<?> clz, final Set<Class<?>> visited) {
if (clz.getSuperclass() == null) return;
typesFor(clz.getSuperclass(), visited);
Arrays.stream(clz.getInterfaces()).forEach(i -> typesFor(i, visited));
visited.add(clz);
}
private static void registerListener(final Object target, final Method method, final Method real, Plugin plugin, EventBus bus) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1) {
throw new IllegalArgumentException(
"Method " + method + " has @SubscribeEvent annotation. " +
"It has " + parameterTypes.length + " arguments, " +
"but event handler methods require a single argument only."
);
}
Class<?> eventType = parameterTypes[0];
if (!Event.class.isAssignableFrom(eventType)) {
throw new IllegalArgumentException(
"Method " + method + " has @SubscribeEvent annotation, " +
"but takes an argument that is not an Event subtype : " + eventType);
}
register(eventType, target, real, plugin, bus);
}
private static void register(Class<?> eventType, Object target, Method method, Plugin plugin, EventBus bus) {
try {
ASMEventHandler asm = new PluginEventHandler(plugin, target, method, IGenericEvent.class.isAssignableFrom(eventType));
MH_ADD_LISTENERS.invokeExact(bus, target, eventType, (IEventListener) asm, asm.getPriority());
} catch (Throwable e) {
ArclightMod.LOGGER.error("Error registering event handler: {} {}", eventType, method, e);
}
}
@Override
public Class<?> createWrapper(Method callback) {
ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
boolean isStatic = Modifier.isStatic(callback.getModifiers());
String name;
try {
name = (String) MH_UNIQUE_NAME.invoke(this, callback);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
String desc = name.replace('.', '/');
String instType = Type.getInternalName(callback.getDeclaringClass());
String eventType = Type.getInternalName(callback.getParameterTypes()[0]);
cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, desc, null, "java/lang/Object", new String[]{HANDLER_DESC});
cw.visitSource(".dynamic", null);
{
if (!isStatic)
cw.visitField(ACC_PUBLIC, "instance", "Ljava/lang/Object;", null, null).visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", isStatic ? "()V" : "(Ljava/lang/Object;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
if (!isStatic) {
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, desc, "instance", "Ljava/lang/Object;");
}
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "invoke", HANDLER_FUNC_DESC, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
if (!isStatic) {
mv.visitFieldInsn(GETFIELD, desc, "instance", "Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, instType);
}
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, eventType);
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, instType, callback.getName(), Type.getMethodDescriptor(callback), false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
cw.visitEnd();
byte[] bytes = cw.toByteArray();
return Unsafe.defineClass(name, bytes, 0, bytes.length, plugin.getClass().getClassLoader(), plugin.getClass().getProtectionDomain());
}
@Override
public String toString() {
return "PL:" + plugin.getName() + " " + super.toString();
}
}

View File

@ -436,7 +436,7 @@ public class ArclightRedirectAdapter implements PluginTransformer {
}
@Override
public Object[] apply(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
public Object[] apply4(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
try {
Method handleMethod = remapper.getGeneratedHandlerClass().getMethod(handlerName, handlerArgs);
if (method.getParameterCount() > 0) {
@ -467,7 +467,7 @@ public class ArclightRedirectAdapter implements PluginTransformer {
}
@Override
public Object[] apply(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
public Object[] apply4(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
try {
Method redirectMethod = remapper.getGeneratedHandlerClass().getMethod(handlerName, handlerArgs);
return new Object[]{redirectMethod, null, Modifier.isStatic(method.getModifiers()) ? param : ArrayUtil.prepend(param, src)};
@ -489,7 +489,7 @@ public class ArclightRedirectAdapter implements PluginTransformer {
}
@Override
public Object[] apply(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
public Object[] apply4(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
boolean bridgeStatic = Modifier.isStatic(targetMethod.getModifiers());
if (bridgeStatic) {
Object[] ret = bridge.apply(remapper, this.targetMethod, null, param);

View File

@ -1,5 +1,6 @@
package io.izzel.arclight.common.mod.util.remapper;
import io.izzel.arclight.api.PluginPatcher;
import net.md_5.specialsource.repo.ClassRepo;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
@ -10,7 +11,7 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
public class ClassLoaderRepo implements ClassRepo {
public class ClassLoaderRepo implements ClassRepo, PluginPatcher.ClassRepo {
private final ClassLoader classLoader;
@ -20,6 +21,11 @@ public class ClassLoaderRepo implements ClassRepo {
@Override
public ClassNode findClass(String internalName) {
return findClass(internalName, ClassReader.SKIP_CODE);
}
@Override
public ClassNode findClass(String internalName, int parsingOptions) {
URL url = classLoader instanceof URLClassLoader
? ((URLClassLoader) classLoader).findResource(internalName + ".class") // search local
: classLoader.getResource(internalName + ".class");
@ -29,7 +35,7 @@ public class ClassLoaderRepo implements ClassRepo {
try (InputStream inputStream = connection.getInputStream()) {
ClassReader reader = new ClassReader(inputStream);
ClassNode classNode = new ClassNode();
reader.accept(classNode, ClassReader.SKIP_CODE);
reader.accept(classNode, parsingOptions);
return classNode;
}
} catch (IOException ignored) {

View File

@ -3,7 +3,9 @@ package io.izzel.arclight.common.mod.util.remapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import io.izzel.arclight.api.PluginPatcher;
import net.md_5.specialsource.repo.ClassRepo;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.service.MixinService;
@ -13,7 +15,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class GlobalClassRepo implements ClassRepo {
public class GlobalClassRepo implements ClassRepo, PluginPatcher.ClassRepo {
public static final GlobalClassRepo INSTANCE = new GlobalClassRepo();
private static final PluginInheritanceProvider PROVIDER = new PluginInheritanceProvider(INSTANCE);
@ -35,6 +37,21 @@ public class GlobalClassRepo implements ClassRepo {
}
}
@Override
public ClassNode findClass(String internalName, int parsingOptions) {
if (parsingOptions == ClassReader.SKIP_CODE) {
return findClass(internalName);
} else {
return this.repos.parallelStream()
.filter(PluginPatcher.ClassRepo.class::isInstance)
.map(PluginPatcher.ClassRepo.class::cast)
.map(it -> it.findClass(internalName, parsingOptions))
.filter(Objects::nonNull)
.findAny()
.orElseGet(() -> this.findMinecraft(internalName));
}
}
private ClassNode findParallel(String internalName) {
return this.repos.parallelStream()
.map(it -> it.findClass(internalName))

View File

@ -1,35 +1,37 @@
package io.izzel.arclight.common.mod.util.remapper.patcher;
import io.izzel.arclight.api.PluginPatcher;
import io.izzel.arclight.common.mod.ArclightMod;
import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper;
import io.izzel.arclight.common.mod.util.remapper.GlobalClassRepo;
import io.izzel.arclight.common.mod.util.remapper.PluginTransformer;
import org.bukkit.configuration.file.YamlConfiguration;
import org.objectweb.asm.tree.ClassNode;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ArclightPluginPatcher implements PluginTransformer {
private final List<PluginTransformer> list;
private final List<PluginPatcher> list;
public ArclightPluginPatcher(List<PluginTransformer> list) {
public ArclightPluginPatcher(List<PluginPatcher> list) {
this.list = list;
}
@Override
public void handleClass(ClassNode node, ClassLoaderRemapper remapper) {
for (PluginTransformer transformer : list) {
transformer.handleClass(node, remapper);
for (PluginPatcher patcher : list) {
patcher.handleClass(node, GlobalClassRepo.INSTANCE);
}
}
@ -37,32 +39,36 @@ public class ArclightPluginPatcher implements PluginTransformer {
File pluginFolder = new File("plugins");
if (pluginFolder.exists()) {
ArclightMod.LOGGER.info("patcher.loading");
ArrayList<PluginTransformer> list = new ArrayList<>();
for (File file : pluginFolder.listFiles()) {
if (file.isFile() && file.getName().endsWith(".jar")) {
loadFromJar(file.toPath()).ifPresent(list::add);
ArrayList<PluginPatcher> list = new ArrayList<>();
File[] files = pluginFolder.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".jar")) {
loadFromJar(file).ifPresent(list::add);
}
}
if (!list.isEmpty()) {
list.sort(Comparator.comparing(PluginPatcher::priority));
ArclightMod.LOGGER.info("patcher.loaded", list.size());
transformerList.add(new ArclightPluginPatcher(list));
}
}
if (!list.isEmpty()) {
list.sort(Comparator.comparing(PluginTransformer::priority));
ArclightMod.LOGGER.info("patcher.loaded", list.size());
transformerList.add(new ArclightPluginPatcher(list));
}
}
}
private static Optional<PluginTransformer> loadFromJar(Path path) {
try {
FileSystem fileSystem = FileSystems.newFileSystem(path, ArclightPluginPatcher.class.getClassLoader());
Path pluginYml = fileSystem.getPath("plugin.yml");
if (Files.exists(pluginYml)) {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(Files.newBufferedReader(pluginYml));
String name = configuration.getString("arclight.patcher");
if (name != null) {
URLClassLoader loader = new URLClassLoader(new URL[]{path.toUri().toURL()}, ArclightPluginPatcher.class.getClassLoader());
Class<?> clazz = Class.forName(name, false, loader);
PluginTransformer transformer = clazz.asSubclass(PluginTransformer.class).newInstance();
return Optional.of(transformer);
private static Optional<PluginPatcher> loadFromJar(File file) {
try (JarFile jarFile = new JarFile(file)) {
JarEntry jarEntry = jarFile.getJarEntry("plugin.yml");
if (jarEntry != null) {
try (InputStream stream = jarFile.getInputStream(jarEntry)) {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(new InputStreamReader(stream));
String name = configuration.getString("arclight.patcher");
if (name != null) {
URLClassLoader loader = new URLClassLoader(new URL[]{file.toURI().toURL()}, ArclightPluginPatcher.class.getClassLoader());
Class<?> clazz = Class.forName(name, false, loader);
PluginPatcher patcher = clazz.asSubclass(PluginPatcher.class).getConstructor().newInstance();
return Optional.of(patcher);
}
}
}
} catch (Throwable e) {

View File

@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.izzel.arclight.common.mixin.api",
"target": "@env(DEFAULT)",
"refmap": "mixins.arclight.refmap.json",
"setSourceFile": true,
"injectors": {
"defaultRequire": 1
},
"mixins": [
"Arclight_ForgeEventMixin"
]
}

View File

@ -97,8 +97,8 @@ dependencies {
embed 'net.md-5:bungeecord-chat:1.16-R0.4@jar'
embed "org.spigotmc:spigot-api:$minecraftVersion-R0.1-SNAPSHOT@jar"
embed 'com.github.ArclightTeam:mixin-tools:1.0.0@jar'
embed 'io.izzel:tools:1.1.+'
embed 'io.izzel.arclight:arclight-api:1.0.+'
embed "io.izzel:tools:$toolsVersion"
embed "io.izzel.arclight:arclight-api:$apiVersion"
annotationProcessor 'org.spongepowered:mixin:0.8.2:processor'
annotationProcessor 'com.github.ArclightTeam:mixin-tools:1.0.0'
}

View File

@ -6,6 +6,8 @@ allprojects {
agpVersion = '1.17'
minecraftVersion = '1.16.5'
forgeVersion = '36.1.24'
apiVersion = '1.0.+'
toolsVersion = '1.3.+'
}
task cleanBuild {

View File

@ -13,6 +13,6 @@ repositories {
dependencies {
compile 'com.google.code.gson:gson:2.8.0'
compile 'io.izzel.arclight:arclight-api:1.0.+'
compile "io.izzel.arclight:arclight-api:$apiVersion"
compile project(':i18n-config')
}