package lilypad.bukkit.connect.injector; import lilypad.bukkit.connect.util.JavassistUtil; import sun.reflect.ReflectionFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import javassist.CtMethod; import javassist.Modifier; import javassist.NotFoundException; import lilypad.bukkit.connect.ConnectPlugin; import lilypad.bukkit.connect.util.ReflectionUtils; import org.bukkit.Server; @SuppressWarnings("restriction") public class OfflineInjector { private static Object offlineMinecraftServer; public static void inject(Server server) throws Exception { Method serverGetHandle = server.getClass().getDeclaredMethod("getServer"); Object minecraftServer = serverGetHandle.invoke(server); // create offline minecraftServer ClassPool classPool = JavassistUtil.getClassPool(); CtClass minecraftServerClass = classPool.getCtClass(minecraftServer.getClass().getName()); CtClass offlineMinecraftServerClass = classPool.makeClass(minecraftServer.getClass().getName() + "$offline"); offlineMinecraftServerClass.setSuperclass(minecraftServerClass); // ... create delegate field CtField delegateField = new CtField(minecraftServerClass, "delegate", offlineMinecraftServerClass); offlineMinecraftServerClass.addField(delegateField); // ... add our special getOfflineMode CtMethod getOnlineModeMethod = new CtMethod(minecraftServerClass.getSuperclass().getDeclaredMethod("getOnlineMode").getReturnType(), "getOnlineMode", new CtClass[] { }, offlineMinecraftServerClass); getOnlineModeMethod.setBody("{ return false; }"); offlineMinecraftServerClass.addMethod(getOnlineModeMethod); // ... proxy all declared methods recursively CtClass cursorClass = minecraftServerClass; while (true) { for (CtMethod method : cursorClass.getDeclaredMethods()) { if(Modifier.isFinal(method.getModifiers())) { continue; } if(Modifier.isPrivate(method.getModifiers())) { continue; } try { offlineMinecraftServerClass.getDeclaredMethod(method.getName(), method.getParameterTypes()); continue; } catch(NotFoundException exception) { // proceed } CtMethod proxyMethod = new CtMethod(method.getReturnType(), method.getName(), method.getParameterTypes(), offlineMinecraftServerClass); proxyMethod.setBody("{ return ($r)this.delegate." + method.getName() + "($$); }"); offlineMinecraftServerClass.addMethod(proxyMethod); } cursorClass = cursorClass.getSuperclass(); if (cursorClass == null) { break; } if (cursorClass.getName().equals("java.lang.Object")) { break; } } // ... make a blank constructor // don't need to make a blank constructor for 1.9 if (ConnectPlugin.getProtocol().isOfflineBlankConstructor()) { CtConstructor constructor = new CtConstructor(new CtClass[] { }, offlineMinecraftServerClass); constructor.setBody("{ super(null); }"); offlineMinecraftServerClass.addConstructor(constructor); } // ... create our class Class<?> offlineMinecraftServerJClass = offlineMinecraftServerClass.toClass(); // ... create an instance of our class without calling the constructor ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<?> objectConstructor = Object.class.getDeclaredConstructor(); Constructor<?> serializeConstructor = reflectionFactory.newConstructorForSerialization(offlineMinecraftServerJClass, objectConstructor); offlineMinecraftServer = serializeConstructor.newInstance(); // ... set our delegate, among other stuff ReflectionUtils.setFinalField(offlineMinecraftServer.getClass(), offlineMinecraftServer, "delegate", minecraftServer); ReflectionUtils.setFinalField(offlineMinecraftServer.getClass().getSuperclass().getSuperclass(), offlineMinecraftServer, "server", server); ReflectionUtils.setFinalField(offlineMinecraftServer.getClass().getSuperclass().getSuperclass(), offlineMinecraftServer, "processQueue", ReflectionUtils.getPrivateField(minecraftServer.getClass().getSuperclass(), minecraftServer, Object.class, "processQueue")); // get server connection Method serverConnectionMethod = null; for (Method method : minecraftServer.getClass().getSuperclass().getDeclaredMethods()) { if (!method.getReturnType().getSimpleName().equals("ServerConnection")) { continue; } serverConnectionMethod = method; break; } Object serverConnection = serverConnectionMethod.invoke(minecraftServer); // set server connection minecraftServer ReflectionUtils.setFinalField(serverConnection.getClass(), serverConnection, ConnectPlugin.getProtocol().getOfflineInjectorServerConnection(), offlineMinecraftServer); } public static Object getOfflineMinecraftServer() { return offlineMinecraftServer; } }