300 lines
10 KiB
Java
300 lines
10 KiB
Java
/**
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* 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.netty.channel;
|
|
|
|
import com.comphenix.protocol.error.ErrorReporter;
|
|
import com.comphenix.protocol.injector.netty.ChannelListener;
|
|
import com.comphenix.protocol.injector.netty.Injector;
|
|
import com.comphenix.protocol.injector.temporary.MinimalInjector;
|
|
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
|
import com.comphenix.protocol.utility.MinecraftFields;
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
|
import com.google.common.collect.MapMaker;
|
|
import io.netty.channel.Channel;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import javax.annotation.Nonnull;
|
|
import org.bukkit.Server;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.plugin.Plugin;
|
|
|
|
/**
|
|
* Represents an injector factory.
|
|
* <p>
|
|
* Note that the factory will return {@link EmptyInjector} when the factory is closed.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class InjectionFactory {
|
|
|
|
// This should work as long as the injectors are, uh, injected
|
|
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
|
|
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
|
|
|
|
// bukkit stuff
|
|
private final Plugin plugin;
|
|
private final Server server;
|
|
|
|
// protocol lib stuff
|
|
private final ErrorReporter errorReporter;
|
|
|
|
// state of the factory
|
|
private boolean closed;
|
|
|
|
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
|
|
this.plugin = plugin;
|
|
this.server = server;
|
|
this.errorReporter = errorReporter;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the main plugin associated with this injection factory.
|
|
*
|
|
* @return The main plugin.
|
|
*/
|
|
public Plugin getPlugin() {
|
|
return this.plugin;
|
|
}
|
|
|
|
/**
|
|
* Construct or retrieve a channel injector from an existing Bukkit player.
|
|
*
|
|
* @param player - the existing Bukkit player.
|
|
* @param listener - the listener.
|
|
* @return A new injector, an existing injector associated with this player, or a closed injector.
|
|
*/
|
|
@Nonnull
|
|
public Injector fromPlayer(Player player, ChannelListener listener) {
|
|
if (this.closed) {
|
|
return new EmptyInjector(player);
|
|
}
|
|
|
|
// try to get the injector using the player reference first
|
|
Injector injector = this.playerLookup.get(player);
|
|
if (injector == null) {
|
|
injector = this.getTemporaryInjector(player);
|
|
}
|
|
|
|
// check if we found an injector
|
|
if (injector != null && !injector.isClosed()) {
|
|
return injector;
|
|
}
|
|
|
|
// check if a network manager is present, if not maybe we cached the player temporarily
|
|
Object networkManager = MinecraftFields.getNetworkManager(player);
|
|
if (networkManager == null) {
|
|
return this.fromName(player.getName(), player);
|
|
}
|
|
|
|
// get the channel of the player and check if we already hooked into it
|
|
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
|
|
injector = NettyChannelInjector.findInjector(channel);
|
|
|
|
if (injector != null) {
|
|
// check if the new player is not the old one, this saves us a bit when many calls to the method are made
|
|
if (injector.getPlayer() != player || !this.playerLookup.containsKey(player)) {
|
|
this.playerLookup.remove(injector.getPlayer());
|
|
this.cacheInjector(player, injector);
|
|
// re-set the player of the injection
|
|
injector.setPlayer(player);
|
|
}
|
|
} else {
|
|
// construct a new injector as it seems like we have none yet
|
|
injector = new NettyChannelInjector(
|
|
player,
|
|
this.server,
|
|
networkManager,
|
|
channel,
|
|
listener,
|
|
this,
|
|
this.errorReporter);
|
|
this.cacheInjector(player, injector);
|
|
}
|
|
|
|
// definitely not null
|
|
return injector;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a cached injector from a name.
|
|
* <p>
|
|
* The injector may be NULL if the plugin has been reloaded during a player login.
|
|
*
|
|
* @param name - the name.
|
|
* @param player - the player.
|
|
* @return The cached injector, or a closed injector if it could not be found.
|
|
*/
|
|
public Injector fromName(String name, Player player) {
|
|
if (this.closed) {
|
|
return new EmptyInjector(player);
|
|
}
|
|
|
|
// check if we have a player with that name cached
|
|
Injector injector = this.nameLookup.get(name);
|
|
if (injector != null) {
|
|
injector.setPlayer(player);
|
|
return injector;
|
|
}
|
|
|
|
return new EmptyInjector(player);
|
|
}
|
|
|
|
/**
|
|
* Construct a new channel injector for the given channel.
|
|
*
|
|
* @param channel - the channel.
|
|
* @param listener - the listener.
|
|
* @param playerFactory - a temporary player creator.
|
|
* @return The channel injector, or a closed injector.
|
|
*/
|
|
@Nonnull
|
|
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
|
|
if (this.closed) {
|
|
return EmptyInjector.WITHOUT_PLAYER;
|
|
}
|
|
|
|
Object netManager = this.findNetworkManager(channel);
|
|
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
|
|
|
|
// Use the channel field from an already found network manager to prevent our channel injector given in
|
|
// this method from overwriting initialized custom channel implementation in some spigot forks
|
|
Channel wrappedChannel = FuzzyReflection.getFieldValue(netManager, Channel.class, true);
|
|
if (wrappedChannel == null) {
|
|
// Use the channel straight from ChannelHandlerContext as fallback if the field has null value
|
|
wrappedChannel = channel;
|
|
}
|
|
|
|
NettyChannelInjector injector = new NettyChannelInjector(
|
|
temporaryPlayer,
|
|
this.server,
|
|
netManager,
|
|
wrappedChannel,
|
|
listener,
|
|
this,
|
|
this.errorReporter);
|
|
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
|
|
|
|
// Initialize temporary player
|
|
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
|
|
return injector;
|
|
}
|
|
|
|
/**
|
|
* Invalidate a cached injector.
|
|
*
|
|
* @param player - the associated player.
|
|
* @return The cached injector, or NULL if nothing was cached.
|
|
*/
|
|
public Injector invalidate(Player player, String name) {
|
|
Injector injector = null;
|
|
|
|
// try the name first, more unsafe but works 99% of the time
|
|
if (name != null) {
|
|
injector = this.nameLookup.remove(name);
|
|
}
|
|
|
|
// if we have a player then use that as the safe removal way
|
|
if (player != null) {
|
|
injector = this.playerLookup.remove(player);
|
|
}
|
|
|
|
return injector;
|
|
}
|
|
|
|
/**
|
|
* Cache an injector by player.
|
|
*
|
|
* @param player - the player.
|
|
* @param injector - the injector to cache.
|
|
* @return The previously cached injector.
|
|
*/
|
|
public Injector cacheInjector(Player player, Injector injector) {
|
|
this.nameLookup.put(player.getName(), injector);
|
|
return this.playerLookup.put(player, injector);
|
|
}
|
|
|
|
/**
|
|
* Cache an injector by name alone.
|
|
*
|
|
* @param name - the name to lookup.
|
|
* @param injector - the injector.
|
|
* @return The cached injector.
|
|
*/
|
|
public Injector cacheInjector(String name, Injector injector) {
|
|
return this.nameLookup.put(name, injector);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the associated channel injector.
|
|
*
|
|
* @param player - the temporary player, or normal Bukkit player.
|
|
* @return The associated injector, or NULL if this is a Bukkit player.
|
|
*/
|
|
private NettyChannelInjector getTemporaryInjector(Player player) {
|
|
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
|
if (injector instanceof NettyChannelMinimalInjector) {
|
|
return ((NettyChannelMinimalInjector) injector).getInjector();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find the network manager in a channel's pipeline.
|
|
*
|
|
* @param channel - the channel.
|
|
* @return The network manager.
|
|
*/
|
|
private Object findNetworkManager(Channel channel) {
|
|
// Find the network manager
|
|
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
|
|
MinecraftReflection.getNetworkManagerClass());
|
|
if (networkManager != null) {
|
|
return networkManager;
|
|
}
|
|
|
|
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
|
|
}
|
|
|
|
/**
|
|
* Determine if the factory is closed.
|
|
* <p>
|
|
* If it is, all new injectors will be closed by default.
|
|
*
|
|
* @return TRUE if it is closed, FALSE otherwise.
|
|
*/
|
|
public boolean isClosed() {
|
|
return this.closed;
|
|
}
|
|
|
|
/**
|
|
* Close all injectors created by this factory, and cease the creation of new injections.
|
|
*/
|
|
public void close() {
|
|
if (!this.closed) {
|
|
this.closed = true;
|
|
|
|
// Close everything
|
|
for (Injector injector : this.playerLookup.values()) {
|
|
injector.close();
|
|
}
|
|
|
|
for (Injector injector : this.nameLookup.values()) {
|
|
injector.close();
|
|
}
|
|
}
|
|
}
|
|
}
|