ProtocolLib/src/main/java/com/comphenix/protocol/utility/RemappedClassSource.java

156 lines
4.8 KiB
Java

package com.comphenix.protocol.utility;
// Thanks to Bergerkiller for his excellent hack. :D
// Copyright (C) 2013 bergerkiller
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import java.lang.reflect.Method;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason;
class RemappedClassSource extends ClassSource {
private Object classRemapper;
private Method mapType;
private ClassLoader loader;
/**
* Construct a new remapped class source using the default class loader.
*/
public RemappedClassSource() {
this(RemappedClassSource.class.getClassLoader());
}
/**
* Construct a new renampped class source with the provided class loader.
* @param loader - the class loader.
*/
public RemappedClassSource(ClassLoader loader) {
this.loader = loader;
}
/**
* Attempt to load the MCPC remapper.
* @return TRUE if we succeeded, FALSE otherwise.
* @throws RemapperUnavailableException If the remapper is not present.
*/
public RemappedClassSource initialize() {
try {
Server server = Bukkit.getServer();
if (server == null) {
throw new IllegalStateException("Bukkit not initialized.");
}
String version = server.getVersion();
if (!version.contains("MCPC") && !version.contains("Cauldron")) {
throw new RemapperUnavailableException(Reason.MCPC_NOT_PRESENT);
}
// Obtain the Class remapper used by MCPC+/Cauldron
this.classRemapper = FieldUtils.readField(getClass().getClassLoader(), "remapper", true);
if (this.classRemapper == null) {
throw new RemapperUnavailableException(Reason.REMAPPER_DISABLED);
}
// Initialize some fields and methods used by the Jar Remapper
Class<?> renamerClazz = classRemapper.getClass();
this.mapType = MethodUtils.getAccessibleMethod(renamerClazz, "map",
new Class<?>[] { String.class });
return this;
} catch (RemapperUnavailableException e) {
throw e;
} catch (Exception e) {
// Damn it
throw new RuntimeException("Cannot access MCPC remapper.", e);
}
}
@Override
public Class<?> loadClass(String canonicalName) throws ClassNotFoundException {
final String remapped = getClassName(canonicalName);
try {
return loader.loadClass(remapped);
} catch (ClassNotFoundException e) {
throw new ClassNotFoundException("Cannot find " + canonicalName + "(Remapped: " + remapped + ")");
}
}
/**
* Retrieve the obfuscated class name given an unobfuscated canonical class name.
* @param path - the canonical class name.
* @return The obfuscated class name.
*/
public String getClassName(String path) {
try {
String remapped = (String) mapType.invoke(classRemapper, path.replace('.', '/'));
return remapped.replace('/', '.');
} catch (Exception e) {
throw new RuntimeException("Cannot remap class name.", e);
}
}
public static class RemapperUnavailableException extends RuntimeException {
private static final long serialVersionUID = 1L;
public enum Reason {
MCPC_NOT_PRESENT("The server is not running MCPC+/Cauldron"),
REMAPPER_DISABLED("Running an MCPC+/Cauldron server but the remapper is unavailable. Please turn it on!");
private final String message;
private Reason(String message) {
this.message = message;
}
/**
* Retrieve a human-readable version of this reason.
* @return The human-readable verison.
*/
public String getMessage() {
return message;
}
}
private final Reason reason;
public RemapperUnavailableException(Reason reason) {
super(reason.getMessage());
this.reason = reason;
}
/**
* Retrieve the reasont he remapper is unavailable.
* @return The reason.
*/
public Reason getReason() {
return reason;
}
}
}