/* * 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); } }