Compare commits
No files matched your search
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
|
||||
<classpathentry kind="lib" path="D:/Games/Minecraft/Server Mods/API/bukkit-1.3.1-R2.0.jar"/>
|
||||
<classpathentry kind="lib" path="/ProtocolLib/bin/ProtocolLib.jar"/>
|
||||
<classpathentry kind="output" path="class"/>
|
||||
</classpath>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ItemDisguise</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -0,0 +1,11 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.itemdisguise;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
public class HideEnchantmentsListener {
|
||||
|
||||
private final Server server;
|
||||
private final Logger logger;
|
||||
|
||||
public HideEnchantmentsListener(Server server, Logger logger) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void addListener(ProtocolManager protocolManager, JavaPlugin plugin) {
|
||||
|
||||
// Hide all enchantments
|
||||
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, 0x67, 0x68, 0x3E) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
|
||||
try {
|
||||
// Item packets
|
||||
switch (event.getPacketID()) {
|
||||
case 0x67: // Set slot
|
||||
removeEnchantments(packet.getItemModifier().read(0));
|
||||
break;
|
||||
|
||||
case 0x68: // Set Window Items
|
||||
ItemStack[] elements = packet.getItemArrayModifier().read(0);
|
||||
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
if (elements[i] != null) {
|
||||
removeEnchantments(elements[i]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x3E: // Sound effect
|
||||
event.setCancelled(true);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
logger.log(Level.SEVERE, "Couldn't access field.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, 0x3) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (event.getPacketID() == 0x3) {
|
||||
try {
|
||||
String message = event.getPacket().
|
||||
getSpecificModifier(String.class).read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("fuck")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
logger.log(Level.SEVERE, "Couldn't access field.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Server getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void removeListener(ProtocolManager protocolManager, JavaPlugin plugin) {
|
||||
// Just remove every adapter with this plugin
|
||||
protocolManager.removePacketListeners(plugin);
|
||||
}
|
||||
|
||||
private void removeEnchantments(ItemStack stack) {
|
||||
Object[] copy = stack.getEnchantments().keySet().toArray();
|
||||
|
||||
for (Object enchantment : copy) {
|
||||
stack.removeEnchantment((Enchantment) enchantment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* ItemDisguise - A simple Bukkit plugin that illustrates how to use ProtocolLib.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.itemdisguise;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
|
||||
public class ItemDisguiseMod extends JavaPlugin {
|
||||
|
||||
private ProtocolManager protocolManager;
|
||||
private Logger logger;
|
||||
|
||||
private HideEnchantmentsListener enchantmentsListener;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
|
||||
logger = getLoggerSafely();
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
enchantmentsListener = new HideEnchantmentsListener(getServer(), logger);
|
||||
enchantmentsListener.addListener(protocolManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
enchantmentsListener.removeListener(protocolManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof Player) {
|
||||
|
||||
Player player = (Player) sender;
|
||||
|
||||
if (label.equalsIgnoreCase("explosion")) {
|
||||
PacketContainer fakeExplosion = protocolManager.createPacket(0x3C);
|
||||
|
||||
// Set the coordinates
|
||||
try {
|
||||
fakeExplosion.getSpecificModifier(double.class).
|
||||
write(0, player.getLocation().getX()).
|
||||
write(1, player.getLocation().getY()).
|
||||
write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getSpecificModifier(float.class).
|
||||
write(0, 3.0F);
|
||||
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the Bukkit logger first, before we try to create our own
|
||||
private Logger getLoggerSafely() {
|
||||
|
||||
Logger log = null;
|
||||
|
||||
try {
|
||||
log = getLogger();
|
||||
} catch (Throwable e) {
|
||||
// We'll handle it
|
||||
}
|
||||
|
||||
if (log == null)
|
||||
log = Logger.getLogger("Minecraft");
|
||||
return log;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
name: ItemDisguiseMod
|
||||
version: 1.0.0
|
||||
description: Change the appearance of inventory items.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ItemDisguise
|
||||
|
||||
main: com.comphenix.itemdisguise.ItemDisguiseMod
|
||||
depends: [ProtocolLib]
|
||||
database: false
|
||||
|
||||
commands:
|
||||
explosion:
|
||||
description: Creates a fake explosion around the caller.
|
||||
usage: /<command>
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
public class Application {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import com.comphenix.protocol.reflect.IntEnum;
|
||||
|
||||
/**
|
||||
* List of known packet IDs since 1.3.2.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class Packets {
|
||||
|
||||
/**
|
||||
* List of packets sent only by the server.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static final class Server extends IntEnum {
|
||||
/**
|
||||
* The singleton instance. Can also be retrieved from the parent class.
|
||||
*/
|
||||
private static Server INSTANCE = new Server();
|
||||
|
||||
public static final int KEEP_ALIVE = 0;
|
||||
public static final int LOGIN = 1;
|
||||
public static final int CHAT = 3;
|
||||
public static final int UPDATE_TIME = 4;
|
||||
public static final int ENTITY_EQUIPMENT = 5;
|
||||
public static final int SPAWN_POSITION = 6;
|
||||
public static final int UPDATE_HEALTH = 8;
|
||||
public static final int RESPAWN = 9;
|
||||
public static final int FLYING = 10;
|
||||
public static final int PLAYER_POSITION = 11;
|
||||
public static final int PLAYER_LOOK = 12;
|
||||
public static final int PLAYER_LOOK_MOVE = 13;
|
||||
public static final int ENTITY_LOCATION_ACTION = 17;
|
||||
public static final int ARM_ANIMATION = 18;
|
||||
public static final int NAMED_ENTITY_SPAWN = 20;
|
||||
public static final int PICKUP_SPAWN = 21;
|
||||
public static final int COLLECT = 22;
|
||||
public static final int VEHICLE_SPAWN = 23;
|
||||
public static final int MOB_SPAWN = 24;
|
||||
public static final int ENTITY_PAINTING = 25;
|
||||
public static final int ADD_EXP_ORB = 26;
|
||||
public static final int ENTITY_VELOCITY = 28;
|
||||
public static final int DESTROY_ENTITY = 29;
|
||||
public static final int ENTITY = 30;
|
||||
public static final int REL_ENTITY_MOVE = 31;
|
||||
public static final int ENTITY_LOOK = 32;
|
||||
public static final int REL_ENTITY_MOVE_LOOK = 33;
|
||||
public static final int ENTITY_TELEPORT = 34;
|
||||
public static final int ENTITY_HEAD_ROTATION = 35;
|
||||
public static final int ENTITY_STATUS = 38;
|
||||
public static final int ATTACH_ENTITY = 39;
|
||||
public static final int ENTITY_METADATA = 40;
|
||||
public static final int MOB_EFFECT = 41;
|
||||
public static final int REMOVE_MOB_EFFECT = 42;
|
||||
public static final int SET_EXPERIENCE = 43;
|
||||
public static final int MAP_CHUNK = 51;
|
||||
public static final int MULTI_BLOCK_CHANGE = 52;
|
||||
public static final int BLOCK_CHANGE = 53;
|
||||
public static final int PLAY_NOTE_BLOCK = 54;
|
||||
public static final int BLOCK_BREAK_ANIMATION = 55;
|
||||
public static final int MAP_CHUNK_BULK = 56;
|
||||
public static final int EXPLOSION = 60;
|
||||
public static final int WORLD_EVENT = 61;
|
||||
public static final int NAMED_SOUND_EFFECT = 62;
|
||||
public static final int BED = 70;
|
||||
public static final int WEATHER = 71;
|
||||
public static final int OPEN_WINDOW = 100;
|
||||
public static final int CLOSE_WINDOW = 101;
|
||||
public static final int SET_SLOT = 103;
|
||||
public static final int WINDOW_ITEMS = 104;
|
||||
public static final int CRAFT_PROGRESS_BAR = 105;
|
||||
public static final int TRANSACTION = 106;
|
||||
public static final int SET_CREATIVE_SLOT = 107;
|
||||
public static final int UPDATE_SIGN = 130;
|
||||
public static final int ITEM_DATA = 131;
|
||||
public static final int TILE_ENTITY_DATA = 132;
|
||||
public static final int STATISTIC = 200;
|
||||
public static final int PLAYER_INFO = 201;
|
||||
public static final int ABILITIES = 202;
|
||||
public static final int TAB_COMPLETE = 203;
|
||||
public static final int CUSTOM_PAYLOAD = 250;
|
||||
public static final int KEY_RESPONSE = 252;
|
||||
public static final int KEY_REQUEST = 253;
|
||||
public static final int KICK_DISCONNECT = 255;
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current server registry.
|
||||
*/
|
||||
public static Server getRegistry() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// We only allow a single instance of this class
|
||||
private Server() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of packets sent by the client.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Client extends IntEnum {
|
||||
/**
|
||||
* The singleton instance. Can also be retrieved from the parent class.
|
||||
*/
|
||||
private static Client INSTANCE = new Client();
|
||||
|
||||
public static final int KEEP_ALIVE = 0;
|
||||
public static final int LOGIN = 1;
|
||||
public static final int HANDSHAKE = 2;
|
||||
public static final int CHAT = 3;
|
||||
public static final int USE_ENTITY = 7;
|
||||
public static final int RESPAWN = 9;
|
||||
public static final int FLYING = 10;
|
||||
public static final int PLAYER_POSITION = 11;
|
||||
public static final int PLAYER_LOOK = 12;
|
||||
public static final int PLAYER_LOOK_MOVE = 13;
|
||||
public static final int BLOCK_DIG = 14;
|
||||
public static final int PLACE = 15;
|
||||
public static final int BLOCK_ITEM_SWITCH = 16;
|
||||
public static final int ARM_ANIMATION = 18;
|
||||
public static final int ENTITY_ACTION = 19;
|
||||
public static final int CLOSE_WINDOW = 101;
|
||||
public static final int WINDOW_CLICK = 102;
|
||||
public static final int TRANSACTION = 106;
|
||||
public static final int SET_CREATIVE_SLOT = 107;
|
||||
public static final int BUTTON_CLICK = 108;
|
||||
public static final int UPDATE_SIGN = 130;
|
||||
public static final int ABILITIES = 202;
|
||||
public static final int TAB_COMPLETE = 203;
|
||||
public static final int LOCALE_AND_VIEW_DISTANCE = 204;
|
||||
public static final int CLIENT_COMMAND = 205;
|
||||
public static final int CUSTOM_PAYLOAD = 250;
|
||||
public static final int KEY_RESPONSE = 252;
|
||||
public static final int GET_INFO = 254;
|
||||
public static final int KICK_DISCONNECT = 255;
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current client registry.
|
||||
*/
|
||||
public static Client getRegistry() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// Like above
|
||||
private Client() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current client registry.
|
||||
*/
|
||||
public static Server getServerRegistry() {
|
||||
return Server.getRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry that parses between names and packet IDs.
|
||||
* @return The current server registry.
|
||||
*/
|
||||
public static Client getClientRegistry() {
|
||||
return Client.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet by name. Must be capitalized and use underscores.
|
||||
* @param name - name of packet to find.
|
||||
* @return The packet ID found.
|
||||
*/
|
||||
public static int valueOf(String name) {
|
||||
Integer serverAttempt = Server.INSTANCE.valueOf(name);
|
||||
|
||||
if (serverAttempt != null)
|
||||
return serverAttempt;
|
||||
else
|
||||
return Client.INSTANCE.valueOf(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of a packet.
|
||||
* @param packetID - packet to retrieve name.
|
||||
* @return The name, or NULL if unable to find such a packet.
|
||||
*/
|
||||
public static String getDeclaredName(int packetID) {
|
||||
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
|
||||
|
||||
if (serverAttempt != null)
|
||||
return serverAttempt;
|
||||
else
|
||||
return Client.INSTANCE.getDeclaredName(packetID);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Server;
|
||||
@@ -7,6 +26,7 @@ import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.metrics.Statistics;
|
||||
|
||||
public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
@@ -15,7 +35,10 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// Error logger
|
||||
private Logger logger;
|
||||
|
||||
|
||||
// Metrics and statistisc
|
||||
private Statistics statistisc;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
logger = getLoggerSafely();
|
||||
@@ -27,24 +50,62 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
Server server = getServer();
|
||||
PluginManager manager = server.getPluginManager();
|
||||
|
||||
// Notify server managers of incompatible plugins
|
||||
checkForIncompatibility(manager);
|
||||
|
||||
// Player login and logout events
|
||||
protocolManager.registerEvents(manager, this);
|
||||
protocolManager.initializePlayers(server.getOnlinePlayers());
|
||||
}
|
||||
|
||||
// Inject our hook into already existing players
|
||||
protocolManager.initializePlayers(server.getOnlinePlayers());
|
||||
|
||||
// Try to enable statistics
|
||||
try {
|
||||
statistisc = new Statistics(this);
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Unable to enable metrics.", e);
|
||||
} catch (Throwable e) {
|
||||
logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
// Plugin authors: Notify me to remove these
|
||||
String[] incompatiblePlugins = { "TagAPI" };
|
||||
|
||||
for (String plugin : incompatiblePlugins) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
// Check for versions, ect.
|
||||
logger.severe("Detected incompatible plugin: " + plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
protocolManager.close();
|
||||
protocolManager = null;
|
||||
statistisc = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager.
|
||||
* @return Packet protocol manager, or NULL if it has been disabled.
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return protocolManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the metrics instance used to measure users of this library.
|
||||
* <p>
|
||||
* Note that this method may return NULL when the server is reloading or shutting down.
|
||||
* @return Metrics instance container.
|
||||
*/
|
||||
public Statistics getStatistics() {
|
||||
return statistisc;
|
||||
}
|
||||
|
||||
// Get the Bukkit logger first, before we try to create our own
|
||||
private Logger getLoggerSafely() {
|
||||
|
||||
|
||||
@@ -1,34 +1,72 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Represents an API for accessing the Minecraft protocol.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ProtocolManager {
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
public abstract ImmutableSet<PacketListener> getPacketListeners();
|
||||
public ImmutableSet<PacketListener> getPacketListeners();
|
||||
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* Adds a packet listener.
|
||||
* <p>
|
||||
* Adding an already registered listener has no effect. If you need to change the packets
|
||||
* the current listener is observing, you must first remove the packet listener before you
|
||||
* can register it again.
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
public abstract void addPacketListener(PacketListener listener);
|
||||
public void addPacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* Removes a given packet listener.
|
||||
* <p>
|
||||
* Attempting to remove a listener that doesn't exist has no effect.
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
public abstract void removePacketListener(PacketListener listener);
|
||||
public void removePacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes every listener associated with the given plugin.
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
public void removePacketListeners(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* @param reciever - the reciever.
|
||||
@@ -75,15 +113,50 @@ public interface ProtocolManager {
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public PacketContainer createPacket(int id);
|
||||
|
||||
|
||||
/**
|
||||
* Retieves a set of every enabled packet.
|
||||
* @return Every packet filter.
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
|
||||
* give non-primitive fields in the packet sensible default values. For instance, certain
|
||||
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
|
||||
* forceDefaults option is true, the List or Set will be automatically created.
|
||||
*
|
||||
* @param id - packet ID.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
public Set<Integer> getPacketFilters();
|
||||
public PacketContainer createPacket(int id, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Determines whether or not is protocol mananger has been disabled.
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* @param id - the packet ID.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
public PacketConstructor createPacketConstructor(int id, Object... arguments);
|
||||
|
||||
/**
|
||||
* Completely refresh all clients about an entity.
|
||||
* @param entity - entity to refresh.
|
||||
* @param observers - the clients to update.
|
||||
*/
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
public Set<Integer> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the recieved client packets that will be observed by listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
public Set<Integer> getReceivingFilters();
|
||||
|
||||
/**
|
||||
* Determines whether or not this protocol mananger has been disabled.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed();
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
|
||||
* @param <T> - type of the elements in the list.
|
||||
*/
|
||||
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Iterable<T>, Collection<T> {
|
||||
// Prevent reordering
|
||||
private volatile List<T> list;
|
||||
|
||||
/**
|
||||
* Construct an empty sorted array.
|
||||
*/
|
||||
public SortedCopyOnWriteArray() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list. The elements will be automatically sorted.
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped) {
|
||||
this.list = new ArrayList<T>(wrapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list.
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
|
||||
this.list = new ArrayList<T>(wrapped);
|
||||
|
||||
if (sort) {
|
||||
Collections.sort(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given element in the proper location.
|
||||
* @param value - element to insert.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean add(T value) {
|
||||
|
||||
// We use NULL as a special marker, so we don't allow it
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("value cannot be NULL");
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
for (T element : list) {
|
||||
// If the value is now greater than the current element, it should be placed right before it
|
||||
if (value != null && value.compareTo(element) < 0) {
|
||||
copy.add(value);
|
||||
value = null;
|
||||
}
|
||||
copy.add(element);
|
||||
}
|
||||
|
||||
// Don't forget to add it
|
||||
if (value != null)
|
||||
copy.add(value);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> values) {
|
||||
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
// Insert the new content and sort it
|
||||
copy.addAll(list);
|
||||
copy.addAll(values);
|
||||
Collections.sort(copy);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a new list with every element except the one given.
|
||||
* <p>
|
||||
* Objects will be compared using the given objects equals() method.
|
||||
* @param value - value to remove.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean remove(Object value) {
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
// Note that there's not much to be gained from using BinarySearch, as we
|
||||
// have to copy (and thus read) the entire list regardless.
|
||||
|
||||
// Copy every element except the one given to us.
|
||||
for (T element : list) {
|
||||
if (value != null && !Objects.equal(value, element)) {
|
||||
copy.add(element);
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
list = copy;
|
||||
return value == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
copy.addAll(list);
|
||||
copy.removeAll(values);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null)
|
||||
throw new IllegalArgumentException("values cannot be NULL");
|
||||
if (values.size() == 0)
|
||||
return false;
|
||||
|
||||
List<T> copy = new ArrayList<T>();
|
||||
|
||||
copy.addAll(list);
|
||||
copy.removeAll(values);
|
||||
|
||||
list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a copy of every element except the one with the given index.
|
||||
* @param index - index of the element to remove.
|
||||
*/
|
||||
public synchronized void remove(int index) {
|
||||
|
||||
List<T> copy = new ArrayList<T>(list);
|
||||
|
||||
copy.remove(index);
|
||||
list = copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an element by index.
|
||||
* @param index - index of element to retrieve.
|
||||
* @return The element at the given location.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the list.
|
||||
* @return Size of the list.
|
||||
*/
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an iterator over the elements in the given list.
|
||||
* Warning: No not attempt to remove elements using the iterator.
|
||||
*/
|
||||
public Iterator<T> iterator() {
|
||||
return Iterables.unmodifiableIterable(list).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
list = new ArrayList<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object value) {
|
||||
return list.contains(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
return list.containsAll(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
/**
|
||||
* Represents a packet event priority, similar to the Bukkit EventPriority.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ListenerPriority {
|
||||
/**
|
||||
* Event call is of very low importance and should be ran first, to allow
|
||||
* other plugins to further customise the outcome.
|
||||
*/
|
||||
LOWEST(0),
|
||||
/**
|
||||
* Event call is of low importance.
|
||||
*/
|
||||
LOW(1),
|
||||
/**
|
||||
* Event call is neither important or unimportant, and may be ran normally.
|
||||
*/
|
||||
NORMAL(2),
|
||||
/**
|
||||
* Event call is of high importance.
|
||||
*/
|
||||
HIGH(3),
|
||||
/**
|
||||
* Event call is critical and must have the final say in what happens to the
|
||||
* event.
|
||||
*/
|
||||
HIGHEST(4),
|
||||
/**
|
||||
* Event is listened to purely for monitoring the outcome of an event.
|
||||
* <p/>
|
||||
* No modifications to the event should be made under this priority.
|
||||
*/
|
||||
MONITOR(5);
|
||||
|
||||
private final int slot;
|
||||
|
||||
private ListenerPriority(int slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* A low slot represents a low priority.
|
||||
* @return Integer representation of this priorty.
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Determines which packets will be observed by a listener, and with what priority.
|
||||
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ListeningWhitelist {
|
||||
|
||||
/**
|
||||
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
|
||||
*/
|
||||
public static ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
|
||||
|
||||
private ListenerPriority priority;
|
||||
private Set<Integer> whitelist;
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist for a given priority with a set of packet IDs.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - set of IDs to observe/enable.
|
||||
*/
|
||||
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
|
||||
this.priority = priority;
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet whitelist of a given priority for a list of packets.
|
||||
* @param priority - the listener priority.
|
||||
* @param whitelist - list of packet IDs to observe/enable.
|
||||
*/
|
||||
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
|
||||
this.priority = priority;
|
||||
this.whitelist = Sets.newHashSet(whitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return whitelist != null || whitelist.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
|
||||
* @return Execution order in terms of priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of packets that will be observed by the listeners.
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
public Set<Integer> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(){
|
||||
return Objects.hashCode(priority, whitelist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj){
|
||||
if(obj instanceof ListeningWhitelist){
|
||||
final ListeningWhitelist other = (ListeningWhitelist) obj;
|
||||
return Objects.equal(priority, other.priority)
|
||||
&& Objects.equal(whitelist, other.whitelist);
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY_WHITELIST)
|
||||
return "EMPTY_WHITELIST";
|
||||
else
|
||||
return Objects.toStringHelper(this)
|
||||
.add("priority", priority)
|
||||
.add("packets", whitelist).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,23 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a packet listener with useful constructors.
|
||||
@@ -14,20 +26,46 @@ import com.google.common.collect.Sets;
|
||||
*/
|
||||
public abstract class PacketAdapter implements PacketListener {
|
||||
|
||||
protected JavaPlugin plugin;
|
||||
protected Set<Integer> packetsID;
|
||||
protected Plugin plugin;
|
||||
protected ConnectionSide connectionSide;
|
||||
|
||||
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
/**
|
||||
* Initialize a packet listener.
|
||||
* Initialize a packet listener with default priority.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(JavaPlugin plugin, ConnectionSide connectionSide, Integer... packets) {
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("plugin cannot be null");
|
||||
if (connectionSide == null)
|
||||
throw new IllegalArgumentException("connectionSide cannot be null");
|
||||
if (listenerPriority == null)
|
||||
throw new IllegalArgumentException("listenerPriority cannot be null");
|
||||
if (packets == null)
|
||||
throw new IllegalArgumentException("packets cannot be null");
|
||||
|
||||
// Add whitelists
|
||||
if (connectionSide.isForServer())
|
||||
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets);
|
||||
if (connectionSide.isForClient())
|
||||
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
this.packetsID = Sets.newHashSet(packets);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,20 +79,46 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionSide getConnectionSide() {
|
||||
return connectionSide;
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getPacketsID() {
|
||||
return packetsID;
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the plugin that has been associated with the listener.
|
||||
* @return Name of the associated plugin.
|
||||
*/
|
||||
public static String getPluginName(PacketListener listener) {
|
||||
|
||||
Plugin plugin = listener.getPlugin();
|
||||
|
||||
// Try to get the plugin name
|
||||
try {
|
||||
if (plugin == null)
|
||||
return "UNKNOWN";
|
||||
else
|
||||
return plugin.getName();
|
||||
|
||||
} catch (NoSuchMethodError e) {
|
||||
return plugin.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used by the error reporter
|
||||
return String.format("PacketAdapter[plugin=%s, side=%s, packets=%s]",
|
||||
plugin.getName(), getConnectionSide().name(),
|
||||
StringUtils.join(packetsID, ", "));
|
||||
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
|
||||
getPluginName(this),
|
||||
sendingWhitelist,
|
||||
receivingWhitelist);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,36 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import org.apache.commons.lang.NullArgumentException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
@@ -22,6 +47,20 @@ public class PacketContainer {
|
||||
|
||||
// Current structure modifier
|
||||
protected StructureModifier<Object> structureModifier;
|
||||
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
|
||||
// The getEntity method
|
||||
private static Method getEntity;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
hasWorldType = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
@@ -48,7 +87,7 @@ public class PacketContainer {
|
||||
*/
|
||||
public PacketContainer(int id, Packet handle, StructureModifier<Object> structure) {
|
||||
if (handle == null)
|
||||
throw new NullArgumentException("handle");
|
||||
throw new IllegalArgumentException("handle cannot be null.");
|
||||
|
||||
this.id = id;
|
||||
this.handle = handle;
|
||||
@@ -71,10 +110,22 @@ public class PacketContainer {
|
||||
return structureModifier;
|
||||
}
|
||||
|
||||
public <T> StructureModifier<T> getPrimitiveModifier(Class<T> primitiveType) {
|
||||
/**
|
||||
* Retrieves a read/write structure for every field with the given type.
|
||||
* @param primitiveType - the type to find.
|
||||
* @return A modifier for this specific type.
|
||||
*/
|
||||
public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
|
||||
return structureModifier.withType(primitiveType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for ItemStack.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the Bukkit ItemStack and the
|
||||
* internal Minecraft ItemStack.
|
||||
* @return A modifier for ItemStack fields.
|
||||
*/
|
||||
public StructureModifier<ItemStack> getItemModifier() {
|
||||
// Convert from and to the Bukkit wrapper
|
||||
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class, new EquivalentConverter<ItemStack>() {
|
||||
@@ -86,9 +137,21 @@ public class PacketContainer {
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for arrays of ItemStacks.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the Bukkit ItemStack and the
|
||||
* internal Minecraft ItemStack.
|
||||
* @return A modifier for ItemStack array fields.
|
||||
*/
|
||||
public StructureModifier<ItemStack[]> getItemArrayModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack[]>withType(net.minecraft.server.ItemStack[].class, new EquivalentConverter<ItemStack[]>() {
|
||||
@@ -113,6 +176,111 @@ public class PacketContainer {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack[]> getSpecificType() {
|
||||
return ItemStack[].class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for the world type enum.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the Bukkit world type and the
|
||||
* internal Minecraft world type.
|
||||
* @return A modifier for world type fields.
|
||||
*/
|
||||
public StructureModifier<WorldType> getWorldTypeModifier() {
|
||||
|
||||
if (!hasWorldType) {
|
||||
// We couldn't find the Minecraft equivalent
|
||||
return structureModifier.withType(null);
|
||||
}
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WorldType>withType(net.minecraft.server.WorldType.class, new EquivalentConverter<WorldType>() {
|
||||
@Override
|
||||
public Object getGeneric(WorldType specific) {
|
||||
return net.minecraft.server.WorldType.getType(specific.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
|
||||
return WorldType.getByName(type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for entity objects.
|
||||
* <p>
|
||||
* Note that entities are transmitted by integer ID, and the type may not be enough
|
||||
* to distinguish between entities and other values. Thus, this structure modifier
|
||||
* MAY return null or invalid entities for certain fields. Using the correct index
|
||||
* is essential.
|
||||
*
|
||||
* @return A modifier entity types.
|
||||
*/
|
||||
public StructureModifier<Entity> getEntityModifier(World world) {
|
||||
|
||||
final Object worldServer = ((CraftWorld) world).getHandle();
|
||||
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
|
||||
final World worldCopy = world;
|
||||
|
||||
if (getEntity == null)
|
||||
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
|
||||
"getEntity", nmsEntityClass, new Class[] { int.class });
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<Entity>withType(int.class, new EquivalentConverter<Entity>() {
|
||||
@Override
|
||||
public Object getGeneric(Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecific(Object generic) {
|
||||
try {
|
||||
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
|
||||
getEntity.invoke(worldServer, generic);
|
||||
Integer id = (Integer) generic;
|
||||
|
||||
// Attempt to get the Bukkit entity
|
||||
if (nmsEntity != null) {
|
||||
return nmsEntity.getBukkitEntity();
|
||||
} else {
|
||||
// Maybe it's a player that's just logged in? Try a search
|
||||
for (Player player : worldCopy.getPlayers()) {
|
||||
if (player.getEntityId() == id) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Entity doesn't exist.");
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Incorrect arguments detected.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read field due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
public class PacketEvent extends EventObject {
|
||||
public class PacketEvent extends EventObject implements Cancellable {
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
|
||||
private PacketContainer packet;
|
||||
private Player sender;
|
||||
private Player reciever;
|
||||
private Player player;
|
||||
private boolean serverPacket;
|
||||
private boolean cancel;
|
||||
|
||||
/**
|
||||
@@ -23,11 +41,11 @@ public class PacketEvent extends EventObject {
|
||||
super(source);
|
||||
}
|
||||
|
||||
private PacketEvent(Object source, PacketContainer packet, Player sender, Player reciever) {
|
||||
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
||||
super(source);
|
||||
this.packet = packet;
|
||||
this.sender = sender;
|
||||
this.reciever = reciever;
|
||||
this.player = player;
|
||||
this.serverPacket = serverPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,7 +56,7 @@ public class PacketEvent extends EventObject {
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
|
||||
return new PacketEvent(source, packet, client, null);
|
||||
return new PacketEvent(source, packet, client, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +67,7 @@ public class PacketEvent extends EventObject {
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
|
||||
return new PacketEvent(source, packet, null, recipient);
|
||||
return new PacketEvent(source, packet, recipient, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,27 +111,18 @@ public class PacketEvent extends EventObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the player that has sent the packet.
|
||||
* @return The sender, or NULL if the server is sending the packet.
|
||||
* Retrieves the player that has sent the packet or is recieving it.
|
||||
* @return The player associated with this event.
|
||||
*/
|
||||
public Player getSender() {
|
||||
return sender;
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the player that will recieve the packet.
|
||||
* @return The reciever, or NULL if the server is recieving the packet.
|
||||
*/
|
||||
public Player getReciever() {
|
||||
return reciever;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return getReciever() != null;
|
||||
return serverPacket;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,30 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Represents a listener that recieves notifications when packets are sent or recieved.
|
||||
* <p>
|
||||
* Use {@link PacketAdapter} for a simple wrapper around this interface.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketListener {
|
||||
|
||||
/**
|
||||
@@ -21,14 +43,20 @@ public interface PacketListener {
|
||||
public void onPacketReceiving(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve whether or not we're listening for client or server packets.
|
||||
* @return The type of packets we expect.
|
||||
* Retrieve which packets sent by the server this listener will observe.
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
public ConnectionSide getConnectionSide();
|
||||
public ListeningWhitelist getSendingWhitelist();
|
||||
|
||||
/**
|
||||
* Set of packet ids we expect to recieve.
|
||||
* @return Packets IDs.
|
||||
* Retrieve which packets sent by the client this listener will observe.
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
public Set<Integer> getPacketsID();
|
||||
public ListeningWhitelist getReceivingWhitelist();
|
||||
|
||||
/**
|
||||
* Retrieve the plugin that created list packet listener.
|
||||
* @return The plugin, or NULL if not available.
|
||||
*/
|
||||
public Plugin getPlugin();
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of a listener multimap.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ConcurrentListenerMultimap {
|
||||
|
||||
// The core of our map
|
||||
protected ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener>> listeners =
|
||||
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener>>();
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet recievers.
|
||||
* @param listener - listener with a list of packets to recieve notifcations for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(PacketListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority());
|
||||
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
addListener(packetID, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(Integer packetID, PrioritizedListener listener) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(packetID);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||
// which is a essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener> value = new SortedCopyOnWriteArray<PrioritizedListener>();
|
||||
|
||||
list = listeners.putIfAbsent(packetID, value);
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree
|
||||
// on the one to use.
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<Integer> removeListener(PacketListener listener, ListeningWhitelist whitelist) {
|
||||
|
||||
List<Integer> removedPackets = new ArrayList<Integer>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (Integer packetID : whitelist.getWhitelist()) {
|
||||
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(packetID);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
// Don't remove from newly created lists
|
||||
if (list.size() > 0) {
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.size() == 0) {
|
||||
listeners.remove(packetID);
|
||||
removedPackets.add(packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move on to the next
|
||||
}
|
||||
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(Logger logger, PacketEvent event) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// We have to be careful. Cannot modify the underlying list when sending notifications.
|
||||
synchronized (list) {
|
||||
for (PrioritizedListener element : list) {
|
||||
try {
|
||||
element.getListener().onPacketReceiving(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE,
|
||||
"Exception occured in onPacketReceiving() for " +
|
||||
PacketAdapter.getPluginName(element.getListener()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(Logger logger, PacketEvent event) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(event.getPacketID());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
synchronized (list) {
|
||||
for (PrioritizedListener element : list) {
|
||||
try {
|
||||
element.getListener().onPacketSending(event);
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE,
|
||||
"Exception occured in onPacketReceiving() for " +
|
||||
PacketAdapter.getPluginName(element.getListener()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener with an associated priority.
|
||||
*/
|
||||
private class PrioritizedListener implements Comparable<PrioritizedListener> {
|
||||
private PacketListener listener;
|
||||
private ListenerPriority priority;
|
||||
|
||||
public PrioritizedListener(PacketListener listener, ListenerPriority priority) {
|
||||
this.listener = listener;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PrioritizedListener other) {
|
||||
// This ensures that lower priority listeners are executed first
|
||||
return Ints.compare(this.getPriority().getSlot(),
|
||||
other.getPriority().getSlot());
|
||||
}
|
||||
|
||||
// Note that this equals() method is NOT consistent with compareTo().
|
||||
// But, it's a private class so who cares.
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// We only care about the listener - priority itself should not make a difference
|
||||
if(obj instanceof PrioritizedListener){
|
||||
final PrioritizedListener other = (PrioritizedListener) obj;
|
||||
return Objects.equal(listener, other.listener);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public PacketListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.injector.PacketConstructor.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* Used to perform certain operations on entities.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class EntityUtilities {
|
||||
|
||||
private static Field entityTrackerField;
|
||||
private static Field trackedEntitiesField;
|
||||
private static Field trackedPlayersField;
|
||||
|
||||
private static Method hashGetMethod;
|
||||
private static Method scanPlayersMethod;
|
||||
|
||||
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
|
||||
World world = entity.getWorld();
|
||||
Object worldServer = ((CraftWorld) world).getHandle();
|
||||
|
||||
// We have to rely on the class naming here.
|
||||
if (entityTrackerField == null)
|
||||
entityTrackerField = FuzzyReflection.fromObject(worldServer).getFieldByType(".*Tracker");
|
||||
|
||||
// Get the tracker
|
||||
Object tracker = null;
|
||||
|
||||
try {
|
||||
tracker = FieldUtils.readField(entityTrackerField, worldServer, false);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access 'tracker' field due to security limitations.", e);
|
||||
}
|
||||
|
||||
if (trackedEntitiesField == null) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
Set<Class> ignoredTypes = new HashSet<Class>();
|
||||
|
||||
// Well, this is more difficult. But we're looking for a Minecraft object that is not
|
||||
// created by the constructor(s).
|
||||
for (Constructor<?> constructor : tracker.getClass().getConstructors()) {
|
||||
for (Class<?> type : constructor.getParameterTypes()) {
|
||||
ignoredTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
// The Minecraft field that's NOT filled in by the constructor
|
||||
trackedEntitiesField = FuzzyReflection.fromObject(tracker, true).
|
||||
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT, ignoredTypes);
|
||||
}
|
||||
|
||||
// Read the entity hashmap
|
||||
Object trackedEntities = null;
|
||||
|
||||
try {
|
||||
trackedEntities = FieldUtils.readField(trackedEntitiesField, tracker, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access 'trackedEntities' field due to security limitations.", e);
|
||||
}
|
||||
|
||||
// Getting the "get" method is pretty hard, but first - try to just get it by name
|
||||
if (hashGetMethod == null) {
|
||||
|
||||
Class<?> type = trackedEntities.getClass();
|
||||
|
||||
try {
|
||||
hashGetMethod = type.getMethod("get", int.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
|
||||
Class<?>[] params = { int.class };
|
||||
|
||||
// Then it's probably the lowest named method that takes an int-parameter
|
||||
for (Method method : type.getMethods()) {
|
||||
if (Arrays.equals(params, method.getParameterTypes())) {
|
||||
if (hashGetMethod == null ||
|
||||
method.getName().compareTo(hashGetMethod.getName()) < 0) {
|
||||
hashGetMethod = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
|
||||
|
||||
if (trackedPlayersField == null) {
|
||||
// This one is fairly easy
|
||||
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||
}
|
||||
|
||||
// Phew, finally there.
|
||||
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
|
||||
List<Object> nmsPlayers = unwrapBukkit(observers);
|
||||
|
||||
// trackEntity.trackedPlayers.clear();
|
||||
trackedPlayers.removeAll(nmsPlayers);
|
||||
|
||||
// We have to rely on a NAME once again. Damn it.
|
||||
if (scanPlayersMethod == null) {
|
||||
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
|
||||
}
|
||||
|
||||
//trackEntity.scanPlayers(server.players);
|
||||
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Object> unwrapBukkit(List<Player> players) {
|
||||
|
||||
List<Object> output = Lists.newArrayList();
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
|
||||
// Get the NMS equivalent
|
||||
for (Player player : players) {
|
||||
Object result = unwrapper.unwrapItem(player);
|
||||
|
||||
if (result != null)
|
||||
output.add(result);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot unwrap item " + player);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import com.comphenix.protocol.injector.NetworkFieldInjector.FakePacket;
|
||||
|
||||
/**
|
||||
* The array list that notifies when packets are sent by the server.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class InjectedArrayList extends ArrayList<Packet> {
|
||||
|
||||
/**
|
||||
* Silly Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -1173865905404280990L;
|
||||
|
||||
private PlayerInjector injector;
|
||||
private Set<Packet> ignoredPackets;
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public InjectedArrayList(ClassLoader classLoader, PlayerInjector injector, Set<Packet> ignoredPackets) {
|
||||
this.classLoader = classLoader;
|
||||
this.injector = injector;
|
||||
this.ignoredPackets = ignoredPackets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Packet packet) {
|
||||
|
||||
Packet result = null;
|
||||
|
||||
// Check for fake packets and ignored packets
|
||||
if (packet instanceof FakePacket) {
|
||||
return true;
|
||||
} else if (ignoredPackets.contains(packet)) {
|
||||
ignoredPackets.remove(packet);
|
||||
} else {
|
||||
result = injector.handlePacketRecieved(packet);
|
||||
}
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
try {
|
||||
if (result != null) {
|
||||
super.add(result);
|
||||
} else {
|
||||
// We'll use the FakePacket marker instead of preventing the filters
|
||||
injector.sendServerPacket(createNegativePacket(packet), true);
|
||||
}
|
||||
|
||||
// Collection.add contract
|
||||
return true;
|
||||
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Reverting cancelled packet failed.", e.getTargetException());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
|
||||
* whereby every int method's return value is inverted (a => -a).
|
||||
*
|
||||
* @param source - packet to invert.
|
||||
* @return The inverted packet.
|
||||
*/
|
||||
Packet createNegativePacket(Packet source) {
|
||||
Enhancer ex = new Enhancer();
|
||||
Class<?> type = source.getClass();
|
||||
|
||||
// We want to subtract the byte amount that were added to the running
|
||||
// total of outstanding packets. Otherwise, cancelling too many packets
|
||||
// might cause a "disconnect.overflow" error.
|
||||
//
|
||||
// We do that by constructing a special packet of the same type that returns
|
||||
// a negative integer for all zero-parameter integer methods. This includes the
|
||||
// size() method, which is used by the queue method to count the number of
|
||||
// bytes to add.
|
||||
//
|
||||
// Essentially, we have:
|
||||
//
|
||||
// public class NegativePacket extends [a packet] {
|
||||
// @Override
|
||||
// public int size() {
|
||||
// return -super.size();
|
||||
// }
|
||||
// ect.
|
||||
// }
|
||||
ex.setInterfaces(new Class[] { FakePacket.class } );
|
||||
ex.setUseCache(true);
|
||||
ex.setClassLoader(classLoader);
|
||||
ex.setSuperclass(type);
|
||||
ex.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
if (method.getReturnType().equals(int.class) && args.length == 0) {
|
||||
Integer result = (Integer) proxy.invokeSuper(obj, args);
|
||||
return -result;
|
||||
} else {
|
||||
return proxy.invokeSuper(obj, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (Packet) ex.create();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,31 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.ObjectUtils;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Static registries in Minecraft.
|
||||
@@ -78,7 +94,7 @@ class MinecraftRegistry {
|
||||
|
||||
// Will most likely not be used
|
||||
for (Map.Entry<Class, Integer> entry : getPacketToID().entrySet()) {
|
||||
if (ObjectUtils.equals(entry.getValue(), packetID)) {
|
||||
if (Objects.equal(entry.getValue(), packetID)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
/**
|
||||
* Injection hook that overrides the packet queue lists in NetworkHandler.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkFieldInjector extends PlayerInjector {
|
||||
|
||||
/**
|
||||
* Marker interface that indicates a packet is fake and should not be processed.
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface FakePacket {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
// Packets to ignore
|
||||
private Set<Packet> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Packet, Boolean>());
|
||||
|
||||
// Overridden fields
|
||||
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
|
||||
|
||||
// Sync field
|
||||
private static Field syncField;
|
||||
private Object syncObject;
|
||||
|
||||
public NetworkFieldInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
|
||||
super(player, manager, sendingFilters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() throws IllegalAccessException {
|
||||
super.initialize();
|
||||
|
||||
// Get the sync field as well
|
||||
if (hasInitialized) {
|
||||
if (syncField == null)
|
||||
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
|
||||
syncObject = FieldUtils.readField(syncField, networkManager, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
|
||||
if (networkManager != null) {
|
||||
try {
|
||||
if (!filtered) {
|
||||
ignoredPackets.add(packet);
|
||||
}
|
||||
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
queueMethod.invoke(networkManager, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access queue method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
||||
if (networkManager != null) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
StructureModifier<List> list = networkModifier.withType(List.class);
|
||||
|
||||
// Subclass both send queues
|
||||
for (Field field : list.getFields()) {
|
||||
VolatileField overwriter = new VolatileField(field, networkManager, true);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Packet> minecraftList = (List<Packet>) overwriter.getOldValue();
|
||||
|
||||
synchronized(syncObject) {
|
||||
// The list we'll be inserting
|
||||
List<Packet> hackedList = new InjectedArrayList(manager.getClassLoader(), this, ignoredPackets);
|
||||
|
||||
// Add every previously stored packet
|
||||
for (Packet packet : minecraftList) {
|
||||
hackedList.add(packet);
|
||||
}
|
||||
|
||||
// Don' keep stale packets around
|
||||
minecraftList.clear();
|
||||
overwriter.setValue(Collections.synchronizedList(hackedList));
|
||||
}
|
||||
|
||||
overridenLists.add(overwriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void cleanupAll() {
|
||||
// Clean up
|
||||
for (VolatileField overriden : overridenLists) {
|
||||
List<Packet> minecraftList = (List<Packet>) overriden.getOldValue();
|
||||
List<Packet> hacketList = (List<Packet>) overriden.getValue();
|
||||
|
||||
if (minecraftList == hacketList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a lock before we modify the list
|
||||
synchronized(syncObject) {
|
||||
try {
|
||||
// Copy over current packets
|
||||
for (Packet packet : (List<Packet>) overriden.getValue()) {
|
||||
minecraftList.add(packet);
|
||||
}
|
||||
} finally {
|
||||
overriden.revertValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
overridenLists.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkObjectInjector extends PlayerInjector {
|
||||
public NetworkObjectInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
|
||||
super(player, manager, sendingFilters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
|
||||
|
||||
if (networkDelegate != null) {
|
||||
try {
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
queueMethod.invoke(networkDelegate, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access queue method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectManager() {
|
||||
|
||||
if (networkManager != null) {
|
||||
final Class<?> networkInterface = networkManagerField.getType();
|
||||
final Object networkDelegate = networkManagerRef.getOldValue();
|
||||
|
||||
if (!networkInterface.isInterface()) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
|
||||
}
|
||||
|
||||
// Create our proxy object
|
||||
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
|
||||
new Class<?>[] { networkInterface }, new InvocationHandler() {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// OH OH! The queue method!
|
||||
if (method.equals(queueMethod)) {
|
||||
Packet packet = (Packet) args[0];
|
||||
|
||||
if (packet != null) {
|
||||
packet = handlePacketRecieved(packet);
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
if (packet != null)
|
||||
args[0] = packet;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate to our underlying class
|
||||
try {
|
||||
return method.invoke(networkDelegate, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inject it, if we can.
|
||||
networkManagerRef.setValue(networkProxy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
// Clean up
|
||||
networkManagerRef.revertValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* A packet constructor that uses an internal Minecraft.
|
||||
* @author Kristian
|
||||
*
|
||||
*/
|
||||
public class PacketConstructor {
|
||||
|
||||
/**
|
||||
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
|
||||
* <p>
|
||||
* Remember to call withPacket().
|
||||
*/
|
||||
public static PacketConstructor DEFAULT = new PacketConstructor(null);
|
||||
|
||||
// The constructor method that's actually responsible for creating the packet
|
||||
private Constructor<?> constructorMethod;
|
||||
|
||||
// The packet ID
|
||||
private int packetID;
|
||||
|
||||
// Used to unwrap Bukkit objects
|
||||
private List<Unwrapper> unwrappers;
|
||||
|
||||
private PacketConstructor(Constructor<?> constructorMethod) {
|
||||
this.constructorMethod = constructorMethod;
|
||||
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper());
|
||||
}
|
||||
|
||||
private PacketConstructor(Constructor<?> constructorMethod, List<Unwrapper> unwrappers) {
|
||||
this.constructorMethod = constructorMethod;
|
||||
this.unwrappers = unwrappers;
|
||||
}
|
||||
|
||||
public ImmutableList<Unwrapper> getUnwrappers() {
|
||||
return ImmutableList.copyOf(unwrappers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the id of the packets this constructor creates.
|
||||
* @return The ID of the packets this constructor will create.
|
||||
*/
|
||||
public int getPacketID() {
|
||||
return packetID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the current constructor with a different list of unwrappers.
|
||||
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
|
||||
* @return A constructor with a different set of unwrappers.
|
||||
*/
|
||||
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
|
||||
return new PacketConstructor(constructorMethod, unwrappers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packet constructor that creates packets using the given types.
|
||||
* @param id - packet ID.
|
||||
* @param types - types to create.
|
||||
* @return A packet constructor with these types.
|
||||
* @throws IllegalArgumentException If no packet constructor could be created with these types.
|
||||
*/
|
||||
public PacketConstructor withPacket(int id, Class<?>[] types) {
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
for (Unwrapper unwrapper : unwrappers) {
|
||||
Class<?> result = unwrapper.unwrapType(types[i]);
|
||||
|
||||
// Update type we're searching for
|
||||
if (result != null) {
|
||||
types[i] = result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> packetType = MinecraftRegistry.getPacketClassFromID(id);
|
||||
|
||||
if (packetType == null)
|
||||
throw new IllegalArgumentException("Could not find a packet by the id " + id);
|
||||
|
||||
// Find the correct constructor
|
||||
for (Constructor<?> constructor : packetType.getConstructors()) {
|
||||
Class<?>[] params = constructor.getParameterTypes();
|
||||
|
||||
if (isCompatible(types, params)) {
|
||||
// Right, we've found our type
|
||||
return new PacketConstructor(constructor, unwrappers);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("No suitable constructor could be found.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
|
||||
* @return The created packet.
|
||||
* @throws FieldAccessException Failure due to a security limitation.
|
||||
* @throws IllegalArgumentException Arguments doesn't match the constructor.
|
||||
* @throws RuntimeException Minecraft threw an exception.
|
||||
*/
|
||||
public PacketContainer createPacket(Object... values) throws FieldAccessException {
|
||||
|
||||
try {
|
||||
// Convert types
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
for (Unwrapper unwrapper : unwrappers) {
|
||||
Object converted = unwrapper.unwrapItem(values[i]);
|
||||
|
||||
if (converted != null) {
|
||||
values[i] = converted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Packet nmsPacket = (Packet) constructorMethod.newInstance(values);
|
||||
return new PacketContainer(packetID, nmsPacket);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InstantiationException e) {
|
||||
throw new FieldAccessException("Cannot construct an abstract packet.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot construct packet due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if a method with the types 'params' can be called with 'types'
|
||||
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
|
||||
|
||||
// Determine if the types are similar
|
||||
if (params.length == types.length) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
if (!params[i].isAssignableFrom(types[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parameter count must match
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class BukkitUnwrapper implements Unwrapper {
|
||||
|
||||
private static Map<Class<?>, Method> cache = new ConcurrentHashMap<Class<?>, Method>();
|
||||
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
|
||||
Class<?> currentClass = wrappedObject.getClass();
|
||||
Method cachedMethod = initializeCache(currentClass);
|
||||
|
||||
try {
|
||||
// Retrieve the handle
|
||||
if (cachedMethod != null)
|
||||
return cachedMethod.invoke(wrappedObject);
|
||||
else
|
||||
return null;
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Impossible
|
||||
return null;
|
||||
} catch (IllegalAccessException e) {
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is REALLY bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> unwrapType(Class<?> type) {
|
||||
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be null.");
|
||||
|
||||
Method unwrapped = initializeCache(type);
|
||||
|
||||
// Determine the unwrapped type
|
||||
if (unwrapped != null)
|
||||
return unwrapped.getReturnType();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private Method initializeCache(Class<?> type) {
|
||||
|
||||
// See if we're already determined this
|
||||
if (cache.containsKey(type)) {
|
||||
// We will never remove from the cache, so this ought to be thread safe
|
||||
return cache.get(type);
|
||||
}
|
||||
|
||||
try {
|
||||
Method find = type.getMethod("getHandle");
|
||||
|
||||
// It's thread safe, as getMethod should return the same handle
|
||||
cache.put(type, find);
|
||||
return find;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
return null;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static interface Unwrapper {
|
||||
public Object unwrapItem(Object wrappedObject);
|
||||
public Class<?> unwrapType(Class<?> type);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,102 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.apache.commons.lang.NullArgumentException;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
private Set<PacketListener> packetListeners = new CopyOnWriteArraySet<PacketListener>();
|
||||
/**
|
||||
* Sets the inject hook type. Different types allow for maximum compatibility.
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum PlayerInjectHooks {
|
||||
/**
|
||||
* Override the packet queue lists in NetworkHandler.
|
||||
*/
|
||||
NETWORK_HANDLER_FIELDS,
|
||||
|
||||
/**
|
||||
* Override the network handler object itself.
|
||||
*/
|
||||
NETWORK_MANAGER_OBJECT
|
||||
}
|
||||
|
||||
// Create a concurrent set
|
||||
private Set<PacketListener> packetListeners =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
|
||||
|
||||
// Player injection
|
||||
private Map<DataInputStream, Player> connectionLookup = new HashMap<DataInputStream, Player>();
|
||||
private Map<DataInputStream, Player> connectionLookup = new ConcurrentHashMap<DataInputStream, Player>();
|
||||
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
|
||||
|
||||
// Player injection type
|
||||
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
|
||||
|
||||
// Packet injection
|
||||
private PacketInjector packetInjector;
|
||||
|
||||
// Enabled packet filters
|
||||
private Set<Integer> packetFilters = new HashSet<Integer>();
|
||||
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
||||
|
||||
// The two listener containers
|
||||
private ConcurrentListenerMultimap recievedListeners = new ConcurrentListenerMultimap();
|
||||
private ConcurrentListenerMultimap sendingListeners = new ConcurrentListenerMultimap();
|
||||
|
||||
// Whether or not this class has been closed
|
||||
private boolean hasClosed;
|
||||
|
||||
// The default class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Error logger
|
||||
private Logger logger;
|
||||
|
||||
@@ -55,12 +105,13 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
*/
|
||||
public PacketFilterManager(ClassLoader classLoader, Logger logger) {
|
||||
if (logger == null)
|
||||
throw new NullArgumentException("logger");
|
||||
throw new IllegalArgumentException("logger cannot be NULL.");
|
||||
if (classLoader == null)
|
||||
throw new NullArgumentException("classLoader");
|
||||
throw new IllegalArgumentException("classLoader cannot be NULL.");
|
||||
|
||||
try {
|
||||
// Initialize values
|
||||
this.classLoader = classLoader;
|
||||
this.logger = logger;
|
||||
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
||||
} catch (IllegalAccessException e) {
|
||||
@@ -68,6 +119,22 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
public PlayerInjectHooks getPlayerHook() {
|
||||
return playerHook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
this.playerHook = playerHook;
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
@@ -80,21 +147,71 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
@Override
|
||||
public void addPacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new NullArgumentException("listener");
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
packetListeners.add(listener);
|
||||
enablePacketFilters(listener.getConnectionSide(),
|
||||
listener.getPacketsID());
|
||||
// A listener can only be added once
|
||||
if (packetListeners.contains(listener))
|
||||
return;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
boolean hasSending = sending != null && sending.isEnabled();
|
||||
boolean hasReceiving = receiving != null && receiving.isEnabled();
|
||||
|
||||
if (hasSending || hasReceiving) {
|
||||
// Add listeners and hooks
|
||||
if (hasSending) {
|
||||
sendingListeners.addListener(listener, sending);
|
||||
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
||||
}
|
||||
if (hasReceiving) {
|
||||
recievedListeners.addListener(listener, receiving);
|
||||
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
||||
}
|
||||
|
||||
// Inform our injected hooks
|
||||
packetListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new NullArgumentException("listener");
|
||||
throw new IllegalArgumentException("listener cannot be NULL");
|
||||
|
||||
List<Integer> sendingRemoved = null;
|
||||
List<Integer> receivingRemoved = null;
|
||||
|
||||
packetListeners.remove(listener);
|
||||
disablePacketFilters(listener.getConnectionSide(),
|
||||
listener.getPacketsID());
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Remove from the overal list of listeners
|
||||
if (!packetListeners.remove(listener))
|
||||
return;
|
||||
|
||||
// Add listeners
|
||||
if (sending != null && sending.isEnabled())
|
||||
sendingRemoved = sendingListeners.removeListener(listener, sending);
|
||||
if (receiving != null && receiving.isEnabled())
|
||||
receivingRemoved = recievedListeners.removeListener(listener, receiving);
|
||||
|
||||
// Remove hooks, if needed
|
||||
if (sendingRemoved != null && sendingRemoved.size() > 0)
|
||||
disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
|
||||
if (receivingRemoved != null && receivingRemoved.size() > 0)
|
||||
disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketListeners(Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PacketListener listener : packetListeners) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getPlugin(), plugin)) {
|
||||
removePacketListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,15 +219,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(PacketEvent event) {
|
||||
for (PacketListener listener : packetListeners) {
|
||||
try {
|
||||
if (canHandlePacket(listener, event))
|
||||
listener.onPacketReceiving(event);
|
||||
} catch (Exception e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
|
||||
}
|
||||
}
|
||||
recievedListeners.invokePacketRecieving(logger, event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,26 +227,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(PacketEvent event) {
|
||||
for (PacketListener listener : packetListeners) {
|
||||
try {
|
||||
if (canHandlePacket(listener, event))
|
||||
listener.onPacketSending(event);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception occured in onPacketReceiving() for " + listener.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canHandlePacket(PacketListener listener, PacketEvent event) {
|
||||
// Make sure the listener is looking for this packet
|
||||
if (!listener.getPacketsID().contains(event.getPacket().getID()))
|
||||
return false;
|
||||
|
||||
// And this type of packet
|
||||
if (event.isServerPacket())
|
||||
return listener.getConnectionSide().isForServer();
|
||||
else
|
||||
return listener.getConnectionSide().isForClient();
|
||||
sendingListeners.invokePacketSending(logger, event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,13 +238,13 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param side - which side the event will arrive from.
|
||||
* @param packets - the packet id(s).
|
||||
*/
|
||||
private void enablePacketFilters(ConnectionSide side, Set<Integer> packets) {
|
||||
private void enablePacketFilters(ConnectionSide side, Iterable<Integer> packets) {
|
||||
if (side == null)
|
||||
throw new NullArgumentException("side");
|
||||
throw new IllegalArgumentException("side cannot be NULL.");
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
packetFilters.add(packetID);
|
||||
if (side.isForServer())
|
||||
sendingFilters.add(packetID);
|
||||
if (side.isForClient() && packetInjector != null)
|
||||
packetInjector.addPacketHandler(packetID);
|
||||
}
|
||||
@@ -165,13 +255,13 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param packets - the packet id(s).
|
||||
* @param side - which side the event no longer should arrive from.
|
||||
*/
|
||||
private void disablePacketFilters(ConnectionSide side, Set<Integer> packets) {
|
||||
private void disablePacketFilters(ConnectionSide side, Iterable<Integer> packets) {
|
||||
if (side == null)
|
||||
throw new NullArgumentException("side");
|
||||
throw new IllegalArgumentException("side cannot be NULL.");
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
packetFilters.remove(packetID);
|
||||
sendingFilters.remove(packetID);
|
||||
if (side.isForClient() && packetInjector != null)
|
||||
packetInjector.removePacketHandler(packetID);
|
||||
}
|
||||
@@ -185,9 +275,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
@Override
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||
if (reciever == null)
|
||||
throw new NullArgumentException("reciever");
|
||||
throw new IllegalArgumentException("reciever cannot be NULL.");
|
||||
if (packet == null)
|
||||
throw new NullArgumentException("packet");
|
||||
throw new IllegalArgumentException("packet cannot be NULL.");
|
||||
|
||||
getInjector(reciever).sendServerPacket(packet.getHandle(), filters);
|
||||
}
|
||||
@@ -201,9 +291,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
if (sender == null)
|
||||
throw new NullArgumentException("sender");
|
||||
throw new IllegalArgumentException("sender cannot be NULL.");
|
||||
if (packet == null)
|
||||
throw new NullArgumentException("packet");
|
||||
throw new IllegalArgumentException("packet cannot be NULL.");
|
||||
|
||||
PlayerInjector injector = getInjector(sender);
|
||||
Packet mcPacket = packet.getHandle();
|
||||
@@ -217,15 +307,50 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
@Override
|
||||
public PacketContainer createPacket(int id) {
|
||||
return new PacketContainer(id);
|
||||
return createPacket(id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getPacketFilters() {
|
||||
if (packetInjector != null)
|
||||
return Sets.union(packetFilters, packetInjector.getPacketHandlers());
|
||||
else
|
||||
return packetFilters;
|
||||
public PacketContainer createPacket(int id, boolean forceDefaults) {
|
||||
PacketContainer packet = new PacketContainer(id);
|
||||
|
||||
// Use any default values if possible
|
||||
if (forceDefaults) {
|
||||
try {
|
||||
packet.getModifier().writeDefaults();
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Security exception.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
|
||||
Class<?>[] types = new Class<?>[arguments.length];
|
||||
|
||||
// Initialize types
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
types[i] = arguments[i] != null ? arguments[i].getClass() : Object.class;
|
||||
}
|
||||
|
||||
return PacketConstructor.DEFAULT.withPacket(id, types);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return ImmutableSet.copyOf(sendingFilters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> getReceivingFilters() {
|
||||
return ImmutableSet.copyOf(packetInjector.getPacketHandlers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
EntityUtilities.updateEntity(entity, observers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,12 +362,35 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
injectPlayer(player);
|
||||
}
|
||||
|
||||
private void injectPlayer(Player player) {
|
||||
/**
|
||||
* Used to construct a player hook.
|
||||
* @param player - the player to hook.
|
||||
* @return A new player hoook
|
||||
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||
*/
|
||||
protected PlayerInjector getPlayerHookInstance(Player player) throws IllegalAccessException {
|
||||
|
||||
// Construct the correct player hook
|
||||
switch (playerHook) {
|
||||
case NETWORK_HANDLER_FIELDS:
|
||||
return new NetworkFieldInjector(player, this, sendingFilters);
|
||||
case NETWORK_MANAGER_OBJECT:
|
||||
return new NetworkObjectInjector(player, this, sendingFilters);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* @param player - player to hook.
|
||||
*/
|
||||
protected void injectPlayer(Player player) {
|
||||
// Don't inject if the class has closed
|
||||
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
||||
try {
|
||||
PlayerInjector injector = new PlayerInjector(player, this, packetFilters);
|
||||
|
||||
PlayerInjector injector = getPlayerHookInstance(player);
|
||||
|
||||
injector.injectManager();
|
||||
playerInjection.put(player, injector);
|
||||
connectionLookup.put(injector.getInputStream(false), player);
|
||||
@@ -260,19 +408,121 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||
* @param plugin - the parent plugin.
|
||||
*/
|
||||
public void registerEvents(PluginManager manager, Plugin plugin) {
|
||||
manager.registerEvents(new Listener() {
|
||||
public void registerEvents(PluginManager manager, final Plugin plugin) {
|
||||
|
||||
try {
|
||||
manager.registerEvents(new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
injectPlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
uninjectPlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPluginDisabled(PluginDisableEvent event) {
|
||||
// Clean up in case the plugin forgets
|
||||
if (event.getPlugin() != plugin) {
|
||||
removePacketListeners(event.getPlugin());
|
||||
}
|
||||
}
|
||||
|
||||
}, plugin);
|
||||
|
||||
} catch (NoSuchMethodError e) {
|
||||
// Oh wow! We're running on 1.0.0 or older.
|
||||
registerOld(manager, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, this is crazy.
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void registerOld(PluginManager manager, Plugin plugin) {
|
||||
|
||||
try {
|
||||
ClassLoader loader = manager.getClass().getClassLoader();
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
injectPlayer(event.getPlayer());
|
||||
}
|
||||
// The different enums we are going to need
|
||||
Class eventTypes = loader.loadClass("org.bukkit.event.Event$Type");
|
||||
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
uninjectPlayer(event.getPlayer());
|
||||
}
|
||||
}, plugin);
|
||||
// Get the priority
|
||||
Object priorityNormal = Enum.valueOf(eventPriority, "Normal");
|
||||
|
||||
// Get event types
|
||||
Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
|
||||
Object playerQuitType = Enum.valueOf(eventTypes, "PLAYER_QUIT");
|
||||
Object pluginDisabledType = Enum.valueOf(eventTypes, "PLUGIN_DISABLE");
|
||||
|
||||
// The player listener! Good times.
|
||||
Class<?> playerListener = loader.loadClass("org.bukkit.event.player.PlayerListener");
|
||||
Class<?> serverListener = loader.loadClass("org.bukkit.event.server.ServerListener");
|
||||
|
||||
// Find the register event method
|
||||
Method registerEvent = FuzzyReflection.fromObject(manager).getMethodByParameters("registerEvent",
|
||||
eventTypes, Listener.class, eventPriority, Plugin.class);
|
||||
|
||||
Enhancer playerEx = new Enhancer();
|
||||
Enhancer serverEx = new Enhancer();
|
||||
|
||||
playerEx.setSuperclass(playerListener);
|
||||
playerEx.setClassLoader(classLoader);
|
||||
playerEx.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
// Must have a parameter
|
||||
if (args.length == 1) {
|
||||
Object event = args[0];
|
||||
|
||||
// Check for the correct event
|
||||
if (event instanceof PlayerJoinEvent)
|
||||
injectPlayer(((PlayerJoinEvent) event).getPlayer());
|
||||
else if (event instanceof PlayerQuitEvent)
|
||||
injectPlayer(((PlayerQuitEvent) event).getPlayer());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
serverEx.setSuperclass(serverListener);
|
||||
serverEx.setClassLoader(classLoader);
|
||||
serverEx.setCallback(new MethodInterceptor() {
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args,
|
||||
MethodProxy proxy) throws Throwable {
|
||||
// Must have a parameter
|
||||
if (args.length == 1) {
|
||||
Object event = args[0];
|
||||
|
||||
if (event instanceof PluginDisableEvent)
|
||||
removePacketListeners(((PluginDisableEvent) event).getPlugin());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Create our listener
|
||||
Object playerProxy = playerEx.create();
|
||||
Object serverProxy = serverEx.create();
|
||||
|
||||
registerEvent.invoke(manager, playerJoinType, playerProxy, priorityNormal, plugin);
|
||||
registerEvent.invoke(manager, playerQuitType, playerProxy, priorityNormal, plugin);
|
||||
registerEvent.invoke(manager, pluginDisabledType, serverProxy, priorityNormal, plugin);
|
||||
|
||||
// A lot can go wrong
|
||||
} catch (ClassNotFoundException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void uninjectPlayer(Player player) {
|
||||
@@ -305,6 +555,14 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
throw new IllegalArgumentException("Player has no injected handler.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current plugin class loader.
|
||||
* @return Class loader.
|
||||
*/
|
||||
public ClassLoader getClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return hasClosed;
|
||||
@@ -324,6 +582,8 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
if (packetInjector != null)
|
||||
packetInjector.cleanupAll();
|
||||
|
||||
// Remove listeners
|
||||
packetListeners.clear();
|
||||
playerInjection.clear();
|
||||
connectionLookup.clear();
|
||||
hasClosed = true;
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
@@ -15,6 +32,7 @@ import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
|
||||
/**
|
||||
@@ -49,10 +67,10 @@ class PacketInjector {
|
||||
private void initialize() throws IllegalAccessException {
|
||||
if (intHashMap == null) {
|
||||
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
||||
Field intHashMapField = FuzzyReflection.fromClass(Packet.class).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
Field intHashMapField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||
|
||||
try {
|
||||
intHashMap = intHashMapField.get(null);
|
||||
intHashMap = FieldUtils.readField(intHashMapField, (Object) null, true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Minecraft is incompatible.", e);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.EntityPlayer;
|
||||
@@ -18,48 +33,55 @@ import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
|
||||
class PlayerInjector {
|
||||
|
||||
abstract class PlayerInjector {
|
||||
|
||||
// Cache previously retrieved fields
|
||||
private static Field serverHandlerField;
|
||||
private static Field networkManagerField;
|
||||
private static Field inputField;
|
||||
private static Field netHandlerField;
|
||||
protected static Field serverHandlerField;
|
||||
protected static Field networkManagerField;
|
||||
protected static Field inputField;
|
||||
protected static Field netHandlerField;
|
||||
|
||||
// To add our injected array lists
|
||||
protected static StructureModifier<Object> networkModifier;
|
||||
|
||||
// And methods
|
||||
private static Method queueMethod;
|
||||
private static Method processMethod;
|
||||
protected static Method queueMethod;
|
||||
protected static Method processMethod;
|
||||
|
||||
private Player player;
|
||||
private boolean hasInitialized;
|
||||
|
||||
protected Player player;
|
||||
protected boolean hasInitialized;
|
||||
|
||||
// Reference to the player's network manager
|
||||
private VolatileField networkManager;
|
||||
|
||||
// The packet manager and filters
|
||||
private PacketFilterManager manager;
|
||||
private Set<Integer> packetFilters;
|
||||
|
||||
// Previous data input
|
||||
private DataInputStream cachedInput;
|
||||
protected VolatileField networkManagerRef;
|
||||
protected Object networkManager;
|
||||
|
||||
// Current net handler
|
||||
private Object netHandler;
|
||||
protected Object netHandler;
|
||||
|
||||
// The packet manager and filters
|
||||
protected PacketFilterManager manager;
|
||||
protected Set<Integer> sendingFilters;
|
||||
|
||||
// Previous data input
|
||||
protected DataInputStream cachedInput;
|
||||
|
||||
public PlayerInjector(Player player, PacketFilterManager manager, Set<Integer> packetFilters) throws IllegalAccessException {
|
||||
public PlayerInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
|
||||
this.player = player;
|
||||
this.manager = manager;
|
||||
this.packetFilters = packetFilters;
|
||||
this.sendingFilters = sendingFilters;
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() throws IllegalAccessException {
|
||||
protected void initialize() throws IllegalAccessException {
|
||||
|
||||
CraftPlayer craft = (CraftPlayer) player;
|
||||
EntityPlayer notchEntity = craft.getHandle();
|
||||
|
||||
Object serverHandler = null;
|
||||
|
||||
if (!hasInitialized) {
|
||||
// Do this first, in case we encounter an exception
|
||||
hasInitialized = true;
|
||||
@@ -67,12 +89,17 @@ class PlayerInjector {
|
||||
// Retrieve the server handler
|
||||
if (serverHandlerField == null)
|
||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
|
||||
Object serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
|
||||
serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
|
||||
|
||||
// Next, get the network manager
|
||||
if (networkManagerField == null)
|
||||
if (networkManagerField == null)
|
||||
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(".*NetworkManager");
|
||||
networkManager = new VolatileField(networkManagerField, serverHandler);
|
||||
networkManagerRef = new VolatileField(networkManagerField, serverHandler);
|
||||
networkManager = networkManagerRef.getValue();
|
||||
|
||||
// Create the network manager modifier from the actual object type
|
||||
if (networkManager != null && networkModifier == null)
|
||||
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
|
||||
|
||||
// And the queue method
|
||||
if (queueMethod == null)
|
||||
@@ -81,7 +108,7 @@ class PlayerInjector {
|
||||
|
||||
// And the data input stream that we'll use to identify a player
|
||||
if (inputField == null)
|
||||
inputField = FuzzyReflection.fromObject(networkManager.getOldValue(), true).
|
||||
inputField = FuzzyReflection.fromObject(networkManager, true).
|
||||
getFieldByType("java\\.io\\.DataInputStream");
|
||||
}
|
||||
}
|
||||
@@ -111,7 +138,7 @@ class PlayerInjector {
|
||||
|
||||
// Get the handler
|
||||
if (netHandler != null)
|
||||
netHandler = FieldUtils.readField(netHandlerField, networkManager.getOldValue(), true);
|
||||
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
|
||||
return netHandler;
|
||||
}
|
||||
|
||||
@@ -151,66 +178,17 @@ class PlayerInjector {
|
||||
* @param filtered - whether or not the packet will be filtered by our listeners.
|
||||
* @param InvocationTargetException If an error occured when sending the packet.
|
||||
*/
|
||||
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||
Object networkDelegate = filtered ? networkManager.getValue() : networkManager.getOldValue();
|
||||
|
||||
if (networkDelegate != null) {
|
||||
try {
|
||||
// Note that invocation target exception is a wrapper for a checked exception
|
||||
queueMethod.invoke(networkDelegate, packet);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Unable to access queue method.", e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
|
||||
}
|
||||
}
|
||||
public abstract void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException;
|
||||
|
||||
public void injectManager() {
|
||||
|
||||
if (networkManager != null) {
|
||||
final Class<?> networkInterface = networkManagerField.getType();
|
||||
final Object networkDelegate = networkManager.getOldValue();
|
||||
|
||||
// Create our proxy object
|
||||
Object networkProxy = Proxy.newProxyInstance(networkInterface.getClassLoader(),
|
||||
new Class<?>[] { networkInterface }, new InvocationHandler() {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// OH OH! The queue method!
|
||||
if (method.equals(queueMethod)) {
|
||||
Packet packet = (Packet) args[0];
|
||||
|
||||
if (packet != null) {
|
||||
packet = handlePacketRecieved(packet);
|
||||
|
||||
// A NULL packet indicate cancelling
|
||||
if (packet != null)
|
||||
args[0] = packet;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate to our underlying class
|
||||
try {
|
||||
return method.invoke(networkDelegate, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inject it, if we can.
|
||||
networkManager.setValue(networkProxy);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Inject a hook to catch packets sent to the current player.
|
||||
*/
|
||||
public abstract void injectManager();
|
||||
|
||||
/**
|
||||
* Remove all hooks and modifications.
|
||||
*/
|
||||
public abstract void cleanupAll();
|
||||
|
||||
/**
|
||||
* Allows a packet to be recieved by the listeners.
|
||||
@@ -222,7 +200,7 @@ class PlayerInjector {
|
||||
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||
|
||||
// Make sure we're listening
|
||||
if (packetFilters.contains(id)) {
|
||||
if (sendingFilters.contains(id)) {
|
||||
// A packet has been sent guys!
|
||||
PacketContainer container = new PacketContainer(id, packet);
|
||||
PacketEvent event = PacketEvent.fromServer(manager, container, player);
|
||||
@@ -239,6 +217,11 @@ class PlayerInjector {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current player's input stream.
|
||||
* @param cache - whether or not to cache the result of this method.
|
||||
* @return The player's input stream.
|
||||
*/
|
||||
public DataInputStream getInputStream(boolean cache) {
|
||||
// Get the associated input stream
|
||||
try {
|
||||
@@ -246,16 +229,11 @@ class PlayerInjector {
|
||||
return cachedInput;
|
||||
|
||||
// Save to cache
|
||||
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager.getOldValue(), true);
|
||||
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
|
||||
return cachedInput;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to read input stream.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Clean up
|
||||
networkManager.revertValue();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,27 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
@@ -55,7 +71,7 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
|
||||
// Is this a readPacketData method?
|
||||
if (returnValue == null &&
|
||||
ArrayUtils.isEquals(method.getParameterTypes(), parameters)) {
|
||||
Arrays.equals(method.getParameterTypes(), parameters)) {
|
||||
|
||||
// We need this in order to get the correct player
|
||||
DataInputStream input = (DataInputStream) args[0];
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -7,6 +24,10 @@ import net.minecraft.server.Packet;
|
||||
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
/**
|
||||
* Caches structure modifiers.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class StructureCache {
|
||||
// Structure modifiers
|
||||
private static Map<Integer, StructureModifier<Object>> structureModifiers = new HashMap<Integer, StructureModifier<Object>>();
|
||||
@@ -37,7 +58,9 @@ public class StructureCache {
|
||||
|
||||
// Use the vanilla class definition
|
||||
if (result == null) {
|
||||
result = new StructureModifier<Object>(MinecraftRegistry.getPacketClassFromID(id, true));
|
||||
result = new StructureModifier<Object>(
|
||||
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
|
||||
|
||||
structureModifiers.put(id, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.metrics;
|
||||
|
||||
/*
|
||||
* Copyright 2011 Tyler Blair. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation are those of the
|
||||
* authors and contributors and should not be interpreted as representing official policies,
|
||||
* either expressed or implied, of anybody else.
|
||||
*/
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginDescriptionFile;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The metrics class obtains data about a plugin and submits statistics about it to the metrics backend.
|
||||
* </p>
|
||||
* <p>
|
||||
* Public methods provided by this class:
|
||||
* </p>
|
||||
* <code>
|
||||
* Graph createGraph(String name); <br/>
|
||||
* void addCustomData(Metrics.Plotter plotter); <br/>
|
||||
* void start(); <br/>
|
||||
* </code>
|
||||
*/
|
||||
class Metrics {
|
||||
|
||||
/**
|
||||
* The current revision number
|
||||
*/
|
||||
private final static int REVISION = 5;
|
||||
|
||||
/**
|
||||
* The base url of the metrics domain
|
||||
*/
|
||||
private static final String BASE_URL = "http://mcstats.org";
|
||||
|
||||
/**
|
||||
* The url used to report a server's status
|
||||
*/
|
||||
private static final String REPORT_URL = "/report/%s";
|
||||
|
||||
/**
|
||||
* The separator to use for custom data. This MUST NOT change unless you are hosting your own
|
||||
* version of metrics and want to change it.
|
||||
*/
|
||||
private static final String CUSTOM_DATA_SEPARATOR = "~~";
|
||||
|
||||
/**
|
||||
* Interval of time to ping (in minutes)
|
||||
*/
|
||||
private static final int PING_INTERVAL = 10;
|
||||
|
||||
/**
|
||||
* The plugin this metrics submits for
|
||||
*/
|
||||
private final Plugin plugin;
|
||||
|
||||
/**
|
||||
* All of the custom graphs to submit to metrics
|
||||
*/
|
||||
private final Set<Graph> graphs = Collections.synchronizedSet(new HashSet<Graph>());
|
||||
|
||||
/**
|
||||
* The default graph, used for addCustomData when you don't want a specific graph
|
||||
*/
|
||||
private final Graph defaultGraph = new Graph("Default");
|
||||
|
||||
/**
|
||||
* The plugin configuration file
|
||||
*/
|
||||
private final YamlConfiguration configuration;
|
||||
|
||||
/**
|
||||
* The plugin configuration file
|
||||
*/
|
||||
private final File configurationFile;
|
||||
|
||||
/**
|
||||
* Unique server id
|
||||
*/
|
||||
private final String guid;
|
||||
|
||||
/**
|
||||
* Lock for synchronization
|
||||
*/
|
||||
private final Object optOutLock = new Object();
|
||||
|
||||
/**
|
||||
* Id of the scheduled task
|
||||
*/
|
||||
private volatile int taskId = -1;
|
||||
|
||||
public Metrics(final Plugin plugin) throws IOException {
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("Plugin cannot be null");
|
||||
}
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
// load the config
|
||||
configurationFile = getConfigFile();
|
||||
configuration = YamlConfiguration.loadConfiguration(configurationFile);
|
||||
|
||||
// add some defaults
|
||||
configuration.addDefault("opt-out", false);
|
||||
configuration.addDefault("guid", UUID.randomUUID().toString());
|
||||
|
||||
// Do we need to create the file?
|
||||
if (configuration.get("guid", null) == null) {
|
||||
configuration.options().header("http://mcstats.org").copyDefaults(true);
|
||||
configuration.save(configurationFile);
|
||||
}
|
||||
|
||||
// Load the guid then
|
||||
guid = configuration.getString("guid");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct and create a Graph that can be used to separate specific plotters to their own graphs
|
||||
* on the metrics website. Plotters can be added to the graph object returned.
|
||||
*
|
||||
* @param name The name of the graph
|
||||
* @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given
|
||||
*/
|
||||
public Graph createGraph(final String name) {
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("Graph name cannot be null");
|
||||
}
|
||||
|
||||
// Construct the graph object
|
||||
final Graph graph = new Graph(name);
|
||||
|
||||
// Now we can add our graph
|
||||
graphs.add(graph);
|
||||
|
||||
// and return back
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Graph object to Metrics that represents data for the plugin that should be sent to the backend
|
||||
*
|
||||
* @param graph The name of the graph
|
||||
*/
|
||||
public void addGraph(final Graph graph) {
|
||||
if (graph == null) {
|
||||
throw new IllegalArgumentException("Graph cannot be null");
|
||||
}
|
||||
|
||||
graphs.add(graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom data plotter to the default graph
|
||||
*
|
||||
* @param plotter The plotter to use to plot custom data
|
||||
*/
|
||||
public void addCustomData(final Plotter plotter) {
|
||||
if (plotter == null) {
|
||||
throw new IllegalArgumentException("Plotter cannot be null");
|
||||
}
|
||||
|
||||
// Add the plotter to the graph o/
|
||||
defaultGraph.addPlotter(plotter);
|
||||
|
||||
// Ensure the default graph is included in the submitted graphs
|
||||
graphs.add(defaultGraph);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start measuring statistics. This will immediately create an async repeating task as the plugin and send
|
||||
* the initial data to the metrics backend, and then after that it will post in increments of
|
||||
* PING_INTERVAL * 1200 ticks.
|
||||
*
|
||||
* @return True if statistics measuring is running, otherwise false.
|
||||
*/
|
||||
public boolean start() {
|
||||
synchronized (optOutLock) {
|
||||
// Did we opt out?
|
||||
if (isOptOut()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is metrics already running?
|
||||
if (taskId >= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Begin hitting the server with glorious data
|
||||
taskId = plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() {
|
||||
|
||||
private boolean firstPost = true;
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
// This has to be synchronized or it can collide with the disable method.
|
||||
synchronized (optOutLock) {
|
||||
// Disable Task, if it is running and the server owner decided to opt-out
|
||||
if (isOptOut() && taskId > 0) {
|
||||
plugin.getServer().getScheduler().cancelTask(taskId);
|
||||
taskId = -1;
|
||||
// Tell all plotters to stop gathering information.
|
||||
for (Graph graph : graphs){
|
||||
graph.onOptOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use the inverse of firstPost because if it is the first time we are posting,
|
||||
// it is not a interval ping, so it evaluates to FALSE
|
||||
// Each time thereafter it will evaluate to TRUE, i.e PING!
|
||||
postPlugin(!firstPost);
|
||||
|
||||
// After the first post we set firstPost to false
|
||||
// Each post thereafter will be a ping
|
||||
firstPost = false;
|
||||
} catch (IOException e) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}, 0, PING_INTERVAL * 1200);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the server owner denied plugin metrics?
|
||||
*
|
||||
* @return true if metrics should be opted out of it
|
||||
*/
|
||||
public boolean isOptOut() {
|
||||
synchronized(optOutLock) {
|
||||
try {
|
||||
// Reload the metrics file
|
||||
configuration.load(getConfigFile());
|
||||
} catch (IOException ex) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
|
||||
return true;
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage());
|
||||
return true;
|
||||
}
|
||||
return configuration.getBoolean("opt-out", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void enable() throws IOException {
|
||||
// This has to be synchronized or it can collide with the check in the task.
|
||||
synchronized (optOutLock) {
|
||||
// Check if the server owner has already set opt-out, if not, set it.
|
||||
if (isOptOut()) {
|
||||
configuration.set("opt-out", false);
|
||||
configuration.save(configurationFile);
|
||||
}
|
||||
|
||||
// Enable Task, if it is not running
|
||||
if (taskId < 0) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void disable() throws IOException {
|
||||
// This has to be synchronized or it can collide with the check in the task.
|
||||
synchronized (optOutLock) {
|
||||
// Check if the server owner has already set opt-out, if not, set it.
|
||||
if (!isOptOut()) {
|
||||
configuration.set("opt-out", true);
|
||||
configuration.save(configurationFile);
|
||||
}
|
||||
|
||||
// Disable Task, if it is running
|
||||
if (taskId > 0) {
|
||||
this.plugin.getServer().getScheduler().cancelTask(taskId);
|
||||
taskId = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the File object of the config file that should be used to store data such as the GUID and opt-out status
|
||||
*
|
||||
* @return the File object for the config file
|
||||
*/
|
||||
public File getConfigFile() {
|
||||
// I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use
|
||||
// is to abuse the plugin object we already have
|
||||
// plugin.getDataFolder() => base/plugins/PluginA/
|
||||
// pluginsFolder => base/plugins/
|
||||
// The base is not necessarily relative to the startup directory.
|
||||
File pluginsFolder = plugin.getDataFolder().getParentFile();
|
||||
|
||||
// return => base/plugins/PluginMetrics/config.yml
|
||||
return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method that posts a plugin to the metrics website
|
||||
*/
|
||||
private void postPlugin(final boolean isPing) throws IOException {
|
||||
// The plugin's description file containg all of the plugin data such as name, version, author, etc
|
||||
final PluginDescriptionFile description = plugin.getDescription();
|
||||
|
||||
// Construct the post data
|
||||
final StringBuilder data = new StringBuilder();
|
||||
data.append(encode("guid")).append('=').append(encode(guid));
|
||||
encodeDataPair(data, "version", description.getVersion());
|
||||
encodeDataPair(data, "server", Bukkit.getVersion());
|
||||
encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length));
|
||||
encodeDataPair(data, "revision", String.valueOf(REVISION));
|
||||
|
||||
// If we're pinging, append it
|
||||
if (isPing) {
|
||||
encodeDataPair(data, "ping", "true");
|
||||
}
|
||||
|
||||
// Acquire a lock on the graphs, which lets us make the assumption we also lock everything
|
||||
// inside of the graph (e.g plotters)
|
||||
synchronized (graphs) {
|
||||
final Iterator<Graph> iter = graphs.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
final Graph graph = iter.next();
|
||||
|
||||
for (Plotter plotter : graph.getPlotters()) {
|
||||
// The key name to send to the metrics server
|
||||
// The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top
|
||||
// Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME
|
||||
final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName());
|
||||
|
||||
// The value to send, which for the foreseeable future is just the string
|
||||
// value of plotter.getValue()
|
||||
final String value = Integer.toString(plotter.getValue());
|
||||
|
||||
// Add it to the http post data :)
|
||||
encodeDataPair(data, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the url
|
||||
URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(plugin.getDescription().getName())));
|
||||
|
||||
// Connect to the website
|
||||
URLConnection connection;
|
||||
|
||||
// Mineshafter creates a socks proxy, so we can safely bypass it
|
||||
// It does not reroute POST requests so we need to go around it
|
||||
if (isMineshafterPresent()) {
|
||||
connection = url.openConnection(Proxy.NO_PROXY);
|
||||
} else {
|
||||
connection = url.openConnection();
|
||||
}
|
||||
|
||||
connection.setDoOutput(true);
|
||||
|
||||
// Write the data
|
||||
final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
writer.write(data.toString());
|
||||
writer.flush();
|
||||
|
||||
// Now read the response
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
final String response = reader.readLine();
|
||||
|
||||
// close resources
|
||||
writer.close();
|
||||
reader.close();
|
||||
|
||||
if (response == null || response.startsWith("ERR")) {
|
||||
throw new IOException(response); //Throw the exception
|
||||
} else {
|
||||
// Is this the first update this hour?
|
||||
if (response.contains("OK This is your first update this hour")) {
|
||||
synchronized (graphs) {
|
||||
final Iterator<Graph> iter = graphs.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
final Graph graph = iter.next();
|
||||
|
||||
for (Plotter plotter : graph.getPlotters()) {
|
||||
plotter.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if mineshafter is present. If it is, we need to bypass it to send POST requests
|
||||
*
|
||||
* @return true if mineshafter is installed on the server
|
||||
*/
|
||||
private boolean isMineshafterPresent() {
|
||||
try {
|
||||
Class.forName("mineshafter.MineServer");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first
|
||||
* key/value pair MUST be included manually, e.g:</p>
|
||||
* <code>
|
||||
* StringBuffer data = new StringBuffer();
|
||||
* data.append(encode("guid")).append('=').append(encode(guid));
|
||||
* encodeDataPair(data, "version", description.getVersion());
|
||||
* </code>
|
||||
*
|
||||
* @param buffer the stringbuilder to append the data pair onto
|
||||
* @param key the key value
|
||||
* @param value the value
|
||||
*/
|
||||
private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException {
|
||||
buffer.append('&').append(encode(key)).append('=').append(encode(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode text as UTF-8
|
||||
*
|
||||
* @param text the text to encode
|
||||
* @return the encoded text, as UTF-8
|
||||
*/
|
||||
private static String encode(final String text) throws UnsupportedEncodingException {
|
||||
return URLEncoder.encode(text, "UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a custom graph on the website
|
||||
*/
|
||||
public static class Graph {
|
||||
|
||||
/**
|
||||
* The graph's name, alphanumeric and spaces only :)
|
||||
* If it does not comply to the above when submitted, it is rejected
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The set of plotters that are contained within this graph
|
||||
*/
|
||||
private final Set<Plotter> plotters = new LinkedHashSet<Plotter>();
|
||||
|
||||
private Graph(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the graph's name
|
||||
*
|
||||
* @return the Graph's name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plotter to the graph, which will be used to plot entries
|
||||
*
|
||||
* @param plotter the plotter to add to the graph
|
||||
*/
|
||||
public void addPlotter(final Plotter plotter) {
|
||||
plotters.add(plotter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a plotter from the graph
|
||||
*
|
||||
* @param plotter the plotter to remove from the graph
|
||||
*/
|
||||
public void removePlotter(final Plotter plotter) {
|
||||
plotters.remove(plotter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an <b>unmodifiable</b> set of the plotter objects in the graph
|
||||
*
|
||||
* @return an unmodifiable {@link Set} of the plotter objects
|
||||
*/
|
||||
public Set<Plotter> getPlotters() {
|
||||
return Collections.unmodifiableSet(plotters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object object) {
|
||||
if (!(object instanceof Graph)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Graph graph = (Graph) object;
|
||||
return graph.name.equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the server owner decides to opt-out of Metrics while the server is running.
|
||||
*/
|
||||
protected void onOptOut() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to collect custom data for a plugin
|
||||
*/
|
||||
public static abstract class Plotter {
|
||||
|
||||
/**
|
||||
* The plot's name
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Construct a plotter with the default plot name
|
||||
*/
|
||||
public Plotter() {
|
||||
this("Default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a plotter with a specific plot name
|
||||
*
|
||||
* @param name the name of the plotter to use, which will show up on the website
|
||||
*/
|
||||
public Plotter(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current value for the plotted point. Since this function defers to an external function
|
||||
* it may or may not return immediately thus cannot be guaranteed to be thread friendly or safe.
|
||||
* This function can be called from any thread so care should be taken when accessing resources
|
||||
* that need to be synchronized.
|
||||
*
|
||||
* @return the current value for the point to be plotted.
|
||||
*/
|
||||
public abstract int getValue();
|
||||
|
||||
/**
|
||||
* Get the column name for the plotted point
|
||||
*
|
||||
* @return the plotted point's column name
|
||||
*/
|
||||
public String getColumnName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the website graphs have been updated
|
||||
*/
|
||||
public void reset() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getColumnName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object object) {
|
||||
if (!(object instanceof Plotter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Plotter plotter = (Plotter) object;
|
||||
return plotter.name.equals(name) && plotter.getValue() == getValue();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.metrics;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.metrics.Metrics.Graph;
|
||||
|
||||
public class Statistics {
|
||||
|
||||
// Metrics
|
||||
private Metrics metrics;
|
||||
|
||||
public Statistics(Plugin plugin) throws IOException {
|
||||
metrics = new Metrics(plugin);
|
||||
metrics.start();
|
||||
|
||||
// Determine who is using this library
|
||||
addPluginUserGraph(metrics);
|
||||
}
|
||||
|
||||
private void addPluginUserGraph(Metrics metrics) {
|
||||
|
||||
Graph pluginUsers = metrics.createGraph("Plugin Users");
|
||||
|
||||
for (Map.Entry<String, Integer> entry : getPluginUsers(ProtocolLibrary.getProtocolManager()).entrySet()) {
|
||||
final int count = entry.getValue();
|
||||
|
||||
// Plot plugins of this type
|
||||
pluginUsers.addPlotter(new Metrics.Plotter(entry.getKey()) {
|
||||
@Override
|
||||
public int getValue() {
|
||||
return count;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve loaded plugins
|
||||
private Map<String, Integer> getPluginUsers(ProtocolManager manager) {
|
||||
|
||||
Map<String, Integer> users = new HashMap<String, Integer>();
|
||||
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
|
||||
String name = PacketAdapter.getPluginName(listener);
|
||||
|
||||
// Increment occurence
|
||||
if (!users.containsKey(name)) {
|
||||
users.put(name, 1);
|
||||
} else {
|
||||
users.put(name, users.get(name) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
/**
|
||||
@@ -9,4 +26,5 @@ package com.comphenix.protocol.reflect;
|
||||
public interface EquivalentConverter<TType> {
|
||||
public TType getSpecific(Object generic);
|
||||
public Object getGeneric(TType specific);
|
||||
public Class<TType> getSpecificType();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
/**
|
||||
* Invoked when a field is inaccessible due to security limitations, or when it simply doesn't exist.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class FieldAccessException extends Exception {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 1911011681494034617L;
|
||||
|
||||
public FieldAccessException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FieldAccessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public FieldAccessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FieldAccessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public static FieldAccessException fromFormat(String message, Object... params) {
|
||||
return new FieldAccessException(String.format(message, params));
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang.ClassUtils;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utilities for working with fields by reflection. Adapted and refactored from
|
||||
@@ -119,11 +119,11 @@ public class FieldUtils {
|
||||
}
|
||||
}
|
||||
// check the public interface case. This must be manually searched for
|
||||
// incase there is a public supersuperclass field hidden by a
|
||||
// in case there is a public supersuperclass field hidden by a
|
||||
// private/package
|
||||
// superclass field.
|
||||
Field match = null;
|
||||
for (Iterator intf = ClassUtils.getAllInterfaces(cls).iterator(); intf.hasNext();) {
|
||||
for (Iterator intf = getAllInterfaces(cls).iterator(); intf.hasNext();) {
|
||||
try {
|
||||
Field test = ((Class) intf.next()).getField(fieldName);
|
||||
if (match != null) {
|
||||
@@ -138,6 +138,44 @@ public class FieldUtils {
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets a <code>List</code> of all interfaces implemented by the given
|
||||
* class and its superclasses.</p>
|
||||
*
|
||||
* <p>The order is determined by looking through each interface in turn as
|
||||
* declared in the source file and following its hierarchy up. Then each
|
||||
* superclass is considered in the same way. Later duplicates are ignored,
|
||||
* so the order is maintained.</p>
|
||||
*
|
||||
* @param cls the class to look up, may be <code>null</code>
|
||||
* @return the <code>List</code> of interfaces in order,
|
||||
* <code>null</code> if null input
|
||||
*/
|
||||
private static List getAllInterfaces(Class cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
List<Class> list = new ArrayList<Class>();
|
||||
|
||||
while (cls != null) {
|
||||
Class[] interfaces = cls.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
if (list.contains(interfaces[i]) == false) {
|
||||
list.add(interfaces[i]);
|
||||
}
|
||||
List superInterfaces = getAllInterfaces(interfaces[i]);
|
||||
for (Iterator it = superInterfaces.iterator(); it.hasNext();) {
|
||||
Class intface = (Class) it.next();
|
||||
if (list.contains(intface) == false) {
|
||||
list.add(intface);
|
||||
}
|
||||
}
|
||||
}
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an accessible static Field.
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
/**
|
||||
* Retrieves fields and methods by signature, not just name.
|
||||
*
|
||||
@@ -106,7 +122,27 @@ public class FuzzyReflection {
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (ArrayUtils.isEquals(method.getParameterTypes(), args)) {
|
||||
if (Arrays.equals(method.getParameterTypes(), args)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
|
||||
// That sucks
|
||||
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a method by looking at the parameter types and return type only.
|
||||
* @param name - potential name of the method. Only used by the error mechanism.
|
||||
* @param returnType - return type of the method to find.
|
||||
* @param args - parameter types of the method to find.
|
||||
* @return The first method that satisfies the parameter types.
|
||||
*/
|
||||
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
|
||||
|
||||
// Find the correct method to call
|
||||
for (Method method : getMethods()) {
|
||||
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
@@ -163,6 +199,37 @@ public class FuzzyReflection {
|
||||
typeRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a field by type.
|
||||
* <p>
|
||||
* Note that the type is matched using the full canonical representation, i.e.:
|
||||
* <ul>
|
||||
* <li>java.util.List</li>
|
||||
* <li>net.comphenix.xp.ExperienceMod</li>
|
||||
* </ul>
|
||||
* @param typeRegex - regular expression that will match the field type.
|
||||
* @param ignored - types to ignore.
|
||||
* @return The first field with a type that matches the given regular expression.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Field getFieldByType(String typeRegex, Set<Class> ignored) {
|
||||
|
||||
Pattern match = Pattern.compile(typeRegex);
|
||||
|
||||
// Like above, only here we test the field type
|
||||
for (Field field : getFields()) {
|
||||
Class type = field.getType();
|
||||
|
||||
if (!ignored.contains(type) && match.matcher(type.getName()).matches()) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like we're outdated. Too bad.
|
||||
throw new RuntimeException("Unable to find a field with the type " +
|
||||
typeRegex + " in " + source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all private and public fields in declared order (after JDK 1.5).
|
||||
* @return Every field.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
|
||||
/**
|
||||
* Represents a traditional int field enum.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntEnum {
|
||||
|
||||
// Used to convert between IDs and names
|
||||
protected BiMap<Integer, String> members = HashBiMap.create();
|
||||
|
||||
/**
|
||||
* Registers every declared integer field.
|
||||
*/
|
||||
public IntEnum() {
|
||||
registerAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers every public int field as a member.
|
||||
*/
|
||||
protected void registerAll() {
|
||||
try {
|
||||
// Register every int field
|
||||
for (Field entry : this.getClass().getFields()) {
|
||||
if (entry.getType().equals(int.class)) {
|
||||
registerMember(entry.getInt(this), entry.getName());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a member.
|
||||
* @param id - id of member.
|
||||
* @param name - name of member.
|
||||
*/
|
||||
protected void registerMember(int id, String name) {
|
||||
members.put(id, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the given member exists.
|
||||
* @param id - the ID of the member to find.
|
||||
* @return TRUE if a member with the given ID exists, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMember(int id) {
|
||||
return members.containsKey(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of the member with the given name.
|
||||
* @param name - name of member to retrieve.
|
||||
* @return ID of the member, or NULL if not found.
|
||||
*/
|
||||
public Integer valueOf(String name) {
|
||||
return members.inverse().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the member with the given id.
|
||||
* @param id - id of the member to retrieve.
|
||||
* @return Declared name of the member, or NULL if not found.
|
||||
*/
|
||||
public String getDeclaredName(Integer id) {
|
||||
return members.get(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Contains static utility methods pertaining to primitive types and their
|
||||
* corresponding wrapper types.
|
||||
*
|
||||
* @author Kevin Bourrillion
|
||||
*/
|
||||
public final class PrimitiveUtils {
|
||||
private PrimitiveUtils() {
|
||||
}
|
||||
|
||||
/** A map from primitive types to their corresponding wrapper types. */
|
||||
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
|
||||
|
||||
/** A map from wrapper types to their corresponding primitive types. */
|
||||
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPE;
|
||||
|
||||
// Sad that we can't use a BiMap. :(
|
||||
static {
|
||||
Map<Class<?>, Class<?>> primToWrap = new HashMap<Class<?>, Class<?>>(16);
|
||||
Map<Class<?>, Class<?>> wrapToPrim = new HashMap<Class<?>, Class<?>>(16);
|
||||
|
||||
add(primToWrap, wrapToPrim, boolean.class, Boolean.class);
|
||||
add(primToWrap, wrapToPrim, byte.class, Byte.class);
|
||||
add(primToWrap, wrapToPrim, char.class, Character.class);
|
||||
add(primToWrap, wrapToPrim, double.class, Double.class);
|
||||
add(primToWrap, wrapToPrim, float.class, Float.class);
|
||||
add(primToWrap, wrapToPrim, int.class, Integer.class);
|
||||
add(primToWrap, wrapToPrim, long.class, Long.class);
|
||||
add(primToWrap, wrapToPrim, short.class, Short.class);
|
||||
add(primToWrap, wrapToPrim, void.class, Void.class);
|
||||
|
||||
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
|
||||
WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim);
|
||||
}
|
||||
|
||||
private static void add(Map<Class<?>, Class<?>> forward,
|
||||
Map<Class<?>, Class<?>> backward, Class<?> key, Class<?> value) {
|
||||
forward.put(key, value);
|
||||
backward.put(value, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this type is a primitive.
|
||||
*/
|
||||
public static boolean isPrimitive(Type type) {
|
||||
return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@code type} is one of the nine primitive-wrapper
|
||||
* types, such as {@link Integer}.
|
||||
*
|
||||
* @see Class#isPrimitive
|
||||
*/
|
||||
public static boolean isWrapperType(Type type) {
|
||||
return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding wrapper type of {@code type} if it is a
|
||||
* primitive type; otherwise returns {@code type} itself. Idempotent.
|
||||
*
|
||||
* <pre>
|
||||
* wrap(int.class) == Integer.class
|
||||
* wrap(Integer.class) == Integer.class
|
||||
* wrap(String.class) == String.class
|
||||
* </pre>
|
||||
*/
|
||||
public static <T> Class<T> wrap(Class<T> type) {
|
||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE
|
||||
.get(checkNotNull(type));
|
||||
return (wrapped == null) ? type : wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding primitive type of {@code type} if it is a
|
||||
* wrapper type; otherwise returns {@code type} itself. Idempotent.
|
||||
*
|
||||
* <pre>
|
||||
* unwrap(Integer.class) == int.class
|
||||
* unwrap(int.class) == int.class
|
||||
* unwrap(String.class) == String.class
|
||||
* </pre>
|
||||
*/
|
||||
public static <T> Class<T> unwrap(Class<T> type) {
|
||||
// cast is safe: long.class and Long.class are both of type Class<Long>
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> unwrapped = (Class<T>) WRAPPER_TO_PRIMITIVE_TYPE
|
||||
.get(checkNotNull(type));
|
||||
return (unwrapped == null) ? type : unwrapped;
|
||||
}
|
||||
|
||||
public static <T> T checkNotNull(T obj) {
|
||||
if (obj == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,38 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StructureModifier<TField> {
|
||||
|
||||
|
||||
// Object and its type
|
||||
private Class targetType;
|
||||
private Object target;
|
||||
@@ -25,47 +44,85 @@ public class StructureModifier<TField> {
|
||||
private Class fieldType;
|
||||
private List<Field> data = new ArrayList<Field>();
|
||||
|
||||
// Improved default values
|
||||
private Set<Field> defaultFields;
|
||||
|
||||
// Cache of previous types
|
||||
private Map<Class, StructureModifier> subtypeCache;
|
||||
|
||||
public StructureModifier(Class targetType) {
|
||||
this(targetType, Object.class, getFields(targetType), null, new HashMap<Class, StructureModifier>());
|
||||
}
|
||||
|
||||
private StructureModifier(Class targetType, Class fieldType, List<Field> data,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
this.data = data;
|
||||
this.converter = converter;
|
||||
this.subtypeCache = subTypeCache;
|
||||
/**
|
||||
* Creates a structure modifier.
|
||||
* @param targetType - the structure to modify.
|
||||
* @param superclassExclude - a superclass to exclude.
|
||||
* @param requireDefault - whether or not we will be using writeDefaults().
|
||||
*/
|
||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
||||
List<Field> fields = getFields(targetType, superclassExclude);
|
||||
Set<Field> defaults = requireDefault ? generateDefaultFields(fields) : new HashSet<Field>();
|
||||
|
||||
initialize(targetType, Object.class, fields, defaults, null, new HashMap<Class, StructureModifier>());
|
||||
}
|
||||
|
||||
private StructureModifier(StructureModifier<TField> other, Object target) {
|
||||
this(other.targetType, other.fieldType, other.data, other.converter, other.subtypeCache);
|
||||
initialize(other.targetType, other.fieldType, other.data, other.defaultFields, other.converter, other.subtypeCache);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
private StructureModifier() {
|
||||
// Consumers of this method should call "initialize"
|
||||
}
|
||||
|
||||
private void initialize(Class targetType, Class fieldType,
|
||||
List<Field> data, Set<Field> defaultFields,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
this.data = data;
|
||||
this.defaultFields = defaultFields;
|
||||
this.converter = converter;
|
||||
this.subtypeCache = subTypeCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of a field given its index.
|
||||
* @param fieldIndex - index of the field.
|
||||
* @return Value of the field.
|
||||
* @throws IllegalAccessException If we're unable to read the field due to a security limitation.
|
||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TField read(int fieldIndex) throws IllegalAccessException {
|
||||
public TField read(int fieldIndex) throws FieldAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new IllegalArgumentException("Field index must be within 0 - count");
|
||||
throw new FieldAccessException("Field index must be within 0 - count",
|
||||
new IndexOutOfBoundsException("Out of bounds"));
|
||||
if (target == null)
|
||||
throw new IllegalStateException("Cannot read from a NULL target.");
|
||||
|
||||
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
||||
|
||||
// Use the converter, if we have it
|
||||
if (converter != null)
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
try {
|
||||
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
||||
|
||||
// Use the converter, if we have it
|
||||
if (converter != null)
|
||||
return converter.getSpecific(result);
|
||||
else
|
||||
return (TField) result;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the value of a field if and ONLY IF it exists.
|
||||
* @param fieldIndex - index of the field.
|
||||
* @return Value of the field, or NULL if it doesn't exist.
|
||||
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public TField readSafely(int fieldIndex) throws FieldAccessException {
|
||||
if (fieldIndex >= 0 && fieldIndex < data.size()) {
|
||||
return read(fieldIndex);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,30 +130,50 @@ public class StructureModifier<TField> {
|
||||
* @param fieldIndex - index of the field.
|
||||
* @param value - new value of the field.
|
||||
* @return This structure modifier - for chaining.
|
||||
* @throws IllegalAccessException If we're unable to write to the field due to a security limitation.
|
||||
* @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public StructureModifier<TField> write(int fieldIndex, TField value) throws IllegalAccessException {
|
||||
public StructureModifier<TField> write(int fieldIndex, TField value) throws FieldAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
throw new IllegalArgumentException("Field index must be within 0 - count");
|
||||
throw new FieldAccessException("Field index must be within 0 - count",
|
||||
new IndexOutOfBoundsException("Out of bounds"));
|
||||
if (target == null)
|
||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||
|
||||
// Use the converter, if it exists
|
||||
Object obj = converter != null ? converter.getGeneric(value) : value;
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
|
||||
try {
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read field due to a security limitation.", e);
|
||||
}
|
||||
|
||||
// Make this method chainable
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value of a given field IF and ONLY if it exists.
|
||||
* @param fieldIndex - index of the potential field.
|
||||
* @param value - new value of the field.
|
||||
* @return This structure modifer - for chaining.
|
||||
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public StructureModifier<TField> writeSafely(int fieldIndex, TField value) throws FieldAccessException {
|
||||
if (fieldIndex >= 0 && fieldIndex < data.size()) {
|
||||
write(fieldIndex, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correctly modifies the value of a field.
|
||||
* @param fieldIndex - index of the field to modify.
|
||||
* @param select - the function that modifies the field value.
|
||||
* @return This structure modifier - for chaining.
|
||||
* @throws IllegalAccessException
|
||||
* @throws FieldAccessException The field cannot be accessed under the current security contraints.
|
||||
*/
|
||||
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws IllegalAccessException {
|
||||
public StructureModifier<TField> modify(int fieldIndex, Function<TField, TField> select) throws FieldAccessException {
|
||||
TField value = read(fieldIndex);
|
||||
return write(fieldIndex, select.apply(value));
|
||||
}
|
||||
@@ -110,6 +187,28 @@ public class StructureModifier<TField> {
|
||||
return withType(fieldType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all non-primitive fields to a more fitting default value. See {@link DefaultInstances#getDefault(Class)}.
|
||||
* @return The current structure modifier - for chaining.
|
||||
* @throws FieldAccessException If we're unable to write to the fields due to a security limitation.
|
||||
*/
|
||||
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
||||
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
|
||||
// Write a default instance to every field
|
||||
for (Field field : defaultFields) {
|
||||
try {
|
||||
FieldUtils.writeField(field, target,
|
||||
generator.getDefault(field.getType()), true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot write to field due to a security limitation.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a structure modifier that only reads and writes fields of a given type.
|
||||
* @param fieldType - the type, or supertype, of every field to modify.
|
||||
@@ -121,28 +220,48 @@ public class StructureModifier<TField> {
|
||||
|
||||
StructureModifier<T> result = subtypeCache.get(fieldType);
|
||||
|
||||
if (fieldType.equals(this.fieldType)) {
|
||||
|
||||
// We're dealing with the exact field type.
|
||||
return withConverter(converter);
|
||||
|
||||
} else if (result == null) {
|
||||
// Do we need to update the cache?
|
||||
if (result == null) {
|
||||
List<Field> filtered = new ArrayList<Field>();
|
||||
Set<Field> defaults = new HashSet<Field>();
|
||||
|
||||
for (Field field : data) {
|
||||
if (fieldType.isAssignableFrom(field.getType())) {
|
||||
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
||||
filtered.add(field);
|
||||
|
||||
if (defaultFields.contains(field))
|
||||
defaults.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache structure modifiers
|
||||
result = new StructureModifier<T>(targetType, fieldType, filtered,
|
||||
converter, new HashMap<Class, StructureModifier>());
|
||||
subtypeCache.put(fieldType, result);
|
||||
result = new StructureModifier<T>();
|
||||
result.initialize(targetType, fieldType, filtered, defaults,
|
||||
converter, new HashMap<Class, StructureModifier>());
|
||||
|
||||
if (fieldType != null)
|
||||
subtypeCache.put(fieldType, result);
|
||||
}
|
||||
|
||||
// Add the target too
|
||||
return result.withTarget(target);
|
||||
result = result.withTarget(target);
|
||||
|
||||
// And the converter, if it's needed
|
||||
if (!sameConverter(result.converter, converter)) {
|
||||
result = result.withConverter(converter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean sameConverter(EquivalentConverter<?> a, EquivalentConverter<?> b) {
|
||||
// Compare the converter types
|
||||
if (a == null)
|
||||
return b == null;
|
||||
else if (b == null)
|
||||
return a == null;
|
||||
else
|
||||
return a.getSpecificType().equals(b.getSpecificType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,8 +318,52 @@ public class StructureModifier<TField> {
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
||||
* @return List of fields.
|
||||
*/
|
||||
public List<Field> getFields() {
|
||||
return ImmutableList.copyOf(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every value stored in the fields of the current type.
|
||||
* @return Every field value.
|
||||
* @throws FieldAccessException Unable to access one or all of the fields
|
||||
*/
|
||||
public List<TField> getValues() throws FieldAccessException {
|
||||
List<TField> values = new ArrayList<TField>();
|
||||
|
||||
for (int i = 0; i < size(); i++)
|
||||
values.add(read(i));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
// Used to generate plausible default values
|
||||
private static Set<Field> generateDefaultFields(List<Field> fields) {
|
||||
|
||||
Set<Field> requireDefaults = new HashSet<Field>();
|
||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||
|
||||
for (Field field : fields) {
|
||||
Class<?> type = field.getType();
|
||||
|
||||
// First, ignore primitive fields
|
||||
if (!PrimitiveUtils.isPrimitive(type)) {
|
||||
// Next, see if we actually can generate a default value
|
||||
if (generator.getDefault(type) != null) {
|
||||
// If so, require it
|
||||
requireDefaults.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requireDefaults;
|
||||
}
|
||||
|
||||
// Used to filter out irrelevant fields
|
||||
private static List<Field> getFields(Class type) {
|
||||
private static List<Field> getFields(Class type, Class superclassExclude) {
|
||||
List<Field> result = new ArrayList<Field>();
|
||||
|
||||
// Retrieve every private and public field
|
||||
@@ -208,7 +371,10 @@ public class StructureModifier<TField> {
|
||||
int mod = field.getModifiers();
|
||||
|
||||
// Ignore static, final and "abstract packet" fields
|
||||
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && !field.getDeclaringClass().equals(Packet.class)) {
|
||||
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
|
||||
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
|
||||
)) {
|
||||
|
||||
result.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a field that will revert to its original state when this class is garbaged collected.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.comphenix.protocol.reflect.instances;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Provides simple constructors for collection interfaces.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class CollectionGenerator implements InstanceProvider {
|
||||
|
||||
/**
|
||||
* Shared instance of this generator.
|
||||
*/
|
||||
public static CollectionGenerator INSTANCE = new CollectionGenerator();
|
||||
|
||||
@Override
|
||||
public Object create(@Nullable Class<?> type) {
|
||||
// Standard collection types
|
||||
if (type.isInterface()) {
|
||||
if (type.equals(Collection.class) || type.equals(List.class))
|
||||
return new ArrayList<Object>();
|
||||
else if (type.equals(Set.class))
|
||||
return new HashSet<Object>();
|
||||
else if (type.equals(Map.class))
|
||||
return new HashMap<Object, Object>();
|
||||
else if (type.equals(SortedSet.class))
|
||||
return new TreeSet<Object>();
|
||||
else if (type.equals(SortedMap.class))
|
||||
return new TreeMap<Object, Object>();
|
||||
else if (type.equals(Queue.class))
|
||||
return new LinkedList<Object>();
|
||||
}
|
||||
|
||||
// Cannot provide an instance
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.reflect.instances;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Used to construct default instances of any type.
|
||||
* @author Kristian
|
||||
*
|
||||
*/
|
||||
public class DefaultInstances {
|
||||
|
||||
/**
|
||||
* Standard default instance provider.
|
||||
*/
|
||||
public static DefaultInstances DEFAULT = DefaultInstances.fromArray(
|
||||
PrimitiveGenerator.INSTANCE, CollectionGenerator.INSTANCE);
|
||||
|
||||
/**
|
||||
* The maximum height of the hierachy of creates types. Used to prevent cycles.
|
||||
*/
|
||||
private final static int MAXIMUM_RECURSION = 20;
|
||||
|
||||
/**
|
||||
* Ordered list of instance provider, from highest priority to lowest.
|
||||
*/
|
||||
private ImmutableList<InstanceProvider> registered;
|
||||
|
||||
/**
|
||||
* Construct a default instance generator using the given instance providers.
|
||||
* @param registered - list of instance providers.
|
||||
*/
|
||||
public DefaultInstances(ImmutableList<InstanceProvider> registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a default instance generator using the given instance providers.
|
||||
* @param instaceProviders - array of instance providers.
|
||||
*/
|
||||
public DefaultInstances(InstanceProvider... instaceProviders) {
|
||||
this(ImmutableList.copyOf(instaceProviders));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a default instance generator using the given instance providers.
|
||||
* @param instaceProviders - array of instance providers.
|
||||
* @return An default instance generator.
|
||||
*/
|
||||
public static DefaultInstances fromArray(InstanceProvider... instaceProviders) {
|
||||
return new DefaultInstances(ImmutableList.copyOf(instaceProviders));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a immutable list of every default object providers that generates instances.
|
||||
* @return Table of instance providers.
|
||||
*/
|
||||
public ImmutableList<InstanceProvider> getRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a default instance or value that is assignable to this type.
|
||||
* <p>
|
||||
* This includes, but isn't limited too:
|
||||
* <ul>
|
||||
* <li>Primitive types. Returns either zero or null.</li>
|
||||
* <li>Primitive wrappers.</li>
|
||||
* <li>String types. Returns an empty string.</li>
|
||||
* <li>Arrays. Returns a zero-length array of the same type.</li>
|
||||
* <li>Enums. Returns the first declared element.</li>
|
||||
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
|
||||
* <li>Any type with a public constructor that has parameters with defaults.</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
* @param type - the type to construct a default value.
|
||||
* @return A default value/instance, or NULL if not possible.
|
||||
*/
|
||||
public <T> T getDefault(Class<T> type) {
|
||||
return getDefaultInternal(type, registered, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a default instance or value that is assignable to this type.
|
||||
* <p>
|
||||
* This includes, but isn't limited too:
|
||||
* <ul>
|
||||
* <li>Primitive types. Returns either zero or null.</li>
|
||||
* <li>Primitive wrappers.</li>
|
||||
* <li>String types. Returns an empty string.</li>
|
||||
* <li>Arrays. Returns a zero-length array of the same type.</li>
|
||||
* <li>Enums. Returns the first declared element.</li>
|
||||
* <li>Collection interfaces, such as List and Set. Returns the most appropriate empty container.</li>
|
||||
* <li>Any type with a public constructor that has parameters with defaults.</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
* @param type - the type to construct a default value.
|
||||
* @param providers - instance providers used during the
|
||||
* @return A default value/instance, or NULL if not possible.
|
||||
*/
|
||||
public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) {
|
||||
return getDefaultInternal(type, providers, 0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
|
||||
|
||||
// Guard against recursion
|
||||
if (recursionLevel > MAXIMUM_RECURSION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (InstanceProvider generator : providers) {
|
||||
Object value = generator.create(type);
|
||||
|
||||
if (value != null)
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
Constructor<T> minimum = null;
|
||||
int lastCount = Integer.MAX_VALUE;
|
||||
|
||||
// Find the constructor with the fewest parameters
|
||||
for (Constructor<?> candidate : type.getConstructors()) {
|
||||
Class<?>[] types = candidate.getParameterTypes();
|
||||
|
||||
// Note that we don't allow recursive types - that is, types that
|
||||
// require itself in the constructor.
|
||||
if (types.length < lastCount) {
|
||||
if (!contains(types, type)) {
|
||||
minimum = (Constructor<T>) candidate;
|
||||
lastCount = types.length;
|
||||
|
||||
// Don't loop again if we've already found the best possible constructor
|
||||
if (lastCount == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the type with this constructor using default values. This might fail, though.
|
||||
try {
|
||||
if (minimum != null) {
|
||||
Object[] params = new Object[lastCount];
|
||||
Class<?>[] types = minimum.getParameterTypes();
|
||||
|
||||
// Fill out
|
||||
for (int i = 0; i < lastCount; i++) {
|
||||
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
|
||||
}
|
||||
|
||||
return createInstance(type, minimum, types, params);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// Nope, we couldn't create this type
|
||||
}
|
||||
|
||||
// No suitable default value could be found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the default instance provider to create a class from a given constructor.
|
||||
* The default method uses reflection.
|
||||
* @param type - the type to create.
|
||||
* @param constructor - the constructor to use.
|
||||
* @param types - type of each parameter in order.
|
||||
* @param params - value of each parameter in order.
|
||||
* @return The constructed instance.
|
||||
*/
|
||||
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
|
||||
try {
|
||||
return (T) constructor.newInstance(params);
|
||||
} catch (Exception e) {
|
||||
// Cannot create it
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// We avoid Apache's utility methods to stay backwards compatible
|
||||
protected <T> boolean contains(T[] elements, T elementToFind) {
|
||||
// Search for the given element in the array
|
||||
for (T element : elements) {
|
||||
if (Objects.equal(elementToFind, element))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.comphenix.protocol.reflect.instances;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a type generator for specific types.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface InstanceProvider {
|
||||
|
||||
/**
|
||||
* Create an instance given a type, if possible.
|
||||
* @param type - type to create.
|
||||
* @return The instance, or NULL if the type cannot be created.
|
||||
*/
|
||||
public abstract Object create(@Nullable Class<?> type);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.comphenix.protocol.reflect.instances;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||
import com.google.common.base.Defaults;
|
||||
|
||||
/**
|
||||
* Provides constructors for primtive types, wrappers, arrays and strings.
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PrimitiveGenerator implements InstanceProvider {
|
||||
|
||||
/**
|
||||
* Default value for Strings.
|
||||
*/
|
||||
public static final String STRING_DEFAULT = "";
|
||||
|
||||
/**
|
||||
* Shared instance of this generator.
|
||||
*/
|
||||
public static PrimitiveGenerator INSTANCE = new PrimitiveGenerator(STRING_DEFAULT);
|
||||
|
||||
// Our default string value
|
||||
private String stringDefault;
|
||||
|
||||
public PrimitiveGenerator(String stringDefault) {
|
||||
this.stringDefault = stringDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the string default.
|
||||
* @return Default instance of a string.
|
||||
*/
|
||||
public String getStringDefault() {
|
||||
return stringDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object create(@Nullable Class<?> type) {
|
||||
|
||||
if (PrimitiveUtils.isPrimitive(type)) {
|
||||
return Defaults.defaultValue(type);
|
||||
} else if (PrimitiveUtils.isWrapperType(type)) {
|
||||
return Defaults.defaultValue(PrimitiveUtils.unwrap(type));
|
||||
} else if (type.isArray()) {
|
||||
Class<?> arrayType = type.getComponentType();
|
||||
return Array.newInstance(arrayType, 0);
|
||||
} else if (type.isEnum()) {
|
||||
Object[] values = type.getEnumConstants();
|
||||
if (values != null && values.length > 0)
|
||||
return values[0];
|
||||
} else if (type.equals(String.class)) {
|
||||
return stringDefault;
|
||||
}
|
||||
|
||||
// Cannot handle this type
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: ProtocolLib
|
||||
version: 1.0.0
|
||||
version: 1.1.0
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
public class SortedCopyOnWriteArrayTest {
|
||||
|
||||
@Test
|
||||
public void testInsertion() {
|
||||
|
||||
final int MAX_NUMBER = 50;
|
||||
|
||||
SortedCopyOnWriteArray<Integer> test = new SortedCopyOnWriteArray<Integer>();
|
||||
|
||||
// Generate some numbers
|
||||
List<Integer> numbers = new ArrayList<Integer>();
|
||||
|
||||
for (int i = 0; i < MAX_NUMBER; i++) {
|
||||
numbers.add(i);
|
||||
}
|
||||
|
||||
// Random insertion to test it all
|
||||
Collections.shuffle(numbers);
|
||||
|
||||
// O(n^2) of course, so don't use too large numbers
|
||||
for (int i = 0; i < MAX_NUMBER; i++) {
|
||||
test.add(numbers.get(i));
|
||||
}
|
||||
|
||||
// Check that everything is correct
|
||||
for (int i = 0; i < MAX_NUMBER; i++) {
|
||||
assertEquals((Integer) i, test.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrder() {
|
||||
PriorityStuff a = new PriorityStuff(ListenerPriority.HIGH, 1);
|
||||
PriorityStuff b = new PriorityStuff(ListenerPriority.NORMAL, 2);
|
||||
PriorityStuff c = new PriorityStuff(ListenerPriority.NORMAL, 3);
|
||||
SortedCopyOnWriteArray<PriorityStuff> test = new SortedCopyOnWriteArray<PriorityStuff>();
|
||||
|
||||
test.add(a);
|
||||
test.add(b);
|
||||
test.add(c);
|
||||
|
||||
// Make sure the normal's are in the right order
|
||||
assertEquals(2, test.get(0).id);
|
||||
assertEquals(3, test.get(1).id);
|
||||
}
|
||||
|
||||
private class PriorityStuff implements Comparable<PriorityStuff> {
|
||||
public ListenerPriority priority;
|
||||
public int id;
|
||||
|
||||
public PriorityStuff(ListenerPriority priority, int id) {
|
||||
this.priority = priority;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PriorityStuff other) {
|
||||
// This ensures that lower priority listeners are executed first
|
||||
return Ints.compare(priority.getSlot(),
|
||||
other.priority.getSlot());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,17 @@ import net.minecraft.server.Packet103SetSlot;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebeaninternal.server.cluster.Packet;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
public class StructureModifierTest {
|
||||
|
||||
@Test
|
||||
public void test() throws IllegalAccessException {
|
||||
public void test() throws FieldAccessException {
|
||||
|
||||
Packet103SetSlot move = new Packet103SetSlot();
|
||||
StructureModifier<Object> modifier = new StructureModifier<Object>(Packet103SetSlot.class);
|
||||
StructureModifier<Object> modifier = new StructureModifier<Object>(
|
||||
Packet103SetSlot.class, Packet.class, true);
|
||||
|
||||
move.a = 1;
|
||||
int value = (Integer) modifier.withTarget(move).withType(int.class).read(0);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
ProtocolLib
|
||||
===========
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modify Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](http://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
Critically, different plugins that use this approach may _hook_ into the same classes,
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
lead to more subtle bugs.
|
||||
|
||||
|
||||
### Resources
|
||||
|
||||
* [JavaDoc](http://aadnk.github.com/ProtocolLib/Javadoc/)
|
||||
|
||||
|
||||
A new API
|
||||
---------
|
||||
|
||||
__ProtocolLib__ attempts to solve this problem by providing a event API, much like Bukkit,
|
||||
that allow plugins to monitor, modify or cancel packets sent and received. But more importantly,
|
||||
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
|
||||
You no longer have to reference CraftBukkit!
|
||||
|
||||
|
||||
### Using ProtocolLib
|
||||
|
||||
To use the library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
|
||||
as a dependency (or soft-dependency, if you can live without it) to your plugin.yml file:
|
||||
|
||||
depends: [ProtocolLib]
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() and you're good to go.
|
||||
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
|
||||
To listen for packets sent by the server to a client, add a server-side listener:
|
||||
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(
|
||||
new PacketAdapter(this, ConnectionSide.SERVER_SIDE, ListenerPriority.NORMAL, 0x3E) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Item packets
|
||||
switch (event.getPacketID()) {
|
||||
case 0x3E: // Sound effect
|
||||
event.setCancelled(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
It's also possible to read and modify the content of these packets. For instance, you can create a global
|
||||
censor by listening for Packet3Chat events:
|
||||
|
||||
// Censor
|
||||
protocolManager.addPacketListener(
|
||||
new PacketAdapter(this, ConnectionSide.CLIENT_SIDE, ListenerPriority.NORMAL, 0x03) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (event.getPacketID() == 0x03) {
|
||||
try {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getSpecificModifier(String.class).read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't access field.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
### Sending packets
|
||||
|
||||
Normally, you might have to do something ugly like the following:
|
||||
|
||||
Packet60Explosion fakeExplosion = new Packet60Explosion();
|
||||
|
||||
fakeExplosion.a = player.getLocation().getX();
|
||||
fakeExplosion.b = player.getLocation().getY();
|
||||
fakeExplosion.c = player.getLocation().getZ();
|
||||
fakeExplosion.d = 3.0F;
|
||||
fakeExplosion.e = new ArrayList<Object>();
|
||||
|
||||
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
|
||||
|
||||
But with ProtocolLib, you can turn that into something more manageable. Notice that
|
||||
you don't have to create an ArrayList this version:
|
||||
|
||||
PacketContainer fakeExplosion = protocolManager.createPacket(60);
|
||||
|
||||
fakeExplosion.getSpecificModifier(double.class).
|
||||
write(0, player.getLocation().getX()).
|
||||
write(1, player.getLocation().getY()).
|
||||
write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getSpecificModifier(float.class).
|
||||
write(0, 3.0F);
|
||||
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
|
||||
|
||||
Compatiblity
|
||||
------------
|
||||
|
||||
One of the main goals of this project was to achive maximum compatibility with craftBukkit. And the end
|
||||
result is quite flexible - in tests I successfully ran an unmodified ProtocolLib on CraftBukkit 1.8.0, and
|
||||
it should be resiliant against future changes. It's likely that I won't have to update ProtocolLib for
|
||||
anything but bug and performance fixes.
|
||||
|
||||
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
|
||||
every field, method and class is deduced by looking at field types, package names or parameter
|
||||
types. It's remarkably consistent across different versions.
|
||||
|
||||
|
||||
### Incompatiblity
|
||||
|
||||
The following plugins (to be expanded) are not compatible with ProtocolLib:
|
||||
|
||||
* TagAPI
|
||||
Reference in new issue
Block a user