Skip to content

Commit

Permalink
Merge branch 'staging' into rfb-support
Browse files Browse the repository at this point in the history
  • Loading branch information
gravit0 authored Oct 21, 2024
2 parents 8219966 + 965ca5e commit bb526e8
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 3 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ dependencies {
libraries "javax.servlet:javax.servlet-api:4.0.1"
libraries 'com.sun.xml.bind:jaxb-impl:3.0.2'
libraries 'org.openjdk.nashorn:nashorn-core:15.4'
libraries 'it.unimi.dsi:fastutil:8.5.4'
libraries 'it.unimi.dsi:fastutil:8.5.12'

// Other libs
libraries 'pw.prok:Damask:0.1.2@jar'
Expand Down Expand Up @@ -287,4 +287,4 @@ def generateMavenLibs() {
libs += " ${it.group}:${it.name}:${it.version}"
}
return libs.trim()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--- ../src-base/minecraft/cpw/mods/fml/common/eventhandler/ASMEventHandler.java
+++ ../src-work/minecraft/cpw/mods/fml/common/eventhandler/ASMEventHandler.java
@@ -36,6 +36,13 @@
readable = "ASM: " + target + " " + method.getName() + Type.getMethodDescriptor(method);
}

+ public ASMEventHandler(IEventListener handler, Object target, Method method, ModContainer owner, String readable) {
+ this.handler = handler;
+ this.subInfo = method.getAnnotation(SubscribeEvent.class);
+ this.owner = owner;
+ this.readable = readable;
+ }
+
@Override
public void invoke(Event event)
{
33 changes: 32 additions & 1 deletion patches/cpw/mods/fml/common/eventhandler/EventBus.java.patch
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,29 @@
import org.apache.logging.log4j.Level;

import com.google.common.base.Preconditions;
@@ -131,19 +135,44 @@
@@ -118,6 +122,21 @@
}
}

+ public void crucible_register(ASMEventHandler listener, Class<?> eventType, Object target, Method method, ModContainer owner) throws Exception {
+ Constructor<?> ctr = eventType.getConstructor();
+ ctr.setAccessible(true);
+ Event event = (Event)ctr.newInstance();
+ event.getListenerList().register(busID, listener.getPriority(), listener);
+
+ ArrayList<IEventListener> others = listeners.get(target);
+ if (others == null)
+ {
+ others = new ArrayList<IEventListener>();
+ listeners.put(target, others);
+ }
+ others.add(listener);
+ }
+
public void unregister(Object object)
{
ArrayList<IEventListener> list = listeners.remove(object);
@@ -131,19 +150,44 @@

public boolean post(Event event)
{
Expand Down Expand Up @@ -66,3 +88,12 @@
}
return (event.isCancelable() ? event.isCanceled() : false);
}
@@ -158,4 +202,8 @@
FMLLog.log(Level.ERROR, "%d: %s", x, listeners[x]);
}
}
+
+ public ConcurrentHashMap<Object, ArrayList<IEventListener>> getListeners() {
+ return listeners;
+ }
}
119 changes: 119 additions & 0 deletions src/main/java/io/github/crucible/api/CrucibleEventBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.github.crucible.api;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.*;
import io.github.crucible.CrucibleModContainer;
import io.github.crucible.eventfactory.PluginClassLoaderFactory;
import net.minecraftforge.common.MinecraftForge;
import org.bukkit.plugin.Plugin;
import org.objectweb.asm.Type;

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;

public class CrucibleEventBus {

/**
* Register an object to the specific forge event bus!
*
*
* @param plugin The plugin that is registering the event, can be 'null'
*
* @param bus The event bus to register to:
* - You can use {@link MinecraftForge#EVENT_BUS}
* - You can use {@link FMLCommonHandler#bus()}
* - Any other bus.
*
* @param target Either a {@link Class} or an arbitrary object.
* The object maybe have methods annotated with {@link SubscribeEvent}
* The class maybe have static methods annotated with {@link SubscribeEvent}
*/
@SuppressWarnings("unchecked")
public static void register(Plugin plugin, EventBus bus, Object target) {
ConcurrentHashMap<Object, ?> listeners = bus.getListeners();
if (!listeners.containsKey(target)) {
if (target.getClass() == Class.class) {
registerClass(plugin, (Class<?>) target, bus);
} else {
registerObject(plugin, target, bus);
}
}
}

private static void registerClass(Plugin plugin, final Class<?> clazz, EventBus bus) {
Arrays.stream(clazz.getMethods()).
filter(m -> Modifier.isStatic(m.getModifiers())).
filter(m -> m.isAnnotationPresent(SubscribeEvent.class)).
forEach(m -> registerListener(plugin, clazz, m, m, bus));
}

private static void registerObject(Plugin plugin, final Object obj, 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(plugin, obj, m, rm.get(), bus)));
}

private static void registerListener(Plugin plugin, final Object target, final Method method, final Method real, 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(plugin, eventType, target, real, bus);
}

private static void register(Plugin plugin, Class<?> eventType, Object target, Method method, EventBus bus) {
String pluginName = (plugin != null ? plugin.getName() : "null");
try {
PluginClassLoaderFactory factory = new PluginClassLoaderFactory();
IEventListener iEventListener = factory.create(method, target);
String readable = "ASM_CRUCIBLE: (Plugin='" + pluginName + "') " + target + " " + method.getName() + Type.getMethodDescriptor(method);
ASMEventHandler asm = new ASMEventHandler(iEventListener, target, method, CrucibleModContainer.instance, readable);
bus.crucible_register(asm, eventType, target, method, CrucibleModContainer.instance);
} catch (Throwable e) {
CrucibleModContainer.logger.error("Error registering event handler for the plugin ({}): {} {}", pluginName, eventType, method, e);
}
}

// -----------------------------------------------------------------------------------------------------------------
// The following methods are utilities that come from isnide the EventBus on modern forge.
// -----------------------------------------------------------------------------------------------------------------

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 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package io.github.crucible.eventfactory;

import com.google.common.collect.Maps;
import cpw.mods.fml.common.eventhandler.Event;
import cpw.mods.fml.common.eventhandler.IEventListener;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static org.objectweb.asm.Opcodes.*;

public class PluginClassLoaderFactory {
private static AtomicInteger IDs = new AtomicInteger();
private static final HashMap<Method, Class<?>> cache = Maps.newHashMap();
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));

public IEventListener create(Method method, Object target) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Class<?> cls = createWrapper(method);
if (Modifier.isStatic(method.getModifiers()))
return (IEventListener)cls.getDeclaredConstructor().newInstance();
else
return (IEventListener)cls.getConstructor(Object.class).newInstance(target);
}

public Class<?> createWrapper(Method callback) {
if (cache.containsKey(callback)) {
return cache.get(callback);
}

ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;

boolean isStatic = Modifier.isStatic(callback.getModifiers());
String name = getUniqueName(callback);
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();

PluginASMClassLoader LOADER = new PluginASMClassLoader(callback.getDeclaringClass().getClassLoader(), callback.getDeclaringClass().getProtectionDomain());
Class<?> ret = LOADER.define(name, bytes);
cache.put(callback, ret);
return ret;
}

public String getUniqueName(Method callback) {
return String.format("%s_%d_%s_%s_%s",
getClass().getName(),
IDs.incrementAndGet(),
callback.getDeclaringClass().getSimpleName(),
callback.getName(),
callback.getParameterTypes()[0].getSimpleName());
}

private static class PluginASMClassLoader extends ClassLoader {
private final ProtectionDomain protectionDomain;

private PluginASMClassLoader(ClassLoader classLoader, ProtectionDomain protectionDomain) {
super(classLoader);
this.protectionDomain = protectionDomain;
}

public Class<?> define(String name, byte[] data) {
return defineClass(name, data, 0, data.length, protectionDomain);
}
}
}

0 comments on commit bb526e8

Please sign in to comment.