282 lines
9.1 KiB
Java
282 lines
9.1 KiB
Java
package com.comphenix.protocol.utility;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.Callable;
|
|
|
|
import com.comphenix.protocol.PacketType;
|
|
import com.comphenix.protocol.events.PacketContainer;
|
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
|
|
|
import io.netty.buffer.Unpooled;
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import net.bytebuddy.dynamic.DynamicType;
|
|
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
|
import net.bytebuddy.implementation.MethodDelegation;
|
|
import net.bytebuddy.implementation.bind.annotation.Origin;
|
|
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
|
import net.bytebuddy.implementation.bind.annotation.SuperCall;
|
|
import net.bytebuddy.matcher.ElementMatchers;
|
|
|
|
/**
|
|
* Static methods for accessing Minecraft methods.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class MinecraftMethods {
|
|
// For player connection
|
|
private volatile static Method sendPacketMethod;
|
|
|
|
// For network manager
|
|
private volatile static Method networkManagerHandle;
|
|
private volatile static Method networkManagerPacketRead;
|
|
|
|
// For packet
|
|
private volatile static Method packetReadByteBuf;
|
|
private volatile static Method packetWriteByteBuf;
|
|
|
|
private static Constructor<?> proxyConstructor;
|
|
|
|
/**
|
|
* Retrieve the send packet method in PlayerConnection/NetServerHandler.
|
|
* @return The send packet method.
|
|
*/
|
|
public static Method getSendPacketMethod() {
|
|
if (sendPacketMethod == null) {
|
|
Class<?> serverHandlerClass = MinecraftReflection.getPlayerConnectionClass();
|
|
|
|
try {
|
|
sendPacketMethod = FuzzyReflection
|
|
.fromClass(serverHandlerClass)
|
|
.getMethod(FuzzyMethodContract.newBuilder()
|
|
.nameRegex("sendPacket.*")
|
|
.returnTypeVoid()
|
|
.parameterCount(1)
|
|
.build());
|
|
} catch (IllegalArgumentException e) {
|
|
// We can't use the method below on Netty
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
sendPacketMethod = FuzzyReflection.fromClass(serverHandlerClass).
|
|
getMethodByParameters("sendPacket", MinecraftReflection.getPacketClass());
|
|
return sendPacketMethod;
|
|
}
|
|
|
|
Map<String, Method> netServer = getMethodList(
|
|
serverHandlerClass, MinecraftReflection.getPacketClass());
|
|
Map<String, Method> netHandler = getMethodList(
|
|
MinecraftReflection.getNetHandlerClass(), MinecraftReflection.getPacketClass());
|
|
|
|
// Remove every method in net handler from net server
|
|
for (String methodName : netHandler.keySet()) {
|
|
netServer.remove(methodName);
|
|
}
|
|
|
|
// The remainder is the send packet method
|
|
if (netServer.size() == 1) {
|
|
Method[] methods = netServer.values().toArray(new Method[0]);
|
|
sendPacketMethod = methods[0];
|
|
} else {
|
|
throw new IllegalArgumentException("Unable to find the sendPacket method in NetServerHandler/PlayerConnection.");
|
|
}
|
|
}
|
|
}
|
|
return sendPacketMethod;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the disconnect method for a given player connection.
|
|
* @param playerConnection - the player connection.
|
|
* @return The
|
|
*/
|
|
public static Method getDisconnectMethod(Class<?> playerConnection) {
|
|
try {
|
|
return FuzzyReflection.fromClass(playerConnection).getMethodByName("disconnect.*");
|
|
} catch (IllegalArgumentException e) {
|
|
// Just assume it's the first String method
|
|
return FuzzyReflection.fromObject(playerConnection).getMethodByParameters("disconnect", String.class);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the handle/send packet method of network manager.
|
|
* <p>
|
|
* This only exists in version 1.7.2 and above.
|
|
* @return The handle method.
|
|
*/
|
|
public static Method getNetworkManagerHandleMethod() {
|
|
if (networkManagerHandle == null) {
|
|
networkManagerHandle = FuzzyReflection
|
|
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
|
|
.getMethod(FuzzyMethodContract.newBuilder()
|
|
.banModifier(Modifier.STATIC)
|
|
.returnTypeVoid()
|
|
.parameterCount(1)
|
|
.parameterExactType(MinecraftReflection.getPacketClass())
|
|
.build());
|
|
networkManagerHandle.setAccessible(true);
|
|
}
|
|
|
|
return networkManagerHandle;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the packetRead(ChannelHandlerContext, Packet) method of NetworkManager.
|
|
* <p>
|
|
* This only exists in version 1.7.2 and above.
|
|
* @return The packetRead method.
|
|
*/
|
|
public static Method getNetworkManagerReadPacketMethod() {
|
|
if (networkManagerPacketRead == null) {
|
|
networkManagerPacketRead = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true).
|
|
getMethodByParameters("packetRead", ChannelHandlerContext.class, MinecraftReflection.getPacketClass());
|
|
networkManagerPacketRead.setAccessible(true);
|
|
}
|
|
return networkManagerPacketRead;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a method mapped list of every method with the given signature.
|
|
* @param source - class source.
|
|
* @param params - parameters.
|
|
* @return Method mapped list.
|
|
*/
|
|
private static Map<String, Method> getMethodList(Class<?> source, Class<?>... params) {
|
|
FuzzyReflection reflect = FuzzyReflection.fromClass(source, true);
|
|
|
|
return reflect.getMappedMethods(
|
|
reflect.getMethodListByParameters(Void.TYPE, params)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieve the Packet.read(PacketDataSerializer) method.
|
|
* <p>
|
|
* This only exists in version 1.7.2 and above.
|
|
* @return The packet read method.
|
|
*/
|
|
public static Method getPacketReadByteBufMethod() {
|
|
initializePacket();
|
|
return packetReadByteBuf;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the Packet.write(PacketDataSerializer) method.
|
|
* <p>
|
|
* This only exists in version 1.7.2 and above.
|
|
* @return The packet write method.
|
|
*/
|
|
public static Method getPacketWriteByteBufMethod() {
|
|
initializePacket();
|
|
return packetWriteByteBuf;
|
|
}
|
|
|
|
private static Constructor<?> setupProxyConstructor()
|
|
{
|
|
try {
|
|
return ByteBuddyFactory.getInstance()
|
|
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
|
|
.name(MinecraftMethods.class.getPackage().getName() + ".PacketDecorator")
|
|
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
|
|
.intercept(MethodDelegation.to(new Object() {
|
|
@RuntimeType
|
|
public Object delegate(@SuperCall Callable<?> zuper, @Origin Method method) throws Exception {
|
|
if (method.getName().contains("read")) {
|
|
throw new ReadMethodException();
|
|
}
|
|
|
|
if (method.getName().contains("write")) {
|
|
throw new WriteMethodException();
|
|
}
|
|
return zuper.call();
|
|
}
|
|
}))
|
|
.make()
|
|
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
|
|
.getLoaded()
|
|
.getDeclaredConstructor(MinecraftReflection.getByteBufClass());
|
|
} catch (NoSuchMethodException e) {
|
|
throw new RuntimeException("Failed to find constructor!", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the two read() and write() methods.
|
|
*/
|
|
private static void initializePacket() {
|
|
|
|
// Initialize the methods
|
|
if (packetReadByteBuf == null || packetWriteByteBuf == null) {
|
|
if (proxyConstructor == null)
|
|
proxyConstructor = setupProxyConstructor();
|
|
|
|
final Object javaProxy;
|
|
try {
|
|
javaProxy = proxyConstructor.newInstance(Unpooled.buffer());
|
|
} catch (IllegalAccessException e) {
|
|
throw new RuntimeException("Cannot access reflection.", e);
|
|
} catch (InstantiationException e) {
|
|
throw new RuntimeException("Cannot instantiate object.", e);
|
|
} catch (InvocationTargetException e) {
|
|
throw new RuntimeException("Error in invocation.", e);
|
|
}
|
|
|
|
final Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle();
|
|
final List<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
|
|
.getMethodListByParameters(Void.TYPE, new Class<?>[] { MinecraftReflection.getPacketDataSerializerClass() });
|
|
|
|
// Look through all the methods
|
|
for (Method method : candidates) {
|
|
try {
|
|
method.invoke(lookPacket, javaProxy);
|
|
} catch (InvocationTargetException e) {
|
|
if (e.getCause() instanceof ReadMethodException) {
|
|
// Must be the reader
|
|
packetReadByteBuf = method;
|
|
} else if (e.getCause() instanceof WriteMethodException) {
|
|
packetWriteByteBuf = method;
|
|
} else {
|
|
// throw new RuntimeException("Inner exception.", e);
|
|
}
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Generic reflection error.", e);
|
|
}
|
|
}
|
|
|
|
if (packetReadByteBuf == null)
|
|
throw new IllegalStateException("Unable to find Packet.read(PacketDataSerializer)");
|
|
if (packetWriteByteBuf == null)
|
|
throw new IllegalStateException("Unable to find Packet.write(PacketDataSerializer)");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An internal exception used to detect read methods.
|
|
* @author Kristian
|
|
*/
|
|
private static class ReadMethodException extends RuntimeException {
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
public ReadMethodException() {
|
|
super("A read method was executed.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An internal exception used to detect write methods.
|
|
* @author Kristian
|
|
*/
|
|
private static class WriteMethodException extends RuntimeException {
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
public WriteMethodException() {
|
|
super("A write method was executed.");
|
|
}
|
|
}
|
|
}
|