2013-07-06 00:24:11 +02:00
|
|
|
/*
|
|
|
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
|
|
|
* Copyright (C) 2012 Kristian S. Stangeland
|
|
|
|
*
|
2015-06-17 20:25:39 +02:00
|
|
|
* 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
|
2013-07-06 00:24:11 +02:00
|
|
|
* the License, or (at your option) any later version.
|
|
|
|
*
|
2015-06-17 20:25:39 +02:00
|
|
|
* 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.
|
2013-07-06 00:24:11 +02:00
|
|
|
* See the GNU General Public License for more details.
|
|
|
|
*
|
2015-06-17 20:25:39 +02:00
|
|
|
* 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
|
2013-07-06 00:24:11 +02:00
|
|
|
* 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.comphenix.protocol.injector;
|
|
|
|
|
|
|
|
import com.comphenix.protocol.ProtocolLibrary;
|
|
|
|
import com.comphenix.protocol.error.ErrorReporter;
|
|
|
|
import com.comphenix.protocol.error.Report;
|
|
|
|
import com.comphenix.protocol.error.ReportType;
|
|
|
|
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
2022-07-24 16:16:05 +02:00
|
|
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
|
|
|
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
2013-07-06 00:24:11 +02:00
|
|
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
2016-08-13 19:19:13 +02:00
|
|
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
2013-07-06 00:24:11 +02:00
|
|
|
import com.google.common.primitives.Primitives;
|
2022-03-08 04:09:04 +01:00
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import org.bukkit.Bukkit;
|
|
|
|
import org.bukkit.entity.Player;
|
2013-07-06 00:24:11 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
|
|
|
|
* <p>
|
|
|
|
* Typical conversions include:
|
|
|
|
* <ul>
|
2015-06-17 20:25:39 +02:00
|
|
|
* <li>org.bukkit.entity.Player to net.minecraft.server.EntityPlayer</li>
|
|
|
|
* <li>org.bukkit.World to net.minecraft.server.WorldServer</li>
|
2013-07-06 00:24:11 +02:00
|
|
|
* </ul>
|
2022-03-08 04:09:04 +01:00
|
|
|
*
|
2013-07-06 00:24:11 +02:00
|
|
|
* @author Kristian
|
|
|
|
*/
|
|
|
|
public class BukkitUnwrapper implements Unwrapper {
|
2013-12-04 04:17:02 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
|
|
|
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
|
|
|
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
|
|
|
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
2022-03-08 04:09:04 +01:00
|
|
|
|
|
|
|
private static final Map<Class<?>, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
|
|
|
private static BukkitUnwrapper DEFAULT;
|
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
// The current error reporter
|
|
|
|
private final ErrorReporter reporter;
|
2022-03-08 04:09:04 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
|
|
|
*/
|
|
|
|
public BukkitUnwrapper() {
|
|
|
|
this(ProtocolLibrary.getErrorReporter());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a new Bukkit unwrapper with the given error reporter.
|
|
|
|
*
|
|
|
|
* @param reporter - the error reporter to use.
|
|
|
|
*/
|
|
|
|
public BukkitUnwrapper(ErrorReporter reporter) {
|
|
|
|
this.reporter = reporter;
|
|
|
|
}
|
|
|
|
|
2013-12-04 04:17:02 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the default instance of the Bukkit unwrapper.
|
2022-03-08 04:09:04 +01:00
|
|
|
*
|
2013-12-04 04:17:02 +01:00
|
|
|
* @return The default instance.
|
|
|
|
*/
|
|
|
|
public static BukkitUnwrapper getInstance() {
|
|
|
|
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-12-04 04:17:02 +01:00
|
|
|
// Also recreate the unwrapper if the error reporter has changed
|
|
|
|
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
|
|
|
|
DEFAULT = new BukkitUnwrapper(currentReporter);
|
|
|
|
}
|
|
|
|
return DEFAULT;
|
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
|
|
|
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
|
|
|
|
if (expected.isAssignableFrom(input)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
return null;
|
2013-07-06 00:24:11 +02:00
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
@Override
|
|
|
|
public Object unwrapItem(Object wrappedObject) {
|
|
|
|
// Special case
|
2022-03-08 04:09:04 +01:00
|
|
|
if (wrappedObject == null) {
|
2013-07-06 00:24:11 +02:00
|
|
|
return null;
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
2013-11-19 00:50:55 +01:00
|
|
|
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-11-19 00:50:55 +01:00
|
|
|
// No need to unwrap primitives
|
2022-03-08 04:09:04 +01:00
|
|
|
if (currentClass.isPrimitive() || currentClass.equals(String.class)) {
|
2013-11-19 00:50:55 +01:00
|
|
|
return null;
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
// Next, check for types that doesn't have a getHandle()
|
|
|
|
if (wrappedObject instanceof Collection) {
|
|
|
|
return handleCollection((Collection<Object>) wrappedObject);
|
|
|
|
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
// Retrieve the handle
|
2022-03-08 04:09:04 +01:00
|
|
|
if (specificUnwrapper != null) {
|
2013-07-06 00:24:11 +02:00
|
|
|
return specificUnwrapper.unwrapItem(wrappedObject);
|
2022-03-08 04:09:04 +01:00
|
|
|
} else {
|
2013-07-06 00:24:11 +02:00
|
|
|
return null;
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
2013-07-06 00:24:11 +02:00
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
// Handle a collection of items
|
|
|
|
private Object handleCollection(Collection<Object> wrappedObject) {
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
if (copy != null) {
|
|
|
|
// Unwrap every element
|
|
|
|
for (Object element : wrappedObject) {
|
|
|
|
copy.add(unwrapItem(element));
|
|
|
|
}
|
|
|
|
return copy;
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
} else {
|
|
|
|
// Impossible
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
/**
|
|
|
|
* Retrieve a cached class unwrapper for the given class.
|
2022-03-08 04:09:04 +01:00
|
|
|
*
|
2013-07-06 00:24:11 +02:00
|
|
|
* @param type - the type of the class.
|
|
|
|
* @return An unwrapper for the given class.
|
|
|
|
*/
|
2013-11-19 00:50:55 +01:00
|
|
|
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
|
2013-07-06 00:24:11 +02:00
|
|
|
// See if we're already determined this
|
2022-03-08 04:09:04 +01:00
|
|
|
if (UNWRAPPER_CACHE.containsKey(type)) {
|
2013-07-06 00:24:11 +02:00
|
|
|
// We will never remove from the cache, so this ought to be thread safe
|
2022-03-08 04:09:04 +01:00
|
|
|
return UNWRAPPER_CACHE.get(type);
|
2013-07-06 00:24:11 +02:00
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
try {
|
|
|
|
final Method find = type.getMethod("getHandle");
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2015-06-17 20:25:39 +02:00
|
|
|
// It's thread safe, as getMethod should return the same handle
|
2013-07-06 00:24:11 +02:00
|
|
|
Unwrapper methodUnwrapper = new Unwrapper() {
|
|
|
|
@Override
|
|
|
|
public Object unwrapItem(Object wrappedObject) {
|
|
|
|
try {
|
2022-03-08 04:09:04 +01:00
|
|
|
if (wrappedObject instanceof Class) {
|
2013-11-19 00:50:55 +01:00
|
|
|
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
2013-07-06 00:24:11 +02:00
|
|
|
return find.invoke(wrappedObject);
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
} catch (IllegalArgumentException e) {
|
2015-06-17 20:25:39 +02:00
|
|
|
reporter.reportDetailed(this,
|
2013-07-06 00:24:11 +02:00
|
|
|
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
|
|
|
);
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
// Should not occur either
|
|
|
|
return null;
|
|
|
|
} catch (InvocationTargetException e) {
|
|
|
|
// This is really bad
|
|
|
|
throw new RuntimeException("Minecraft error.", e);
|
|
|
|
}
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
2022-03-08 04:09:04 +01:00
|
|
|
|
|
|
|
UNWRAPPER_CACHE.put(type, methodUnwrapper);
|
2013-07-06 00:24:11 +02:00
|
|
|
return methodUnwrapper;
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
} catch (SecurityException e) {
|
2015-06-17 20:25:39 +02:00
|
|
|
reporter.reportDetailed(this,
|
2013-07-06 00:24:11 +02:00
|
|
|
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
|
|
|
|
);
|
|
|
|
} catch (NoSuchMethodException e) {
|
2016-08-13 19:19:13 +02:00
|
|
|
// Maybe it's a proxy?
|
|
|
|
Unwrapper proxyUnwrapper = getProxyUnwrapper(type);
|
2022-03-08 04:09:04 +01:00
|
|
|
if (proxyUnwrapper != null) {
|
2016-08-13 19:19:13 +02:00
|
|
|
return proxyUnwrapper;
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
2016-08-13 19:19:13 +02:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
// Try getting the field unwrapper too
|
|
|
|
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
2022-03-08 04:09:04 +01:00
|
|
|
if (fieldUnwrapper != null) {
|
2013-07-06 00:24:11 +02:00
|
|
|
return fieldUnwrapper;
|
2022-03-08 04:09:04 +01:00
|
|
|
} else {
|
2015-06-17 20:25:39 +02:00
|
|
|
reporter.reportDetailed(this,
|
2013-07-06 00:24:11 +02:00
|
|
|
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
2013-07-06 00:24:11 +02:00
|
|
|
}
|
2016-08-13 19:19:13 +02:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
// Default method
|
|
|
|
return null;
|
|
|
|
}
|
2016-08-13 19:19:13 +02:00
|
|
|
|
|
|
|
// Players should /always/ be able to be unwrapped
|
|
|
|
// We should only get here if the 'Player' is a proxy
|
|
|
|
private Unwrapper getProxyUnwrapper(final Class<?> type) {
|
|
|
|
try {
|
|
|
|
if (Player.class.isAssignableFrom(type)) {
|
|
|
|
final Method getHandle = MinecraftReflection.getCraftPlayerClass().getMethod("getHandle");
|
|
|
|
|
|
|
|
Unwrapper unwrapper = new Unwrapper() {
|
|
|
|
@Override
|
|
|
|
public Object unwrapItem(Object wrapped) {
|
|
|
|
try {
|
|
|
|
return getHandle.invoke(((Player) wrapped).getPlayer());
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
try {
|
|
|
|
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
|
|
|
|
} catch (ReflectiveOperationException ex1) {
|
|
|
|
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-08 04:09:04 +01:00
|
|
|
UNWRAPPER_CACHE.put(type, unwrapper);
|
2016-08-13 19:19:13 +02:00
|
|
|
return unwrapper;
|
|
|
|
}
|
|
|
|
} catch (Throwable ignored) {
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
/**
|
|
|
|
* Retrieve a cached unwrapper using the handle field.
|
2022-03-08 04:09:04 +01:00
|
|
|
*
|
2013-07-06 00:24:11 +02:00
|
|
|
* @param type - a cached field unwrapper.
|
|
|
|
* @return The cached field unwrapper.
|
|
|
|
*/
|
2013-11-19 00:50:55 +01:00
|
|
|
private Unwrapper getFieldUnwrapper(final Class<?> type) {
|
2013-07-06 00:24:11 +02:00
|
|
|
// See if we succeeded
|
2022-07-24 16:16:05 +02:00
|
|
|
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(type, "handle", null);
|
|
|
|
if (accessor != null) {
|
2013-07-06 00:24:11 +02:00
|
|
|
Unwrapper fieldUnwrapper = new Unwrapper() {
|
|
|
|
@Override
|
|
|
|
public Object unwrapItem(Object wrappedObject) {
|
|
|
|
try {
|
2022-03-08 04:09:04 +01:00
|
|
|
if (wrappedObject instanceof Class) {
|
2022-07-24 16:16:05 +02:00
|
|
|
return checkClass((Class<?>) wrappedObject, type, accessor.getField().getType());
|
2022-03-08 04:09:04 +01:00
|
|
|
}
|
2022-07-24 16:16:05 +02:00
|
|
|
|
|
|
|
return accessor.get(wrappedObject);
|
|
|
|
} catch (IllegalStateException e) {
|
2015-06-17 20:25:39 +02:00
|
|
|
reporter.reportDetailed(this,
|
2022-07-24 16:16:05 +02:00
|
|
|
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e)
|
|
|
|
.callerParam(wrappedObject, accessor.getField())
|
2013-07-06 00:24:11 +02:00
|
|
|
);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2022-03-08 04:09:04 +01:00
|
|
|
|
|
|
|
UNWRAPPER_CACHE.put(type, fieldUnwrapper);
|
2013-07-06 00:24:11 +02:00
|
|
|
return fieldUnwrapper;
|
2022-03-08 04:09:04 +01:00
|
|
|
|
2013-07-06 00:24:11 +02:00
|
|
|
} else {
|
|
|
|
// Inform about this too
|
2015-06-17 20:25:39 +02:00
|
|
|
reporter.reportDetailed(this,
|
2022-07-24 16:16:05 +02:00
|
|
|
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(type)
|
2013-07-06 00:24:11 +02:00
|
|
|
);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|