/*
* This file is part of HoloAPI.
*
* HoloAPI 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 3 of the License, or
* (at your option) any later version.
*
* HoloAPI 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 HoloAPI. If not, see <http://www.gnu.org/licenses/>.
*/
package com.dsh105.holoapi.protocol.netty;
import com.captainbern.minecraft.conversion.BukkitUnwrapper;
import com.captainbern.minecraft.protocol.PacketType;
import com.captainbern.minecraft.reflection.MinecraftFields;
import com.captainbern.minecraft.reflection.MinecraftReflection;
import com.captainbern.minecraft.wrapper.WrappedPacket;
import com.captainbern.reflection.Reflection;
import com.captainbern.reflection.accessor.FieldAccessor;
import com.dsh105.holoapi.HoloAPI;
import com.dsh105.holoapi.protocol.InjectionManager;
import com.dsh105.holoapi.protocol.Injector;
import com.google.common.base.Preconditions;
import net.minecraft.util.io.netty.channel.Channel;
import net.minecraft.util.io.netty.channel.ChannelDuplexHandler;
import net.minecraft.util.io.netty.channel.ChannelHandlerContext;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.concurrent.Callable;
import java.util.NoSuchElementException;
public class PlayerInjector extends ChannelDuplexHandler implements Injector {
// "Cache" the field to speed up the whole process.
private static FieldAccessor<Channel> CHANNEL_FIELD;
protected Player player;
protected volatile InjectionManager injectionManager;
protected Object nmsHandle;
protected Object playerConnection;
protected Object networkManager;
protected Channel channel;
private boolean isInjected = false;
private boolean isClosed = false;
public PlayerInjector(Player player, InjectionManager injectionManager) {
Preconditions.checkNotNull(player);
Preconditions.checkNotNull(injectionManager);
this.injectionManager = injectionManager;
this.initialize(player);
}
@Override
public void inject() {
synchronized (this.networkManager) {
if (isInjected())
throw new IllegalStateException("Cannot inject twice!");
if (this.channel == null)
throw new IllegalStateException("Channel is NULL! Perhaps we failed to find it?");
try {
this.channel.pipeline().addBefore("packet_handler", "holoapi_packet_handler", this);
} catch (NoSuchElementException e){
//ignore exception, we know about it, so remove spam ;)
}
this.isInjected = true;
}
}
@Override
public void close() {
if (!this.isClosed) {
this.isClosed = true;
if (this.isInjected) {
// Avoid dead-locks, thanks Comphenix
getChannel().eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
getChannel().pipeline().remove(PlayerInjector.this);
return null;
}
});
this.isInjected = false;
}
}
}
@Override
public boolean isInjected() {
return this.isInjected;
}
@Override
public boolean isClosed() {
return this.isClosed;
}
@Override
public void sendPacket(Object packet) {
if (this.isClosed())
throw new IllegalStateException("The PlayerInjector is closed!");
this.getChannel().pipeline().writeAndFlush(packet);
}
@Override
public void receivePacket(Object packet) {
if (this.isClosed())
throw new IllegalStateException("The PlayerInjector is closed!");
this.getChannel().pipeline().context("encoder").fireChannelRead(packet);
}
@Override
public Player getPlayer() {
return this.player;
}
@Override
public void setPlayer(Player player) {
this.initialize(player);
}
private void initialize(Player player) {
if (player == null)
throw new IllegalArgumentException("Player can't be NULL!"); // This should never happen, but in case it does...
try {
this.player = player;
this.nmsHandle = BukkitUnwrapper.getInstance().unwrap(player);
Reflection reflection = new Reflection();
this.playerConnection = MinecraftFields.getPlayerConnection(player);
this.networkManager = MinecraftFields.getNetworkManager(player);
if (CHANNEL_FIELD == null) {
try {
CHANNEL_FIELD = reflection.reflect(MinecraftReflection.getNetworkManagerClass()).getSafeFieldByType(Channel.class).getAccessor();
} catch (Exception e) {
// Oops
throw new RuntimeException("Failed to get the Channel accessor!", e);
}
}
this.channel = CHANNEL_FIELD.get(this.networkManager);
} catch (Exception e) {
// Oops
throw new RuntimeException("Failed to initialize the PlayerInjector for: " + player, e);
}
}
private Channel getChannel() {
if (this.channel == null)
throw new IllegalStateException("The Channel is NULL!");
return this.channel;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Handle the packet
final WrappedPacket packet = new WrappedPacket(msg);
if (packet.getPacketType().equals(PacketType.Play.Client.USE_ENTITY))
Bukkit.getScheduler().scheduleSyncDelayedTask(HoloAPI.getCore(), new Runnable() {
@Override
public void run() {
PlayerInjector.this.injectionManager.handlePacket(packet, PlayerInjector.this);
}
});
super.channelRead(ctx, msg);
}
}