mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-05 02:10:14 +01:00
Git decided to ignore a good chunk of the files
This commit is contained in:
parent
325b91fb1f
commit
49fb3339be
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.guava;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Guava {
|
||||||
|
private static GuavaCompat compat;
|
||||||
|
|
||||||
|
private static GuavaCompat getCompat() {
|
||||||
|
if (compat == null) {
|
||||||
|
try {
|
||||||
|
Range.closed(1, 2);
|
||||||
|
return compat = new Guava17();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
try {
|
||||||
|
ProtocolLibrary.log("Falling back to Guava 10 compat");
|
||||||
|
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.guava.Guava10");
|
||||||
|
return compat = (GuavaCompat) clazz.newInstance();
|
||||||
|
} catch (Throwable ex1) {
|
||||||
|
ProtocolLibrary.getStaticLogger().log(Level.SEVERE, "Failed to create Guava 10 compat:", ex1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
|
||||||
|
return getCompat().closedRange(lower, upper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <C extends Comparable<C>> Range<C> singleton(C singleton) {
|
||||||
|
return getCompat().singletonRange(singleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Integer> toSet(Range<Integer> range) {
|
||||||
|
return getCompat().toSet(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataInputStream addHeader(DataInputStream input, PacketType type) {
|
||||||
|
return getCompat().addHeader(input, type);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.guava;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.google.common.collect.ContiguousSet;
|
||||||
|
import com.google.common.collect.DiscreteDomain;
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Guava17 implements GuavaCompat {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <C extends Comparable<C>> Range<C> closedRange(C lower, C upper) {
|
||||||
|
return Range.closed(lower, upper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <C extends Comparable<C>> Range<C> singletonRange(C singleton) {
|
||||||
|
return Range.singleton(singleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Integer> toSet(Range<Integer> range) {
|
||||||
|
return ContiguousSet.create(range, DiscreteDomain.integers());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataInputStream addHeader(final DataInputStream input, final PacketType type) {
|
||||||
|
ByteSource header = new ByteSource() {
|
||||||
|
@Override
|
||||||
|
public InputStream openStream() throws IOException {
|
||||||
|
byte[] data = new byte[] { (byte) type.getLegacyId() };
|
||||||
|
return new ByteArrayInputStream(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ByteSource data = new ByteSource() {
|
||||||
|
@Override
|
||||||
|
public InputStream openStream() throws IOException {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine them into a single stream
|
||||||
|
try {
|
||||||
|
return new DataInputStream(ByteSource.concat(header, data).openStream());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot add header.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.guava;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface GuavaCompat {
|
||||||
|
|
||||||
|
<C extends Comparable<C>> Range<C> closedRange(C lower, C upper);
|
||||||
|
|
||||||
|
<C extends Comparable<C>> Range<C> singletonRange(C singleton);
|
||||||
|
|
||||||
|
Set<Integer> toSet(Range<Integer> range);
|
||||||
|
|
||||||
|
DataInputStream addHeader(DataInputStream input, PacketType type);
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.injector.netty.Injector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface ChannelInjector extends Injector {
|
||||||
|
|
||||||
|
WrappedChannel getChannel();
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.comphenix.protocol.compat.netty.independent.IndependentNetty;
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Netty {
|
||||||
|
private static NettyCompat compat;
|
||||||
|
|
||||||
|
private static NettyCompat getCompat() {
|
||||||
|
if (compat == null) {
|
||||||
|
try {
|
||||||
|
Class.forName("io.netty.buffer.ByteBuf");
|
||||||
|
return compat = new IndependentNetty();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
try {
|
||||||
|
ProtocolLibrary.log("Falling back to legacy Netty compat");
|
||||||
|
Class<?> clazz = Class.forName("com.comphenix.protocol.compat.netty.shaded.ShadedNetty");
|
||||||
|
return compat = (NettyCompat) clazz.newInstance();
|
||||||
|
} catch (Throwable ex1) {
|
||||||
|
ProtocolLibrary.getStaticLogger().log(Level.SEVERE, "Failed to create legacy netty compat:", ex1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WrappedByteBuf createPacketBuffer() {
|
||||||
|
return getCompat().createPacketBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WrappedByteBuf allocateUnpooled() {
|
||||||
|
return getCompat().allocateUnpooled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> getGenericFutureListenerArray() {
|
||||||
|
return getCompat().getGenericFutureListenerArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?> getChannelHandlerContext() {
|
||||||
|
return getCompat().getChannelHandlerContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toEncodedText(CompressedImage image) {
|
||||||
|
return getCompat().toEncodedText(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WrappedByteBuf decode(byte[] encoded) {
|
||||||
|
return getCompat().decode(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProtocolInjector getProtocolInjector(Plugin library, PacketFilterManager packetFilterManager, ErrorReporter reporter) {
|
||||||
|
return getCompat().getProtocolInjector(library, packetFilterManager, reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WrappedByteBuf packetReader(DataInputStream input) {
|
||||||
|
return getCompat().packetReader(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WrappedByteBuf packetWriter(DataOutputStream output) {
|
||||||
|
return getCompat().packetWriter(output);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface NettyCompat {
|
||||||
|
|
||||||
|
WrappedByteBuf createPacketBuffer();
|
||||||
|
|
||||||
|
WrappedByteBuf allocateUnpooled();
|
||||||
|
|
||||||
|
Class<?> getGenericFutureListenerArray();
|
||||||
|
|
||||||
|
Class<?> getChannelHandlerContext();
|
||||||
|
|
||||||
|
String toEncodedText(CompressedImage image);
|
||||||
|
|
||||||
|
WrappedByteBuf decode(byte[] encoded);
|
||||||
|
|
||||||
|
ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter);
|
||||||
|
|
||||||
|
WrappedByteBuf packetReader(DataInputStream input);
|
||||||
|
|
||||||
|
WrappedByteBuf packetWriter(DataOutputStream output);
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||||
|
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||||
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface ProtocolInjector extends ChannelListener {
|
||||||
|
|
||||||
|
PlayerInjectionHandler getPlayerInjector();
|
||||||
|
|
||||||
|
PacketInjector getPacketInjector();
|
||||||
|
|
||||||
|
void setDebug(boolean debug);
|
||||||
|
|
||||||
|
void inject();
|
||||||
|
|
||||||
|
void close();
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface WrappedByteBuf {
|
||||||
|
|
||||||
|
void writeBytes(ObjectInputStream input, int id) throws IOException;
|
||||||
|
|
||||||
|
Object getHandle();
|
||||||
|
|
||||||
|
int readableBytes();
|
||||||
|
|
||||||
|
void readBytes(ObjectOutputStream output, int readableBytes) throws IOException;
|
||||||
|
|
||||||
|
void readBytes(byte[] data);
|
||||||
|
|
||||||
|
void writeByte(byte b);
|
||||||
|
|
||||||
|
void writeByte(int i);
|
||||||
|
|
||||||
|
void writeBytes(byte[] bytes);
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface WrappedChannel {
|
||||||
|
|
||||||
|
void writeAndFlush(Object packet);
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.base64.Base64;
|
||||||
|
import io.netty.util.concurrent.GenericFutureListener;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.compat.netty.NettyCompat;
|
||||||
|
import com.comphenix.protocol.compat.netty.ProtocolInjector;
|
||||||
|
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class IndependentNetty implements NettyCompat {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedByteBuf createPacketBuffer() {
|
||||||
|
return getPacketDataSerializer(allocateUnpooled());
|
||||||
|
}
|
||||||
|
|
||||||
|
private WrappedByteBuf getPacketDataSerializer(WrappedByteBuf buffer) {
|
||||||
|
Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new NettyByteBuf((ByteBuf) packetSerializer.getConstructor(MinecraftReflection.getByteBufClass())
|
||||||
|
.newInstance(buffer.getHandle()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot construct packet serializer.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedByteBuf allocateUnpooled() {
|
||||||
|
return new NettyByteBuf(UnpooledByteBufAllocator.DEFAULT.buffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getGenericFutureListenerArray() {
|
||||||
|
return GenericFutureListener[].class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<?> getChannelHandlerContext() {
|
||||||
|
return ChannelHandlerContext.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toEncodedText(CompressedImage image) {
|
||||||
|
final ByteBuf buffer = Unpooled.wrappedBuffer(image.getDataCopy());
|
||||||
|
String computed = "data:" + image.getMime() + ";base64," +
|
||||||
|
Base64.encode(buffer).toString(Charsets.UTF_8);
|
||||||
|
return computed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedByteBuf decode(byte[] encoded) {
|
||||||
|
return new NettyByteBuf(Base64.decode(Unpooled.wrappedBuffer(encoded)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolInjector getProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
|
||||||
|
return new NettyProtocolInjector(plugin, invoker, reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedByteBuf packetReader(DataInputStream input) {
|
||||||
|
return new NettyByteBuf(NettyByteBufAdapter.packetReader(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedByteBuf packetWriter(DataOutputStream output) {
|
||||||
|
return new NettyByteBuf(NettyByteBufAdapter.packetWriter(output));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
public class NettyBootstrapList implements List<Object> {
|
||||||
|
private List<Object> delegate;
|
||||||
|
private ChannelHandler handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new bootstrap list.
|
||||||
|
* @param delegate - the delegate.
|
||||||
|
* @param handler - the channel handler to add.
|
||||||
|
*/
|
||||||
|
public NettyBootstrapList(List<Object> delegate, ChannelHandler handler) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.handler = handler;
|
||||||
|
|
||||||
|
// Process all existing bootstraps
|
||||||
|
for (Object item : this) {
|
||||||
|
processElement(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean add(Object element) {
|
||||||
|
processElement(element);
|
||||||
|
return delegate.add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean addAll(Collection<? extends Object> collection) {
|
||||||
|
List<Object> copy = Lists.newArrayList(collection);
|
||||||
|
|
||||||
|
// Process the collection before we pass it on
|
||||||
|
for (Object element : copy) {
|
||||||
|
processElement(element);
|
||||||
|
}
|
||||||
|
return delegate.addAll(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Object set(int index, Object element) {
|
||||||
|
Object old = delegate.set(index, element);
|
||||||
|
|
||||||
|
// Handle the old future, and the newly inserted future
|
||||||
|
if (old != element) {
|
||||||
|
unprocessElement(old);
|
||||||
|
processElement(element);
|
||||||
|
}
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a single element.
|
||||||
|
* @param element - the element.
|
||||||
|
*/
|
||||||
|
protected void processElement(Object element) {
|
||||||
|
if (element instanceof ChannelFuture) {
|
||||||
|
processBootstrap((ChannelFuture) element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unprocess a single element.
|
||||||
|
* @param element - the element to unprocess.
|
||||||
|
*/
|
||||||
|
protected void unprocessElement(Object element) {
|
||||||
|
if (element instanceof ChannelFuture) {
|
||||||
|
unprocessBootstrap((ChannelFuture) element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a single channel future.
|
||||||
|
* @param future - the future.
|
||||||
|
*/
|
||||||
|
protected void processBootstrap(ChannelFuture future) {
|
||||||
|
// Important: Must be addFirst()
|
||||||
|
future.channel().pipeline().addFirst(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert any changes we made to the channel future.
|
||||||
|
* @param future - the future.
|
||||||
|
*/
|
||||||
|
protected void unprocessBootstrap(ChannelFuture future) {
|
||||||
|
final Channel channel = future.channel();
|
||||||
|
|
||||||
|
// For thread safety - see ChannelInjector.close()
|
||||||
|
channel.eventLoop().submit(new Callable<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object call() throws Exception {
|
||||||
|
channel.pipeline().remove(handler);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close and revert all changes.
|
||||||
|
*/
|
||||||
|
public synchronized void close() {
|
||||||
|
for (Object element : this)
|
||||||
|
unprocessElement(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boiler plate
|
||||||
|
@Override
|
||||||
|
public synchronized int size() {
|
||||||
|
return delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean isEmpty() {
|
||||||
|
return delegate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return delegate.contains(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Iterator<Object> iterator() {
|
||||||
|
return delegate.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Object[] toArray() {
|
||||||
|
return delegate.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized <T> T[] toArray(T[] a) {
|
||||||
|
return delegate.toArray(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean remove(Object o) {
|
||||||
|
return delegate.remove(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean containsAll(Collection<?> c) {
|
||||||
|
return delegate.containsAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean addAll(int index, Collection<? extends Object> c) {
|
||||||
|
return delegate.addAll(index, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean removeAll(Collection<?> c) {
|
||||||
|
return delegate.removeAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean retainAll(Collection<?> c) {
|
||||||
|
return delegate.retainAll(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void clear() {
|
||||||
|
delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Object get(int index) {
|
||||||
|
return delegate.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void add(int index, Object element) {
|
||||||
|
delegate.add(index, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized Object remove(int index) {
|
||||||
|
return delegate.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int indexOf(Object o) {
|
||||||
|
return delegate.indexOf(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int lastIndexOf(Object o) {
|
||||||
|
return delegate.lastIndexOf(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized ListIterator<Object> listIterator() {
|
||||||
|
return delegate.listIterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized ListIterator<Object> listIterator(int index) {
|
||||||
|
return delegate.listIterator(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized List<Object> subList(int fromIndex, int toIndex) {
|
||||||
|
return delegate.subList(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
// End boiler plate
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class NettyByteBuf implements WrappedByteBuf {
|
||||||
|
private final ByteBuf handle;
|
||||||
|
|
||||||
|
public NettyByteBuf(ByteBuf handle) {
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeBytes(ObjectInputStream input, int id) throws IOException {
|
||||||
|
handle.writeBytes(input, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readableBytes() {
|
||||||
|
return handle.readableBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBytes(ObjectOutputStream output, int readableBytes) throws IOException {
|
||||||
|
handle.readBytes(output, readableBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBytes(byte[] data) {
|
||||||
|
handle.readBytes(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeByte(byte b) {
|
||||||
|
handle.writeByte(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeByte(int i) {
|
||||||
|
handle.writeByte(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeBytes(byte[] bytes) {
|
||||||
|
handle.writeBytes(bytes);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,392 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.buffer.AbstractByteBuf;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.GatheringByteChannel;
|
||||||
|
import java.nio.channels.ScatteringByteChannel;
|
||||||
|
import java.nio.channels.WritableByteChannel;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||||
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a ByteBuf around an input stream and an output stream.
|
||||||
|
* <p>
|
||||||
|
* Note that as streams usually don't support seeking, this implementation will ignore
|
||||||
|
* all indexing in the byte buffer.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NettyByteBufAdapter extends AbstractByteBuf {
|
||||||
|
private DataInputStream input;
|
||||||
|
private DataOutputStream output;
|
||||||
|
|
||||||
|
// For modifying the reader or writer index
|
||||||
|
private static FieldAccessor READER_INDEX;
|
||||||
|
private static FieldAccessor WRITER_INDEX;
|
||||||
|
|
||||||
|
private static final int CAPACITY = Short.MAX_VALUE;
|
||||||
|
|
||||||
|
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
|
||||||
|
// Just pick a figure
|
||||||
|
super(CAPACITY);
|
||||||
|
this.input = input;
|
||||||
|
this.output = output;
|
||||||
|
|
||||||
|
// Prepare accessors
|
||||||
|
try {
|
||||||
|
if (READER_INDEX == null) {
|
||||||
|
READER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("readerIndex"));
|
||||||
|
}
|
||||||
|
if (WRITER_INDEX == null) {
|
||||||
|
WRITER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("writerIndex"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Infinite" reading/writing
|
||||||
|
if (input == null)
|
||||||
|
READER_INDEX.set(this, Integer.MAX_VALUE);
|
||||||
|
if (output == null)
|
||||||
|
WRITER_INDEX.set(this, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new Minecraft packet serializer using the current byte buf adapter.
|
||||||
|
* @param input - the input stream.
|
||||||
|
* @return A packet serializer with a wrapped byte buf adapter.
|
||||||
|
*/
|
||||||
|
public static ByteBuf packetReader(DataInputStream input) {
|
||||||
|
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
|
||||||
|
* @param output - the output stream.
|
||||||
|
* @return A packet serializer with a wrapped byte buf adapter.
|
||||||
|
*/
|
||||||
|
public static ByteBuf packetWriter(DataOutputStream output) {
|
||||||
|
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refCnt() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release(int paramInt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte _getByte(int paramInt) {
|
||||||
|
try {
|
||||||
|
return input.readByte();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short _getShort(int paramInt) {
|
||||||
|
try {
|
||||||
|
return input.readShort();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int _getUnsignedMedium(int paramInt) {
|
||||||
|
try {
|
||||||
|
return input.readUnsignedShort();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int _getInt(int paramInt) {
|
||||||
|
try {
|
||||||
|
return input.readInt();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long _getLong(int paramInt) {
|
||||||
|
try {
|
||||||
|
return input.readLong();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void _setByte(int index, int value) {
|
||||||
|
try {
|
||||||
|
output.writeByte(value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void _setShort(int index, int value) {
|
||||||
|
try {
|
||||||
|
output.writeShort(value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void _setMedium(int index, int value) {
|
||||||
|
try {
|
||||||
|
output.writeShort(value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void _setInt(int index, int value) {
|
||||||
|
try {
|
||||||
|
output.writeInt(value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void _setLong(int index, long value) {
|
||||||
|
try {
|
||||||
|
output.writeLong(value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int capacity() {
|
||||||
|
return CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf capacity(int paramInt) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBufAllocator alloc() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteOrder order() {
|
||||||
|
return ByteOrder.LITTLE_ENDIAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf unwrap() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
dst.setByte(dstIndex + i, input.read());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||||
|
try {
|
||||||
|
input.read(dst, dstIndex, length);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||||
|
try {
|
||||||
|
dst.put(ByteStreams.toByteArray(input));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot read input.", e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
|
||||||
|
ByteStreams.copy(ByteStreams.limit(input, length), dst);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||||
|
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length));
|
||||||
|
|
||||||
|
out.write(ByteBuffer.wrap(data));
|
||||||
|
return data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||||
|
byte[] buffer = new byte[length];
|
||||||
|
src.getBytes(srcIndex, buffer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
output.write(buffer);
|
||||||
|
return this;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||||
|
try {
|
||||||
|
output.write(src, srcIndex, length);
|
||||||
|
return this;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||||
|
try {
|
||||||
|
WritableByteChannel channel = Channels.newChannel(output);
|
||||||
|
|
||||||
|
channel.write(src);
|
||||||
|
return this;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Cannot write output.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||||
|
InputStream limit = ByteStreams.limit(in, length);
|
||||||
|
ByteStreams.copy(limit, output);
|
||||||
|
return length - limit.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||||
|
WritableByteChannel channel = Channels.newChannel(output);
|
||||||
|
|
||||||
|
int count = in.read(buffer);
|
||||||
|
channel.write(buffer);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf copy(int index, int length) {
|
||||||
|
throw new UnsupportedOperationException("Cannot seek in input stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int nioBufferCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer nioBuffer(int paramInt1, int paramInt2) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer internalNioBuffer(int paramInt1, int paramInt2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer[] nioBuffers(int paramInt1, int paramInt2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasArray() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] array() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int arrayOffset() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMemoryAddress() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long memoryAddress() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf retain(int paramInt) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf retain() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.compat.netty.WrappedChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author dmulloy2
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class NettyChannel implements WrappedChannel {
|
||||||
|
private final Channel channel;
|
||||||
|
|
||||||
|
public NettyChannel(Channel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeAndFlush(Object packet) {
|
||||||
|
channel.writeAndFlush(packet);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,948 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
import io.netty.handler.codec.MessageToByteEncoder;
|
||||||
|
import io.netty.util.concurrent.GenericFutureListener;
|
||||||
|
import io.netty.util.internal.TypeParameterMatcher;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import net.sf.cglib.proxy.Factory;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.PacketType.Protocol;
|
||||||
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
|
import com.comphenix.protocol.compat.netty.ChannelInjector;
|
||||||
|
import com.comphenix.protocol.compat.netty.WrappedByteBuf;
|
||||||
|
import com.comphenix.protocol.compat.netty.WrappedChannel;
|
||||||
|
import com.comphenix.protocol.error.Report;
|
||||||
|
import com.comphenix.protocol.error.ReportType;
|
||||||
|
import com.comphenix.protocol.events.ConnectionSide;
|
||||||
|
import com.comphenix.protocol.events.NetworkMarker;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.injector.NetworkProcessor;
|
||||||
|
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||||
|
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
|
||||||
|
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||||
|
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.VolatileField;
|
||||||
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||||
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||||
|
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftFields;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a channel injector.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NettyChannelInjector extends ByteToMessageDecoder implements ChannelInjector {
|
||||||
|
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
|
||||||
|
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
|
||||||
|
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
|
||||||
|
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
|
||||||
|
public static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a packet has bypassed packet listeners.
|
||||||
|
*/
|
||||||
|
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(NettyChannelInjector.class);
|
||||||
|
|
||||||
|
// The login packet
|
||||||
|
private static Class<?> PACKET_LOGIN_CLIENT = null;
|
||||||
|
private static FieldAccessor LOGIN_GAME_PROFILE = null;
|
||||||
|
|
||||||
|
// Saved accessors
|
||||||
|
private static MethodAccessor DECODE_BUFFER;
|
||||||
|
private static MethodAccessor ENCODE_BUFFER;
|
||||||
|
private static FieldAccessor ENCODER_TYPE_MATCHER;
|
||||||
|
|
||||||
|
// For retrieving the protocol
|
||||||
|
private static FieldAccessor PROTOCOL_ACCESSOR;
|
||||||
|
|
||||||
|
// For retrieving the protocol version
|
||||||
|
private static MethodAccessor PROTOCOL_VERSION;
|
||||||
|
|
||||||
|
// The factory that created this injector
|
||||||
|
private NettyInjectionFactory factory;
|
||||||
|
|
||||||
|
// The player, or temporary player
|
||||||
|
private Player player;
|
||||||
|
private Player updated;
|
||||||
|
private String playerName;
|
||||||
|
|
||||||
|
// The player connection
|
||||||
|
private Object playerConnection;
|
||||||
|
|
||||||
|
// The current network manager and channel
|
||||||
|
private final Object networkManager;
|
||||||
|
private final Channel originalChannel;
|
||||||
|
private VolatileField channelField;
|
||||||
|
|
||||||
|
// Known network markers
|
||||||
|
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that this packet has been processed by event listeners.
|
||||||
|
* <p>
|
||||||
|
* This must never be set outside the channel pipeline's thread.
|
||||||
|
*/
|
||||||
|
private PacketEvent currentEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A packet event that should be processed by the write method.
|
||||||
|
*/
|
||||||
|
private PacketEvent finalEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flag set by the main thread to indiciate that a packet should not be processed.
|
||||||
|
*/
|
||||||
|
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
|
||||||
|
@Override
|
||||||
|
protected Boolean initialValue() {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Other handlers
|
||||||
|
private ByteToMessageDecoder vanillaDecoder;
|
||||||
|
private MessageToByteEncoder<Object> vanillaEncoder;
|
||||||
|
|
||||||
|
private Deque<PacketEvent> finishQueue = new ArrayDeque<PacketEvent>();
|
||||||
|
|
||||||
|
// The channel listener
|
||||||
|
private ChannelListener channelListener;
|
||||||
|
|
||||||
|
// Processing network markers
|
||||||
|
private NetworkProcessor processor;
|
||||||
|
|
||||||
|
// Closed
|
||||||
|
private boolean injected;
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new channel injector.
|
||||||
|
* @param player - the current player, or temporary player.
|
||||||
|
* @param networkManager - its network manager.
|
||||||
|
* @param channel - its channel.
|
||||||
|
* @param channelListener - a listener.
|
||||||
|
* @param factory - the factory that created this injector
|
||||||
|
*/
|
||||||
|
public NettyChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener, NettyInjectionFactory factory) {
|
||||||
|
this.player = Preconditions.checkNotNull(player, "player cannot be NULL");
|
||||||
|
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
|
||||||
|
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
|
||||||
|
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
|
||||||
|
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
|
||||||
|
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
|
||||||
|
|
||||||
|
// Get the channel field
|
||||||
|
this.channelField = new VolatileField(FuzzyReflection.fromObject(networkManager, true).getFieldByType("channel", Channel.class),
|
||||||
|
networkManager, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version of the current protocol.
|
||||||
|
* @return The version.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getProtocolVersion() {
|
||||||
|
MethodAccessor accessor = PROTOCOL_VERSION;
|
||||||
|
if (accessor == null) {
|
||||||
|
try {
|
||||||
|
accessor = Accessors.getMethodAccessor(networkManager.getClass(), "getVersion");
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessor != null) {
|
||||||
|
return (Integer) accessor.invoke(networkManager);
|
||||||
|
} else {
|
||||||
|
return MinecraftProtocolVersion.getCurrentVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public boolean inject() {
|
||||||
|
synchronized (networkManager) {
|
||||||
|
if (closed)
|
||||||
|
return false;
|
||||||
|
if (originalChannel instanceof Factory)
|
||||||
|
return false;
|
||||||
|
if (!originalChannel.isActive())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Main thread? We should synchronize with the channel thread, otherwise we might see a
|
||||||
|
// pipeline with only some of the handlers removed
|
||||||
|
if (Bukkit.isPrimaryThread()) {
|
||||||
|
// Just like in the close() method, we'll avoid blocking the main thread
|
||||||
|
executeInChannelThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
inject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false; // We don't know
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't inject the same channel twice
|
||||||
|
if (findChannelHandler(originalChannel, NettyChannelInjector.class) != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the vanilla decoder, so we don't have to replicate the work
|
||||||
|
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
|
||||||
|
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
|
||||||
|
|
||||||
|
if (vanillaDecoder == null)
|
||||||
|
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
|
||||||
|
if (vanillaEncoder == null)
|
||||||
|
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
|
||||||
|
patchEncoder(vanillaEncoder);
|
||||||
|
|
||||||
|
if (DECODE_BUFFER == null)
|
||||||
|
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
|
||||||
|
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
|
||||||
|
if (ENCODE_BUFFER == null)
|
||||||
|
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
|
||||||
|
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
|
||||||
|
|
||||||
|
// Intercept sent packets
|
||||||
|
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
|
||||||
|
if (packet instanceof WirePacket) {
|
||||||
|
// Special case for wire format
|
||||||
|
NettyChannelInjector.this.encodeWirePacket((WirePacket) packet, new NettyByteBuf(output));
|
||||||
|
} else {
|
||||||
|
NettyChannelInjector.this.encode(ctx, packet, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
|
||||||
|
super.write(ctx, packet, promise);
|
||||||
|
NettyChannelInjector.this.finalWrite(ctx, packet, promise);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Intercept recieved packets
|
||||||
|
ChannelInboundHandlerAdapter finishHandler = new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
// Execute context first
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
NettyChannelInjector.this.finishRead(ctx, msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ChannelHandlerAdapter exceptionHandler = new ChannelHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext context, Throwable ex) throws Exception {
|
||||||
|
if (ex instanceof ClosedChannelException) {
|
||||||
|
// Ignore
|
||||||
|
} else {
|
||||||
|
// TODO Actually handle exceptions?
|
||||||
|
System.err.println("[ProtocolLib] Encountered an uncaught exception in the channel pipeline:");
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
|
||||||
|
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
|
||||||
|
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
|
||||||
|
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
|
||||||
|
originalChannel.pipeline().addLast("protocol_lib_exception_handler", exceptionHandler);
|
||||||
|
|
||||||
|
// Intercept all write methods
|
||||||
|
channelField.setValue(new NettyChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
|
||||||
|
// Compatibility with Spigot 1.8
|
||||||
|
private final NettyPipelineProxy pipelineProxy = new NettyPipelineProxy(originalChannel.pipeline(), this) {
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler) {
|
||||||
|
// Correct the position of the decoder
|
||||||
|
if ("decoder".equals(baseName)) {
|
||||||
|
if (super.get("protocol_lib_decoder") != null && guessCompression(handler)) {
|
||||||
|
super.addBefore("protocol_lib_decoder", name, handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.addBefore(baseName, name, handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline pipeline() {
|
||||||
|
return pipelineProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
|
||||||
|
final PacketEvent event = handleScheduled(callable, packetAccessor);
|
||||||
|
|
||||||
|
// Handle cancelled events
|
||||||
|
if (event != null && event.isCancelled())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Callable<T>() {
|
||||||
|
@Override
|
||||||
|
public T call() throws Exception {
|
||||||
|
T result = null;
|
||||||
|
|
||||||
|
// This field must only be updated in the pipeline thread
|
||||||
|
currentEvent = event;
|
||||||
|
result = callable.call();
|
||||||
|
currentEvent = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
|
||||||
|
final PacketEvent event = handleScheduled(runnable, packetAccessor);
|
||||||
|
|
||||||
|
// Handle cancelled events
|
||||||
|
if (event != null && event.isCancelled())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
currentEvent = event;
|
||||||
|
runnable.run();
|
||||||
|
currentEvent = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
|
||||||
|
// Let the filters handle this packet
|
||||||
|
Object original = accessor.get(instance);
|
||||||
|
|
||||||
|
// See if we've been instructed not to process packets
|
||||||
|
if (!scheduleProcessPackets.get()) {
|
||||||
|
NetworkMarker marker = getMarker(original);
|
||||||
|
|
||||||
|
if (marker != null) {
|
||||||
|
PacketEvent result = new PacketEvent(NettyChannelInjector.class);
|
||||||
|
result.setNetworkMarker(marker);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return BYPASSED_PACKET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PacketEvent event = processSending(original);
|
||||||
|
|
||||||
|
if (event != null && !event.isCancelled()) {
|
||||||
|
Object changed = event.getPacket().getHandle();
|
||||||
|
|
||||||
|
// Change packet to be scheduled
|
||||||
|
if (original != changed)
|
||||||
|
accessor.set(instance, changed);
|
||||||
|
};
|
||||||
|
return event != null ? event : BYPASSED_PACKET;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
injected = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given object is a compressor or decompressor.
|
||||||
|
* @param handler - object to test.
|
||||||
|
* @return TRUE if it is, FALSE if not or unknown.
|
||||||
|
*/
|
||||||
|
private boolean guessCompression(ChannelHandler handler) {
|
||||||
|
String className = handler != null ? handler.getClass().getCanonicalName() : null;
|
||||||
|
return className.contains("Compressor") || className.contains("Decompressor");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a given message on the packet listeners.
|
||||||
|
* @param message - the message/packet.
|
||||||
|
* @return The resulting message/packet.
|
||||||
|
*/
|
||||||
|
private PacketEvent processSending(Object message) {
|
||||||
|
return channelListener.onPacketSending(NettyChannelInjector.this, message, getMarker(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method patches the encoder so that it skips already created packets.
|
||||||
|
* @param encoder - the encoder to patch.
|
||||||
|
*/
|
||||||
|
private void patchEncoder(MessageToByteEncoder<Object> encoder) {
|
||||||
|
if (ENCODER_TYPE_MATCHER == null) {
|
||||||
|
ENCODER_TYPE_MATCHER = Accessors.getFieldAccessor(encoder.getClass(), "matcher", true);
|
||||||
|
}
|
||||||
|
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
if (channelListener.isDebug())
|
||||||
|
cause.printStackTrace();
|
||||||
|
super.exceptionCaught(ctx, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void encodeWirePacket(WirePacket packet, WrappedByteBuf output) throws Exception {
|
||||||
|
packet.writeId(output);
|
||||||
|
packet.writeBytes(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
|
||||||
|
* @param ctx - the current context.
|
||||||
|
* @param packet - the packet to encode to a byte array.
|
||||||
|
* @param output - the output byte array.
|
||||||
|
* @throws Exception If anything went wrong.
|
||||||
|
*/
|
||||||
|
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
|
||||||
|
NetworkMarker marker = null;
|
||||||
|
PacketEvent event = currentEvent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Skip every kind of non-filtered packet
|
||||||
|
if (!scheduleProcessPackets.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This packet has not been seen by the main thread
|
||||||
|
if (event == null) {
|
||||||
|
Class<?> clazz = packet.getClass();
|
||||||
|
|
||||||
|
// Schedule the transmission on the main thread instead
|
||||||
|
if (channelListener.hasMainThreadListener(clazz)) {
|
||||||
|
// Delay the packet
|
||||||
|
scheduleMainThread(packet);
|
||||||
|
packet = null;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
event = processSending(packet);
|
||||||
|
|
||||||
|
// Handle the output
|
||||||
|
if (event != null) {
|
||||||
|
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event != null) {
|
||||||
|
// Retrieve marker without accidentally constructing it
|
||||||
|
marker = NetworkMarker.getNetworkMarker(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process output handler
|
||||||
|
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
|
||||||
|
ByteBuf packetBuffer = ctx.alloc().buffer();
|
||||||
|
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
|
||||||
|
|
||||||
|
// Let each handler prepare the actual output
|
||||||
|
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
|
||||||
|
|
||||||
|
// Write the result
|
||||||
|
output.writeBytes(data);
|
||||||
|
packet = null;
|
||||||
|
|
||||||
|
// Sent listeners?
|
||||||
|
finalEvent = event;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
channelListener.getReporter().reportDetailed(this,
|
||||||
|
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
|
||||||
|
} finally {
|
||||||
|
// Attempt to handle the packet nevertheless
|
||||||
|
if (packet != null) {
|
||||||
|
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
|
||||||
|
finalEvent = event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a packet has been written to the channel.
|
||||||
|
* @param ctx - current context.
|
||||||
|
* @param packet - the packet that has been written.
|
||||||
|
* @param promise - a promise.
|
||||||
|
*/
|
||||||
|
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
|
||||||
|
PacketEvent event = finalEvent;
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
// Necessary to prevent infinite loops
|
||||||
|
finalEvent = null;
|
||||||
|
currentEvent = null;
|
||||||
|
|
||||||
|
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleMainThread(final Object packetCopy) {
|
||||||
|
// Don't use BukkitExecutors for this - it has a bit of overhead
|
||||||
|
Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
invokeSendPacket(packetCopy);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
|
||||||
|
byteBuffer.markReaderIndex();
|
||||||
|
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Reset queue
|
||||||
|
finishQueue.clear();
|
||||||
|
|
||||||
|
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
|
||||||
|
Object input = it.next();
|
||||||
|
Class<?> packetClass = input.getClass();
|
||||||
|
NetworkMarker marker = null;
|
||||||
|
|
||||||
|
// Special case!
|
||||||
|
handleLogin(packetClass, input);
|
||||||
|
|
||||||
|
if (channelListener.includeBuffer(packetClass)) {
|
||||||
|
byteBuffer.resetReaderIndex();
|
||||||
|
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
|
||||||
|
|
||||||
|
// Handle packet changes
|
||||||
|
if (output != null) {
|
||||||
|
if (output.isCancelled()) {
|
||||||
|
it.remove();
|
||||||
|
continue;
|
||||||
|
} else if (output.getPacket().getHandle() != input) {
|
||||||
|
it.set(output.getPacket().getHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
finishQueue.addLast(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
channelListener.getReporter().reportDetailed(this,
|
||||||
|
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked after our decoder.
|
||||||
|
* @param ctx - current context.
|
||||||
|
* @param msg - the current packet.
|
||||||
|
*/
|
||||||
|
protected void finishRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
|
// Assume same order
|
||||||
|
PacketEvent event = finishQueue.pollFirst();
|
||||||
|
|
||||||
|
if (event != null) {
|
||||||
|
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
||||||
|
|
||||||
|
if (marker != null) {
|
||||||
|
processor.invokePostEvent(event, marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when we may need to handle the login packet.
|
||||||
|
* @param packetClass - the packet class.
|
||||||
|
* @param packet - the packet.
|
||||||
|
*/
|
||||||
|
protected void handleLogin(Class<?> packetClass, Object packet) {
|
||||||
|
Class<?> loginClass = PACKET_LOGIN_CLIENT;
|
||||||
|
FieldAccessor loginClient = LOGIN_GAME_PROFILE;
|
||||||
|
|
||||||
|
// Initialize packet class and login
|
||||||
|
if (loginClass == null) {
|
||||||
|
loginClass = PacketType.Login.Client.START.getPacketClass();
|
||||||
|
PACKET_LOGIN_CLIENT = loginClass;
|
||||||
|
}
|
||||||
|
if (loginClient == null) {
|
||||||
|
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, MinecraftReflection.getGameProfileClass(), true);
|
||||||
|
LOGIN_GAME_PROFILE = loginClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we are dealing with the login packet
|
||||||
|
if (loginClass.equals(packetClass)) {
|
||||||
|
// GameProfile profile = (GameProfile) loginClient.get(packet);
|
||||||
|
WrappedGameProfile profile = WrappedGameProfile.fromHandle(loginClient.get(packet));
|
||||||
|
|
||||||
|
// Save the channel injector
|
||||||
|
factory.cacheInjector(profile.getName(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
super.channelActive(ctx);
|
||||||
|
|
||||||
|
// See NetworkManager.channelActive(ChannelHandlerContext) for why
|
||||||
|
if (channelField != null) {
|
||||||
|
channelField.refreshValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every byte in the given byte buffer.
|
||||||
|
* @param buffer - the buffer.
|
||||||
|
* @return The bytes.
|
||||||
|
*/
|
||||||
|
private byte[] getBytes(ByteBuf buffer) {
|
||||||
|
byte[] data = new byte[buffer.readableBytes()];
|
||||||
|
|
||||||
|
buffer.readBytes(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect the current player.
|
||||||
|
* @param message - the disconnect message, if possible.
|
||||||
|
*/
|
||||||
|
private void disconnect(String message) {
|
||||||
|
// If we're logging in, we can only close the channel
|
||||||
|
if (playerConnection == null || player instanceof Factory) {
|
||||||
|
originalChannel.disconnect();
|
||||||
|
} else {
|
||||||
|
// Call the disconnect method
|
||||||
|
try {
|
||||||
|
MinecraftMethods.getDisconnectMethod(playerConnection.getClass()).
|
||||||
|
invoke(playerConnection, message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("Unable to invoke disconnect method.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||||
|
saveMarker(packet, marker);
|
||||||
|
|
||||||
|
try {
|
||||||
|
scheduleProcessPackets.set(filtered);
|
||||||
|
invokeSendPacket(packet);
|
||||||
|
} finally {
|
||||||
|
scheduleProcessPackets.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the sendPacket method in Minecraft.
|
||||||
|
* @param packet - the packet to send.
|
||||||
|
*/
|
||||||
|
private void invokeSendPacket(Object packet) {
|
||||||
|
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
|
||||||
|
try {
|
||||||
|
if (player instanceof Factory) {
|
||||||
|
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet, new GenericFutureListener[0]);
|
||||||
|
} else {
|
||||||
|
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);
|
||||||
|
}
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
ProtocolLibrary.getErrorReporter().reportWarning(factory.getPlugin(),
|
||||||
|
Report.newBuilder(REPORT_CANNOT_SEND_PACKET).messageParam(packet, playerName).error(ex).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recieveClientPacket(final Object packet) {
|
||||||
|
// TODO: Ensure the packet listeners are executed in the channel thread.
|
||||||
|
|
||||||
|
// Execute this in the channel thread
|
||||||
|
Runnable action = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Inform the user
|
||||||
|
ProtocolLibrary.getErrorReporter().reportMinimal(factory.getPlugin(), "recieveClientPacket", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute in the worker thread
|
||||||
|
if (originalChannel.eventLoop().inEventLoop()) {
|
||||||
|
action.run();
|
||||||
|
} else {
|
||||||
|
originalChannel.eventLoop().execute(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Protocol getCurrentProtocol() {
|
||||||
|
if (PROTOCOL_ACCESSOR == null) {
|
||||||
|
PROTOCOL_ACCESSOR = Accessors.getFieldAccessor(
|
||||||
|
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true);
|
||||||
|
}
|
||||||
|
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the player connection of the current player.
|
||||||
|
* @return The player connection.
|
||||||
|
*/
|
||||||
|
private Object getPlayerConnection() {
|
||||||
|
if (playerConnection == null) {
|
||||||
|
playerConnection = MinecraftFields.getPlayerConnection(player);
|
||||||
|
}
|
||||||
|
return playerConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NetworkMarker getMarker(Object packet) {
|
||||||
|
return packetMarker.get(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||||
|
if (marker != null) {
|
||||||
|
packetMarker.put(packet, marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getPlayer() {
|
||||||
|
if (player == null && playerName != null) {
|
||||||
|
return Bukkit.getPlayer(playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the player instance.
|
||||||
|
* @param player - current instance.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setPlayer(Player player) {
|
||||||
|
this.player = player;
|
||||||
|
this.playerName = player.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the updated player instance.
|
||||||
|
* @param updated - updated instance.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setUpdatedPlayer(Player updated) {
|
||||||
|
this.updated = updated;
|
||||||
|
this.playerName = updated.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInjected() {
|
||||||
|
return injected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this channel has been closed and cleaned up.
|
||||||
|
* @return TRUE if it has, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (!closed) {
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
if (injected) {
|
||||||
|
channelField.revertValue();
|
||||||
|
|
||||||
|
// Calling remove() in the main thread will block the main thread, which may lead
|
||||||
|
// to a deadlock:
|
||||||
|
// http://pastebin.com/L3SBVKzp
|
||||||
|
//
|
||||||
|
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
|
||||||
|
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
|
||||||
|
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
|
||||||
|
// it's called from a different thread, blocking until the removal has been confirmed.
|
||||||
|
//
|
||||||
|
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
|
||||||
|
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
|
||||||
|
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
|
||||||
|
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
|
||||||
|
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
|
||||||
|
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
|
||||||
|
//
|
||||||
|
// TLDR: Concurrency is hard.
|
||||||
|
executeInChannelThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
String[] handlers = new String[] {
|
||||||
|
"protocol_lib_decoder", "protocol_lib_finish", "protocol_lib_encoder", "protocol_lib_exception_handler"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String handler : handlers) {
|
||||||
|
try {
|
||||||
|
originalChannel.pipeline().remove(handler);
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
factory.invalidate(player);
|
||||||
|
|
||||||
|
// Clear player instances
|
||||||
|
// Should help fix memory leaks
|
||||||
|
this.player = null;
|
||||||
|
this.updated = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a specific command in the channel thread.
|
||||||
|
* <p>
|
||||||
|
* Exceptions are printed through the standard error reporter mechanism.
|
||||||
|
* @param command - the command to execute.
|
||||||
|
*/
|
||||||
|
private void executeInChannelThread(final Runnable command) {
|
||||||
|
originalChannel.eventLoop().execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
command.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ProtocolLibrary.getErrorReporter().reportDetailed(NettyChannelInjector.this,
|
||||||
|
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first channel handler that is assignable to a given type.
|
||||||
|
* @param channel - the channel.
|
||||||
|
* @param clazz - the type.
|
||||||
|
* @return The first handler, or NULL.
|
||||||
|
*/
|
||||||
|
public static ChannelHandler findChannelHandler(Channel channel, Class<?> clazz) {
|
||||||
|
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
|
||||||
|
if (clazz.isAssignableFrom(entry.getValue().getClass())) {
|
||||||
|
return entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a socket injector that foreards to the current channel injector.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public static class ChannelSocketInjector implements SocketInjector {
|
||||||
|
private final NettyChannelInjector injector;
|
||||||
|
|
||||||
|
public ChannelSocketInjector(NettyChannelInjector injector) {
|
||||||
|
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket getSocket() throws IllegalAccessException {
|
||||||
|
return NettySocketAdapter.adapt((SocketChannel) injector.originalChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getAddress() throws IllegalAccessException {
|
||||||
|
return injector.originalChannel.remoteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnect(String message) throws InvocationTargetException {
|
||||||
|
injector.disconnect(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
|
||||||
|
injector.sendServerPacket(packet, marker, filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getPlayer() {
|
||||||
|
return injector.getPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getUpdatedPlayer() {
|
||||||
|
return injector.updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transferState(SocketInjector delegate) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUpdatedPlayer(Player updatedPlayer) {
|
||||||
|
injector.setPlayer(updatedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NettyChannelInjector getChannelInjector() {
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedChannel getChannel() {
|
||||||
|
return new NettyChannel(originalChannel);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,333 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBufAllocator;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelConfig;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelMetadata;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.ChannelProgressivePromise;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.util.Attribute;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||||
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
public abstract class NettyChannelProxy implements Channel {
|
||||||
|
// Mark that a certain object does not contain a message field
|
||||||
|
private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() {
|
||||||
|
@Override
|
||||||
|
public void set(Object instance, Object value) { }
|
||||||
|
@Override
|
||||||
|
public Object get(Object instance) { return null; }
|
||||||
|
@Override
|
||||||
|
public Field getField() { return null; };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Looking up packets in inner classes
|
||||||
|
private static Map<Class<?>, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap();
|
||||||
|
|
||||||
|
// The underlying channel
|
||||||
|
protected Channel delegate;
|
||||||
|
protected Class<?> messageClass;
|
||||||
|
|
||||||
|
// Event loop proxy
|
||||||
|
private transient NettyEventLoopProxy loopProxy;
|
||||||
|
|
||||||
|
public NettyChannelProxy(Channel delegate, Class<?> messageClass) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.messageClass = messageClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a packet is scheduled for transmission in the event loop.
|
||||||
|
* @param callable - callable to schedule for execution.
|
||||||
|
* @param packetAccessor - accessor for modifying the packet in the callable.
|
||||||
|
* @return The callable that will be scheduled, or NULL to cancel.
|
||||||
|
*/
|
||||||
|
protected abstract <T> Callable<T> onMessageScheduled(Callable<T> callable, FieldAccessor packetAccessor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a packet is scheduled for transmission in the event loop.
|
||||||
|
* @param runnable - the runnable that contains a packet to be scheduled.
|
||||||
|
* @param packetAccessor - accessor for modifying the packet in the runnable.
|
||||||
|
* @return The runnable that will be scheduled, or NULL to cancel.
|
||||||
|
*/
|
||||||
|
protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
|
||||||
|
return delegate.attr(paramAttributeKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture bind(SocketAddress paramSocketAddress) {
|
||||||
|
return delegate.bind(paramSocketAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline pipeline() {
|
||||||
|
return delegate.pipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress paramSocketAddress) {
|
||||||
|
return delegate.connect(paramSocketAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBufAllocator alloc() {
|
||||||
|
return delegate.alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPromise newPromise() {
|
||||||
|
return delegate.newPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventLoop eventLoop() {
|
||||||
|
if (loopProxy == null) {
|
||||||
|
loopProxy = new NettyEventLoopProxy() {
|
||||||
|
@Override
|
||||||
|
protected EventLoop getDelegate() {
|
||||||
|
return delegate.eventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Runnable schedulingRunnable(final Runnable runnable) {
|
||||||
|
final FieldAccessor accessor = getMessageAccessor(runnable);
|
||||||
|
|
||||||
|
if (accessor != null) {
|
||||||
|
Runnable result = onMessageScheduled(runnable, accessor);;
|
||||||
|
return result != null ? result : getEmptyRunnable();
|
||||||
|
}
|
||||||
|
return runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> Callable<T> schedulingCallable(Callable<T> callable) {
|
||||||
|
FieldAccessor accessor = getMessageAccessor(callable);
|
||||||
|
|
||||||
|
if (accessor != null) {
|
||||||
|
Callable<T> result = onMessageScheduled(callable, accessor);;
|
||||||
|
return result != null ? result : NettyEventLoopProxy.<T>getEmptyCallable();
|
||||||
|
}
|
||||||
|
return callable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return loopProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a way to access the packet field of an object.
|
||||||
|
* @param value - the object.
|
||||||
|
* @return The packet field accessor, or NULL if not found.
|
||||||
|
*/
|
||||||
|
private FieldAccessor getMessageAccessor(Object value) {
|
||||||
|
Class<?> clazz = value.getClass();
|
||||||
|
FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz);
|
||||||
|
|
||||||
|
if (accessor == null) {
|
||||||
|
try {
|
||||||
|
accessor = Accessors.getFieldAccessor(clazz, messageClass, true);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
accessor = MARK_NO_MESSAGE;
|
||||||
|
}
|
||||||
|
// Save the result
|
||||||
|
MESSAGE_LOOKUP.put(clazz, accessor);
|
||||||
|
}
|
||||||
|
return accessor != MARK_NO_MESSAGE ? accessor : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress paramSocketAddress1,
|
||||||
|
SocketAddress paramSocketAddress2) {
|
||||||
|
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelProgressivePromise newProgressivePromise() {
|
||||||
|
return delegate.newProgressivePromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Channel parent() {
|
||||||
|
return delegate.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelConfig config() {
|
||||||
|
return delegate.config();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture newSucceededFuture() {
|
||||||
|
return delegate.newSucceededFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return delegate.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture disconnect() {
|
||||||
|
return delegate.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRegistered() {
|
||||||
|
return delegate.isRegistered();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
|
||||||
|
return delegate.newFailedFuture(paramThrowable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture close() {
|
||||||
|
return delegate.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return delegate.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public ChannelFuture deregister() {
|
||||||
|
return delegate.deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPromise voidPromise() {
|
||||||
|
return delegate.voidPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelMetadata metadata() {
|
||||||
|
return delegate.metadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture bind(SocketAddress paramSocketAddress,
|
||||||
|
ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.bind(paramSocketAddress, paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress localAddress() {
|
||||||
|
return delegate.localAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress remoteAddress() {
|
||||||
|
return delegate.remoteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress paramSocketAddress,
|
||||||
|
ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.connect(paramSocketAddress, paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture closeFuture() {
|
||||||
|
return delegate.closeFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWritable() {
|
||||||
|
return delegate.isWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Channel flush() {
|
||||||
|
return delegate.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress paramSocketAddress1,
|
||||||
|
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Channel read() {
|
||||||
|
return delegate.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Unsafe unsafe() {
|
||||||
|
return delegate.unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.disconnect(paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture close(ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.close(paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.deregister(paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object paramObject) {
|
||||||
|
return delegate.write(paramObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.write(paramObject, paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) {
|
||||||
|
return delegate.writeAndFlush(paramObject, paramChannelPromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeAndFlush(Object paramObject) {
|
||||||
|
return delegate.writeAndFlush(paramObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Channel o) {
|
||||||
|
return delegate.compareTo(o);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,259 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.channel.EventLoop;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.util.concurrent.EventExecutor;
|
||||||
|
import io.netty.util.concurrent.Future;
|
||||||
|
import io.netty.util.concurrent.ProgressivePromise;
|
||||||
|
import io.netty.util.concurrent.Promise;
|
||||||
|
import io.netty.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event loop proxy.
|
||||||
|
* @author Kristian.
|
||||||
|
*/
|
||||||
|
abstract class NettyEventLoopProxy implements EventLoop {
|
||||||
|
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final Callable<?> EMPTY_CALLABLE = new Callable<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object call() throws Exception {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying event loop.
|
||||||
|
* @return The event loop.
|
||||||
|
*/
|
||||||
|
protected abstract EventLoop getDelegate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a callable that does nothing but return NULL.
|
||||||
|
* @return The empty callable.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> Callable<T> getEmptyCallable() {
|
||||||
|
return (Callable<T>) EMPTY_CALLABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a runnable that does nothing.
|
||||||
|
* @return A NO-OP runnable.
|
||||||
|
*/
|
||||||
|
public static Runnable getEmptyRunnable() {
|
||||||
|
return EMPTY_RUNNABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a runnable is being scheduled.
|
||||||
|
* @param runnable - the runnable that is scheduling.
|
||||||
|
* @return The runnable to schedule instead. Cannot be NULL.
|
||||||
|
*/
|
||||||
|
protected abstract Runnable schedulingRunnable(Runnable runnable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a callable is being scheduled.
|
||||||
|
* @param runnable - the callable that is scheduling.
|
||||||
|
* @return The callable to schedule instead. Cannot be NULL.
|
||||||
|
*/
|
||||||
|
protected abstract <T> Callable<T> schedulingCallable(Callable<T> callable);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
getDelegate().execute(schedulingRunnable(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> submit(Callable<T> action) {
|
||||||
|
return getDelegate().submit(schedulingCallable(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> Future<T> submit(Runnable action, T arg1) {
|
||||||
|
return getDelegate().submit(schedulingRunnable(action), arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> submit(Runnable action) {
|
||||||
|
return getDelegate().submit(schedulingRunnable(action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ScheduledFuture<V> schedule(Callable<V> action, long arg1, TimeUnit arg2) {
|
||||||
|
return getDelegate().schedule(schedulingCallable(action), arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> schedule(Runnable action, long arg1, TimeUnit arg2) {
|
||||||
|
return getDelegate().schedule(schedulingRunnable(action), arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) {
|
||||||
|
return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) {
|
||||||
|
return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boiler plate:
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return getDelegate().awaitTermination(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inEventLoop() {
|
||||||
|
return getDelegate().inEventLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inEventLoop(Thread arg0) {
|
||||||
|
return getDelegate().inEventLoop(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return getDelegate().isShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return getDelegate().isTerminated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
|
||||||
|
throws InterruptedException {
|
||||||
|
return getDelegate().invokeAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
|
||||||
|
TimeUnit unit) throws InterruptedException {
|
||||||
|
return getDelegate().invokeAll(tasks, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
|
||||||
|
ExecutionException {
|
||||||
|
return getDelegate().invokeAny(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
return getDelegate().invokeAny(tasks, timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShuttingDown() {
|
||||||
|
return getDelegate().isShuttingDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<EventExecutor> iterator() {
|
||||||
|
return getDelegate().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> newFailedFuture(Throwable arg0) {
|
||||||
|
return getDelegate().newFailedFuture(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventLoop next() {
|
||||||
|
return ((EventLoopGroup) getDelegate()).next();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ProgressivePromise<V> newProgressivePromise() {
|
||||||
|
return getDelegate().newProgressivePromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Promise<V> newPromise() {
|
||||||
|
return getDelegate().newPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> Future<V> newSucceededFuture(V arg0) {
|
||||||
|
return getDelegate().newSucceededFuture(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EventLoopGroup parent() {
|
||||||
|
return getDelegate().parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture register(Channel arg0, ChannelPromise arg1) {
|
||||||
|
return getDelegate().register(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture register(Channel arg0) {
|
||||||
|
return getDelegate().register(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully() {
|
||||||
|
return getDelegate().shutdownGracefully();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> shutdownGracefully(long arg0, long arg1, TimeUnit arg2) {
|
||||||
|
return getDelegate().shutdownGracefully(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<?> terminationFuture() {
|
||||||
|
return getDelegate().terminationFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void shutdown() {
|
||||||
|
getDelegate().shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
return getDelegate().shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.compat.netty.independent.NettyChannelInjector.ChannelSocketInjector;
|
||||||
|
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||||
|
import com.comphenix.protocol.injector.netty.ClosedInjector;
|
||||||
|
import com.comphenix.protocol.injector.netty.Injector;
|
||||||
|
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||||
|
import com.comphenix.protocol.injector.server.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an injector factory.
|
||||||
|
* <p>
|
||||||
|
* Note that the factory will return {@link ClosedInjector} when the factory is closed.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NettyInjectionFactory {
|
||||||
|
// This should work as long as the injectors are, uh, injected
|
||||||
|
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
|
||||||
|
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
|
||||||
|
|
||||||
|
// Whether or not the factory is closed
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
// The current plugin
|
||||||
|
private final Plugin plugin;
|
||||||
|
|
||||||
|
public NettyInjectionFactory(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the main plugin associated with this injection factory.
|
||||||
|
* @return The main plugin.
|
||||||
|
*/
|
||||||
|
public Plugin getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct or retrieve a channel injector from an existing Bukkit player.
|
||||||
|
* @param player - the existing Bukkit player.
|
||||||
|
* @param channelListener - 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 (closed)
|
||||||
|
return new ClosedInjector(player);
|
||||||
|
Injector injector = playerLookup.get(player);
|
||||||
|
|
||||||
|
// Find a temporary injector as well
|
||||||
|
if (injector == null)
|
||||||
|
injector = getTemporaryInjector(player);
|
||||||
|
if (injector != null && !injector.isClosed())
|
||||||
|
return injector;
|
||||||
|
|
||||||
|
Object networkManager = MinecraftFields.getNetworkManager(player);
|
||||||
|
|
||||||
|
// Must be a temporary Bukkit player
|
||||||
|
if (networkManager == null) {
|
||||||
|
return fromName(player.getName(), player);
|
||||||
|
}
|
||||||
|
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
|
||||||
|
|
||||||
|
// See if a channel has already been created
|
||||||
|
injector = (NettyChannelInjector) NettyChannelInjector.findChannelHandler(channel, NettyChannelInjector.class);
|
||||||
|
|
||||||
|
if (injector != null) {
|
||||||
|
// Update the player instance
|
||||||
|
playerLookup.remove(injector.getPlayer());
|
||||||
|
injector.setPlayer(player);
|
||||||
|
} else {
|
||||||
|
injector = new NettyChannelInjector(player, networkManager, channel, listener, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache injector and return
|
||||||
|
cacheInjector(player, injector);
|
||||||
|
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 address - the name.
|
||||||
|
* @return The cached injector, or a closed injector if it could not be found.
|
||||||
|
*/
|
||||||
|
public Injector fromName(String name, Player player) {
|
||||||
|
if (!closed) {
|
||||||
|
Injector injector = nameLookup.get(name);
|
||||||
|
|
||||||
|
// We can only retrieve cached injectors
|
||||||
|
if (injector != null) {
|
||||||
|
// Update instance
|
||||||
|
injector.setUpdatedPlayer(player);
|
||||||
|
return injector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ClosedInjector(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new channel injector for the given channel.
|
||||||
|
* @param channel - the channel.
|
||||||
|
* @param playerFactory - a temporary player creator.
|
||||||
|
* @param channelListener - the listener.
|
||||||
|
* @param loader - the current (plugin) class loader.
|
||||||
|
* @return The channel injector, or a closed injector.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
|
||||||
|
if (closed)
|
||||||
|
return new ClosedInjector(null);
|
||||||
|
|
||||||
|
Object networkManager = findNetworkManager(channel);
|
||||||
|
Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer());
|
||||||
|
NettyChannelInjector injector = new NettyChannelInjector(temporaryPlayer, networkManager, channel, listener, this);
|
||||||
|
|
||||||
|
// Initialize temporary player
|
||||||
|
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector));
|
||||||
|
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) {
|
||||||
|
Injector injector = playerLookup.remove(player);
|
||||||
|
|
||||||
|
nameLookup.remove(player.getName());
|
||||||
|
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) {
|
||||||
|
nameLookup.put(player.getName(), injector);
|
||||||
|
return 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 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) {
|
||||||
|
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||||
|
|
||||||
|
if (injector != null) {
|
||||||
|
return ((ChannelSocketInjector) injector).getChannelInjector();
|
||||||
|
}
|
||||||
|
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 closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all injectors created by this factory, and cease the creation of new injections.
|
||||||
|
*/
|
||||||
|
public synchronized void close() {
|
||||||
|
if (!closed) {
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
// Close everything
|
||||||
|
for (Injector injector : playerLookup.values())
|
||||||
|
injector.close();
|
||||||
|
for (Injector injector : nameLookup.values())
|
||||||
|
injector.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,371 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.util.concurrent.EventExecutorGroup;
|
||||||
|
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A pipeline proxy.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NettyPipelineProxy implements ChannelPipeline {
|
||||||
|
protected final ChannelPipeline pipeline;
|
||||||
|
protected final Channel channel;
|
||||||
|
|
||||||
|
public NettyPipelineProxy(ChannelPipeline pipeline, Channel channel) {
|
||||||
|
this.pipeline = pipeline;
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
|
||||||
|
pipeline.addAfter(arg0, arg1, arg2, arg3);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
pipeline.addAfter(arg0, arg1, arg2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
|
||||||
|
pipeline.addBefore(arg0, arg1, arg2, arg3);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
pipeline.addBefore(arg0, arg1, arg2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addFirst(ChannelHandler... arg0) {
|
||||||
|
pipeline.addFirst(arg0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) {
|
||||||
|
pipeline.addFirst(arg0, arg1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
pipeline.addFirst(arg0, arg1, arg2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) {
|
||||||
|
pipeline.addFirst(arg0, arg1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addLast(ChannelHandler... arg0) {
|
||||||
|
pipeline.addLast(arg0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) {
|
||||||
|
pipeline.addLast(arg0, arg1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
pipeline.addLast(arg0, arg1, arg2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline addLast(String arg0, ChannelHandler arg1) {
|
||||||
|
pipeline.addLast(arg0, arg1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) {
|
||||||
|
return pipeline.bind(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture bind(SocketAddress arg0) {
|
||||||
|
return pipeline.bind(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Channel channel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture close() {
|
||||||
|
return pipeline.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture close(ChannelPromise arg0) {
|
||||||
|
return pipeline.close(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) {
|
||||||
|
return pipeline.connect(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) {
|
||||||
|
return pipeline.connect(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) {
|
||||||
|
return pipeline.connect(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture connect(SocketAddress arg0) {
|
||||||
|
return pipeline.connect(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext context(ChannelHandler arg0) {
|
||||||
|
return pipeline.context(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext context(Class<? extends ChannelHandler> arg0) {
|
||||||
|
return pipeline.context(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext context(String arg0) {
|
||||||
|
return pipeline.context(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to call the depreciated methods to properly implement the proxy
|
||||||
|
@Override
|
||||||
|
public ChannelFuture deregister() {
|
||||||
|
return pipeline.deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture deregister(ChannelPromise arg0) {
|
||||||
|
return pipeline.deregister(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelUnregistered() {
|
||||||
|
pipeline.fireChannelUnregistered();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture disconnect() {
|
||||||
|
return pipeline.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture disconnect(ChannelPromise arg0) {
|
||||||
|
return pipeline.disconnect(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelActive() {
|
||||||
|
pipeline.fireChannelActive();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelInactive() {
|
||||||
|
pipeline.fireChannelInactive();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelRead(Object arg0) {
|
||||||
|
pipeline.fireChannelRead(arg0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelReadComplete() {
|
||||||
|
pipeline.fireChannelReadComplete();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelRegistered() {
|
||||||
|
pipeline.fireChannelRegistered();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireChannelWritabilityChanged() {
|
||||||
|
pipeline.fireChannelWritabilityChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireExceptionCaught(Throwable arg0) {
|
||||||
|
pipeline.fireExceptionCaught(arg0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline fireUserEventTriggered(Object arg0) {
|
||||||
|
pipeline.fireUserEventTriggered(arg0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler first() {
|
||||||
|
return pipeline.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext firstContext() {
|
||||||
|
return pipeline.firstContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline flush() {
|
||||||
|
pipeline.flush();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ChannelHandler> T get(Class<T> arg0) {
|
||||||
|
return pipeline.get(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler get(String arg0) {
|
||||||
|
return pipeline.get(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<String, ChannelHandler>> iterator() {
|
||||||
|
return pipeline.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler last() {
|
||||||
|
return pipeline.last();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandlerContext lastContext() {
|
||||||
|
return pipeline.lastContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> names() {
|
||||||
|
return pipeline.names();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline read() {
|
||||||
|
pipeline.read();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline remove(ChannelHandler arg0) {
|
||||||
|
pipeline.remove(arg0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ChannelHandler> T remove(Class<T> arg0) {
|
||||||
|
return pipeline.remove(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler remove(String arg0) {
|
||||||
|
return pipeline.remove(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler removeFirst() {
|
||||||
|
return pipeline.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler removeLast() {
|
||||||
|
return pipeline.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
pipeline.replace(arg0, arg1, arg2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ChannelHandler> T replace(Class<T> arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
return pipeline.replace(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) {
|
||||||
|
return pipeline.replace(arg0, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, ChannelHandler> toMap() {
|
||||||
|
return pipeline.toMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object arg0, ChannelPromise arg1) {
|
||||||
|
return pipeline.write(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object arg0) {
|
||||||
|
return pipeline.write(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) {
|
||||||
|
return pipeline.writeAndFlush(arg0, arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture writeAndFlush(Object arg0) {
|
||||||
|
return pipeline.writeAndFlush(arg0);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,438 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandler;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketType;
|
||||||
|
import com.comphenix.protocol.compat.netty.ChannelInjector;
|
||||||
|
import com.comphenix.protocol.compat.netty.ProtocolInjector;
|
||||||
|
import com.comphenix.protocol.compat.netty.WrappedChannel;
|
||||||
|
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.error.Report;
|
||||||
|
import com.comphenix.protocol.error.ReportType;
|
||||||
|
import com.comphenix.protocol.events.ConnectionSide;
|
||||||
|
import com.comphenix.protocol.events.ListenerOptions;
|
||||||
|
import com.comphenix.protocol.events.NetworkMarker;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||||
|
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||||
|
import com.comphenix.protocol.injector.netty.Injector;
|
||||||
|
import com.comphenix.protocol.injector.netty.NettyNetworkMarker;
|
||||||
|
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||||
|
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||||
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||||
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
|
import com.comphenix.protocol.injector.spigot.AbstractPacketInjector;
|
||||||
|
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.VolatileField;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
public class NettyProtocolInjector implements ProtocolInjector {
|
||||||
|
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
|
||||||
|
|
||||||
|
private volatile boolean injected;
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
// The temporary player factory
|
||||||
|
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||||
|
private List<VolatileField> bootstrapFields = Lists.newArrayList();
|
||||||
|
|
||||||
|
// The channel injector factory
|
||||||
|
private NettyInjectionFactory injectionFactory;
|
||||||
|
|
||||||
|
// List of network managers
|
||||||
|
private volatile List<Object> networkManagers;
|
||||||
|
|
||||||
|
// Different sending filters
|
||||||
|
private PacketTypeSet sendingFilters = new PacketTypeSet();
|
||||||
|
private PacketTypeSet reveivedFilters = new PacketTypeSet();
|
||||||
|
|
||||||
|
// Packets that must be executed on the main thread
|
||||||
|
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
|
||||||
|
|
||||||
|
// Which packets are buffered
|
||||||
|
private PacketTypeSet bufferedPackets = new PacketTypeSet();
|
||||||
|
private ListenerInvoker invoker;
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
private ErrorReporter reporter;
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
|
public NettyProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
|
||||||
|
this.injectionFactory = new NettyInjectionFactory(plugin);
|
||||||
|
this.invoker = invoker;
|
||||||
|
this.reporter = reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDebug() {
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the debug mode is enabled.
|
||||||
|
* @param debug - TRUE if it is, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setDebug(boolean debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject into the spigot connection class.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public synchronized void inject() {
|
||||||
|
if (injected)
|
||||||
|
throw new IllegalStateException("Cannot inject twice.");
|
||||||
|
try {
|
||||||
|
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
|
||||||
|
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
|
||||||
|
|
||||||
|
// Get the server connection
|
||||||
|
Object server = fuzzyServer.getSingleton();
|
||||||
|
Object serverConnection = null;
|
||||||
|
|
||||||
|
for (Method method : serverConnectionMethods) {
|
||||||
|
try {
|
||||||
|
serverConnection = method.invoke(server);
|
||||||
|
|
||||||
|
// Continue until we get a server connection
|
||||||
|
if (serverConnection != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Try the next though
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle connected channels
|
||||||
|
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(Channel channel) throws Exception {
|
||||||
|
try {
|
||||||
|
// This can take a while, so we need to stop the main thread from interfering
|
||||||
|
synchronized (networkManagers) {
|
||||||
|
injectionFactory.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
reporter.reportDetailed(NettyProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).
|
||||||
|
messageParam(channel).error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is executed before Minecraft's channel handler
|
||||||
|
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(Channel channel) throws Exception {
|
||||||
|
// Our only job is to add init protocol
|
||||||
|
channel.pipeline().addLast(endInitProtocol);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add our handler to newly created channels
|
||||||
|
final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
Channel channel = (Channel) msg;
|
||||||
|
|
||||||
|
// Prepare to initialize ths channel
|
||||||
|
channel.pipeline().addFirst(beginInitProtocol);
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the current NetworkMananger list
|
||||||
|
networkManagers = (List<Object>) FuzzyReflection.fromObject(serverConnection, true).
|
||||||
|
invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
|
||||||
|
|
||||||
|
// Insert ProtocolLib's connection interceptor
|
||||||
|
bootstrapFields = getBootstrapFields(serverConnection);
|
||||||
|
|
||||||
|
for (VolatileField field : bootstrapFields) {
|
||||||
|
final List<Object> list = (List<Object>) field.getValue();
|
||||||
|
|
||||||
|
// We don't have to override this list
|
||||||
|
if (list == networkManagers) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize with each list before we attempt to replace them.
|
||||||
|
field.setValue(new NettyBootstrapList(list, connectionHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
injected = true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to inject channel futures.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasListener(Class<?> packetClass) {
|
||||||
|
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMainThreadListener(Class<?> packetClass) {
|
||||||
|
return mainThreadFilters.contains(packetClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ErrorReporter getReporter() {
|
||||||
|
return reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject our packet handling into a specific player.
|
||||||
|
* @param player Player to inject into
|
||||||
|
*/
|
||||||
|
public void injectPlayer(Player player) {
|
||||||
|
injectionFactory.fromPlayer(player, this).inject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<VolatileField> getBootstrapFields(Object serverConnection) {
|
||||||
|
List<VolatileField> result = Lists.newArrayList();
|
||||||
|
|
||||||
|
// Find and (possibly) proxy every list
|
||||||
|
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
|
||||||
|
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Object> list = (List<Object>) volatileField.getValue();
|
||||||
|
|
||||||
|
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
|
||||||
|
result.add(volatileField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up any remaning injections.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void close() {
|
||||||
|
if (!closed) {
|
||||||
|
closed = true;
|
||||||
|
|
||||||
|
for (VolatileField field : bootstrapFields) {
|
||||||
|
Object value = field.getValue();
|
||||||
|
|
||||||
|
// Undo the processed channels, if any
|
||||||
|
if (value instanceof NettyBootstrapList) {
|
||||||
|
((NettyBootstrapList) value).close();
|
||||||
|
}
|
||||||
|
field.revertValue();
|
||||||
|
}
|
||||||
|
// Uninject all the players
|
||||||
|
injectionFactory.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
|
||||||
|
Class<?> clazz = packet.getClass();
|
||||||
|
|
||||||
|
if (sendingFilters.contains(clazz) || marker != null) {
|
||||||
|
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
|
||||||
|
return packetQueued(container, injector.getPlayer(), marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change anything
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
|
||||||
|
Class<?> clazz = packet.getClass();
|
||||||
|
|
||||||
|
if (reveivedFilters.contains(clazz) || marker != null) {
|
||||||
|
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
|
||||||
|
return packetReceived(container, injector.getPlayer(), marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't change anything
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includeBuffer(Class<?> packetClass) {
|
||||||
|
return bufferedPackets.contains(packetClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to inform the event listeners of a queued packet.
|
||||||
|
* @param packet - the packet that is to be sent.
|
||||||
|
* @param receiver - the receiver of this packet.
|
||||||
|
* @return The packet event that was used.
|
||||||
|
*/
|
||||||
|
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
|
||||||
|
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
|
||||||
|
|
||||||
|
invoker.invokePacketSending(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to inform the event listeners of a received packet.
|
||||||
|
* @param packet - the packet that has been receieved.
|
||||||
|
* @param sender - the client packet.
|
||||||
|
* @param marker - the network marker.
|
||||||
|
* @return The packet event that was used.
|
||||||
|
*/
|
||||||
|
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
|
||||||
|
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
|
||||||
|
|
||||||
|
invoker.invokePacketRecieving(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server side
|
||||||
|
@Override
|
||||||
|
public PlayerInjectionHandler getPlayerInjector() {
|
||||||
|
return new AbstractPlayerHandler(sendingFilters) {
|
||||||
|
private ChannelListener listener = NettyProtocolInjector.this;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getProtocolVersion(Player player) {
|
||||||
|
return injectionFactory.fromPlayer(player, listener).getProtocolVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePlayer(Player player) {
|
||||||
|
injectionFactory.fromPlayer(player, listener).inject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||||
|
injectionFactory.fromPlayer(player, listener).inject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||||
|
// Ignore this too
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||||
|
if (options != null && !options.contains(ListenerOptions.ASYNC))
|
||||||
|
mainThreadFilters.addType(type);
|
||||||
|
super.addPacketHandler(type, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePacketHandler(PacketType type) {
|
||||||
|
mainThreadFilters.removeType(type);
|
||||||
|
super.removePacketHandler(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean uninjectPlayer(Player player) {
|
||||||
|
// Just let Netty clean this up
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
|
||||||
|
injectionFactory.fromPlayer(receiver, listener).
|
||||||
|
sendServerPacket(packet.getHandle(), marker, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMainThreadListener(PacketType type) {
|
||||||
|
return mainThreadFilters.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||||
|
injectionFactory.fromPlayer(player, listener).
|
||||||
|
recieveClientPacket(mcPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
|
||||||
|
// Ignore this
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDisconnect(Player player) {
|
||||||
|
injectionFactory.fromPlayer(player, listener).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WrappedChannel getChannel(Player player) {
|
||||||
|
Injector injector = injectionFactory.fromPlayer(player, listener);
|
||||||
|
if (injector instanceof ChannelInjector) {
|
||||||
|
return ((ChannelInjector) injector).getChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a view of this protocol injector as a packet injector.
|
||||||
|
* @return The packet injector.
|
||||||
|
*/
|
||||||
|
// Client side
|
||||||
|
@Override
|
||||||
|
public PacketInjector getPacketInjector() {
|
||||||
|
return new AbstractPacketInjector(reveivedFilters) {
|
||||||
|
@Override
|
||||||
|
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||||
|
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
|
||||||
|
injectionFactory.fromPlayer(client, NettyProtocolInjector.this).
|
||||||
|
saveMarker(packet.getHandle(), marker);
|
||||||
|
return packetReceived(packet, client, marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inputBuffersChanged(Set<PacketType> set) {
|
||||||
|
bufferedPackets = new PacketTypeSet(set);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
|
* Copyright (C) 2015 dmulloy2
|
||||||
|
*
|
||||||
|
* 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.compat.netty.independent;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides
|
||||||
|
* all methods in {@link Socket} to ensure that calls are not mistakingly made
|
||||||
|
* to the unsupported super socket. All operations that can be sanely applied to
|
||||||
|
* a {@link Channel} are implemented here. Those which cannot will throw an
|
||||||
|
* {@link UnsupportedOperationException}.
|
||||||
|
*/
|
||||||
|
// Thanks MD5. :)
|
||||||
|
public class NettySocketAdapter extends Socket {
|
||||||
|
private final SocketChannel ch;
|
||||||
|
|
||||||
|
private NettySocketAdapter(SocketChannel ch) {
|
||||||
|
this.ch = ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NettySocketAdapter adapt(SocketChannel ch) {
|
||||||
|
return new NettySocketAdapter(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bind(SocketAddress bindpoint) throws IOException {
|
||||||
|
ch.bind(bindpoint).syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
ch.close().syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress endpoint) throws IOException {
|
||||||
|
ch.connect(endpoint).syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect(SocketAddress endpoint, int timeout) throws IOException {
|
||||||
|
ch.config().setConnectTimeoutMillis(timeout);
|
||||||
|
ch.connect(endpoint).syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof NettySocketAdapter && ch.equals(((NettySocketAdapter) obj).ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.nio.channels.SocketChannel getChannel() {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getInetAddress() {
|
||||||
|
return ch.remoteAddress().getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getKeepAlive() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.SO_KEEPALIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetAddress getLocalAddress() {
|
||||||
|
return ch.localAddress().getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLocalPort() {
|
||||||
|
return ch.localAddress().getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getLocalSocketAddress() {
|
||||||
|
return ch.localAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getOOBInline() throws SocketException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OutputStream getOutputStream() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPort() {
|
||||||
|
return ch.remoteAddress().getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getReceiveBufferSize() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.SO_RCVBUF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketAddress getRemoteSocketAddress() {
|
||||||
|
return ch.remoteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getReuseAddress() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.SO_REUSEADDR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getSendBufferSize() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.SO_SNDBUF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSoLinger() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.SO_LINGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int getSoTimeout() throws SocketException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getTcpNoDelay() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.TCP_NODELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrafficClass() throws SocketException {
|
||||||
|
return ch.config().getOption(ChannelOption.IP_TOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return ch.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBound() {
|
||||||
|
return ch.localAddress() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return !ch.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return ch.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInputShutdown() {
|
||||||
|
return ch.isInputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOutputShutdown() {
|
||||||
|
return ch.isOutputShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendUrgentData(int data) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKeepAlive(boolean on) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.SO_KEEPALIVE, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOOBInline(boolean on) throws SocketException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setReceiveBufferSize(int size) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.SO_RCVBUF, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReuseAddress(boolean on) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.SO_REUSEADDR, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSendBufferSize(int size) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.SO_SNDBUF, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSoLinger(boolean on, int linger) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.SO_LINGER, linger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTcpNoDelay(boolean on) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.TCP_NODELAY, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrafficClass(int tc) throws SocketException {
|
||||||
|
ch.config().setOption(ChannelOption.IP_TOS, tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownInput() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdownOutput() throws IOException {
|
||||||
|
ch.shutdownOutput().syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return ch.toString();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user