Adding "support" for Spout by proxying it's NetServerHandler.

While it may seem better to use a Spout PacketListener, we can't
prevent other spout listeners from stopping the listener
chain. For instance, Orebfuscator does supports Spout, but does this
by cancelling every chunk packet and sending them to be processed and
sent by Orebfuscator only. This can never be made compatible with
other plugins.

So, we choose to add some overhead and inject our proxy onto Spout. 
Fortunately, Spout injects the proxy using a player listener on LOWEST, 
so we get to override Spout again with our HIGHEST. The proxy method
should be generic enough to handle most proxy types.
This commit is contained in:
Kristian S. Stangeland 2012-10-05 01:41:17 +02:00
parent af3e278e06
commit 18ef06ea21
9 changed files with 78 additions and 71 deletions

View File

@ -10,6 +10,5 @@
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jre6"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jre6"/>
<classpathentry kind="lib" path="D:/Games/Minecraft/Server Mods/1.3.2/SpoutPlugin.jar"/>
<classpathentry kind="output" path="class"/> <classpathentry kind="output" path="class"/>
</classpath> </classpath>

View File

@ -441,17 +441,17 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
try { try {
manager.registerEvents(new Listener() { manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
playerInjection.injectPlayer(event.getPlayer()); playerInjection.injectPlayer(event.getPlayer());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
playerInjection.uninjectPlayer(event.getPlayer()); playerInjection.uninjectPlayer(event.getPlayer());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPluginDisabled(PluginDisableEvent event) { public void onPluginDisabled(PluginDisableEvent event) {
// Clean up in case the plugin forgets // Clean up in case the plugin forgets
if (event.getPlugin() != plugin) { if (event.getPlugin() != plugin) {
@ -487,7 +487,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority"); Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
// Get the priority // Get the priority
Object priorityNormal = Enum.valueOf(eventPriority, "Normal"); Object priorityNormal = Enum.valueOf(eventPriority, "Highest");
// Get event types // Get event types
Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN"); Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");

View File

@ -136,8 +136,17 @@ public class NetworkServerInjector extends PlayerInjector {
}); });
// Use the existing field values when we create our copy // Use the existing field values when we create our copy
DefaultInstances serverInstances = DefaultInstances.fromArray( DefaultInstances serverInstances = null;
if (hasProxyServerHandler()) {
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
serverInstances = DefaultInstances.fromArray(
ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass));
} else {
serverInstances = DefaultInstances.fromArray(
ExistingGenerator.fromObjectFields(serverHandler)); ExistingGenerator.fromObjectFields(serverHandler));
}
serverInstances.setNonNull(true); serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1); serverInstances.setMaximumRecursion(1);
@ -154,6 +163,15 @@ public class NetworkServerInjector extends PlayerInjector {
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object."); "Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
} }
} }
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (clazz.getName().startsWith("net.minecraft"))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return clazz.getSuperclass();
}
@Override @Override
public void cleanupAll() { public void cleanupAll() {

View File

@ -1,60 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
public class NetworkSpoutHook extends PlayerInjector {
public NetworkSpoutHook(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
super(logger, player, invoker);
}
@Override
protected boolean hasListener(int packetID) {
return false;
}
@Override
public boolean canInject() {
return getSpout() != null;
}
private Plugin getSpout() {
// Spout must be loaded
try {
return Bukkit.getServer().getPluginManager().getPlugin("Spout");
} catch (Throwable e) {
return null;
}
}
@Override
public void injectManager() {
}
@Override
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
}
@Override
public void cleanupAll() {
// TODO Auto-generated method stub
}
@Override
public void checkListener(PacketListener listener) {
// We support everything Spout does
}
}

View File

@ -23,6 +23,11 @@ import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
public class PlayerInjectionHandler { public class PlayerInjectionHandler {
// Server connection injection // Server connection injection

View File

@ -50,6 +50,9 @@ abstract class PlayerInjector {
protected static Field inputField; protected static Field inputField;
protected static Field netHandlerField; protected static Field netHandlerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists // To add our injected array lists
protected static StructureModifier<Object> networkModifier; protected static StructureModifier<Object> networkModifier;
@ -142,6 +145,14 @@ abstract class PlayerInjector {
} }
} }
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
private Field getProxyField(EntityPlayer notchEntity, Field serverField) { private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
try { try {
@ -154,6 +165,8 @@ abstract class PlayerInjector {
if (handler instanceof Factory) if (handler instanceof Factory)
return null; return null;
hasProxyType = true;
// No? Is it a Proxy type? // No? Is it a Proxy type?
try { try {
FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true); FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
@ -162,7 +175,7 @@ abstract class PlayerInjector {
return reflection.getFieldByType(".*NetServerHandler"); return reflection.getFieldByType(".*NetServerHandler");
} catch (RuntimeException e) { } catch (RuntimeException e) {
logger.log(Level.WARNING, "Server handler is a proxy type.", e); logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
} }
} }

View File

@ -55,6 +55,14 @@ public class ObjectCloner {
// System.out.println(String.format("Writing value %s to %s", // System.out.println(String.format("Writing value %s to %s",
// value, modifier.getFields().get(i).getName())); // value, modifier.getFields().get(i).getName()));
} }
// Copy private fields underneath
Class<?> superclass = commonType.getSuperclass();
if (!superclass.equals(Object.class)) {
copyTo(source, destination, superclass);
}
} catch (FieldAccessException e) { } catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e); throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
} }

View File

@ -235,8 +235,9 @@ public class DefaultInstances {
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1); params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
// Did we break the non-null contract? // Did we break the non-null contract?
if (params[i] == null && nonNull) if (params[i] == null && nonNull) {
return null; return null;
}
} }
return createInstance(type, minimum, types, params); return createInstance(type, minimum, types, params);
@ -244,7 +245,6 @@ public class DefaultInstances {
} catch (Exception e) { } catch (Exception e) {
// Nope, we couldn't create this type // Nope, we couldn't create this type
e.printStackTrace();
} }
// No suitable default value could be found // No suitable default value could be found

View File

@ -33,13 +33,37 @@ public class ExistingGenerator implements InstanceProvider {
* @return The instance generator. * @return The instance generator.
*/ */
public static ExistingGenerator fromObjectFields(Object object) { public static ExistingGenerator fromObjectFields(Object object) {
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
return fromObjectFields(object, object.getClass());
}
/**
* Automatically create an instance provider from a objects public and private fields.
* <p>
* If two or more fields share the same type, the last declared non-null field will take
* precedent.
* @param object - object to create an instance generator from.
* @param type - the type to cast the object.
* @return The instance generator.
*/
public static ExistingGenerator fromObjectFields(Object object, Class<?> type) {
ExistingGenerator generator = new ExistingGenerator(); ExistingGenerator generator = new ExistingGenerator();
// Possible errors
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
if (type == null)
throw new IllegalArgumentException("Type cannot be NULL.");
if (!type.isAssignableFrom(object.getClass()))
throw new IllegalArgumentException("Type must be a superclass or be the same type.");
// Read instances from every field. // Read instances from every field.
for (Field field : FuzzyReflection.fromObject(object, true).getFields()) { for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
try { try {
Object value = FieldUtils.readField(field, object, true); Object value = FieldUtils.readField(field, object, true);
// Use the type of the field, not the object itself // Use the type of the field, not the object itself
if (value != null) if (value != null)
generator.addObject(field.getType(), value); generator.addObject(field.getType(), value);