Provides read and write access to the Minecraft protocol with Bukkit.
Go to file
Kristian S. Stangeland 9b349299a0 Significantly reduce the possibility of a race condition.
The vanilla server bootstrap (ServerConnectionChannel) is executed 
asynchronously when a new channel object has been registered in an event
loop, much before it is ready to accept a new client connection. It is 
here the whole channel handler pipeline is set up, along with a 
NetworkManager responsible for reading and writing packets. 

The trouble starts when the bootstrap class adds the created 
NetworkManager to a list (f) of managers in  ServerConnection. This list
is regularly inspected by the main thread (in order to process packets 
on the main thread) and includes a clean up procedure 
(ServerConnection#61) in case it detects a disconnected network manager.

Unfortunately, the network manager IS considered disconnected for a 
moment when its added to the list, so the main thread MAY end up 
getting to the network manager before Netty has connected the channel.
This is still very rare under normal circumstances, but because 
ProtocolLib does a lot of initial processing in the channel handler, the
asynchronous thread gets hold up for a long while the first time a 
player connects to the server, allowing the main thread sufficient time 
to catch up and evict the network manager.

The partial solution here is to synchronize on the network manager list,
stopping the main thread from processing network managers when we are 
preparing our new connection. 

But I suspect the best solution would be to correct the root of the 
problem, and correct the race condition in the server itself. This 
could be done by only adding the network manager when the channel is
active
(see ChannelInboundHandler.channelActive).
2013-12-14 04:05:12 +01:00
ItemDisguise Merge branch 'master' into gh-pages 2013-05-14 01:24:47 +02:00
ProtocolLib Significantly reduce the possibility of a race condition. 2013-12-14 04:05:12 +01:00
.gitignore Stop tracking the dependency reduced POM. 2013-01-10 00:51:52 +01:00
License.txt Adding GPL v2 license information to every file. 2012-10-10 22:18:11 +02:00
Readme.md Update Readme.md to include Java and YAML syntax highlighting 2013-12-12 12:02:57 -05:00

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, 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

Building

You can compile this project yourself by using the latest version of Maven.

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]

Future versions will be available in a public Maven repository, possibly on Maven central. But it will always be possible to reference ProtocolLib manually.

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 achieve 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: