--- ../src-base/minecraft/org/bukkit/plugin/java/PluginClassLoader.java +++ ../src-work/minecraft/org/bukkit/plugin/java/PluginClassLoader.java @@ -1,5 +1,23 @@ package org.bukkit.plugin.java; +// Cauldron start +import net.md_5.specialsource.provider.ClassLoaderProvider; +import net.md_5.specialsource.transformer.MavenShade; +//import org.bouncycastle.util.io.Streams; +import net.md_5.specialsource.*; +import net.md_5.specialsource.repo.*; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.cauldron.configuration.CauldronConfig; +import net.minecraftforge.cauldron.CauldronUtils; + +import org.bukkit.plugin.PluginDescriptionFile; +import java.io.*; +import java.net.JarURLConnection; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.util.concurrent.*; +// Cauldron end + import java.io.File; import java.net.MalformedURLException; import java.net.URL; @@ -15,16 +33,40 @@ /** * A ClassLoader for plugins, to allow shared classes across multiple plugins */ -final class PluginClassLoader extends URLClassLoader { +public class PluginClassLoader extends URLClassLoader { private final JavaPluginLoader loader; - private final Map> classes = new HashMap>(); + private final ConcurrentMap> classes = new ConcurrentHashMap>(); // Cauldron - Threadsafe classloading private final PluginDescriptionFile description; private final File dataFolder; private final File file; - final JavaPlugin plugin; + JavaPlugin plugin; // Cauldron - remove final private JavaPlugin pluginInit; private IllegalStateException pluginState; + // Cauldron start + private JarRemapper remapper; // class remapper for this plugin, or null + private RemapperProcessor remapperProcessor; // secondary; for inheritance & remapping reflection + private boolean debug; // classloader debugging + private int remapFlags = -1; + private static ConcurrentMap jarMappings = new ConcurrentHashMap(); + private static final int F_GLOBAL_INHERIT = 1 << 1; + private static final int F_REMAP_OBCPRE = 1 << 2; + private static final int F_REMAP_NMS152 = 1 << 3; + private static final int F_REMAP_NMS164 = 1 << 4; + private static final int F_REMAP_NMS172 = 1 << 5; + private static final int F_REMAP_NMS179 = 1 << 6; + private static final int F_REMAP_NMS1710 = 1 << 7; + private static final int F_REMAP_OBC152 = 1 << 8; + private static final int F_REMAP_OBC164 = 1 << 9; + private static final int F_REMAP_OBC172 = 1 << 10; + private static final int F_REMAP_OBC179 = 1 << 11; + private static final int F_REMAP_OBC1710 = 1 << 12; + private static final int F_REMAP_NMSPRE_MASK= 0xffff0000; // "unversioned" NMS plugin version + + // This trick bypasses Maven Shade's package rewriting when using String literals [same trick in jline] + private static final String org_bukkit_craftbukkit = new String(new char[] {'o','r','g','/','b','u','k','k','i','t','/','c','r','a','f','t','b','u','k','k','i','t'}); + // Cauldron end + PluginClassLoader(final JavaPluginLoader loader, final ClassLoader parent, final PluginDescriptionFile description, final File dataFolder, final File file) throws InvalidPluginException, MalformedURLException { super(new URL[] {file.toURI().toURL()}, parent); Validate.notNull(loader, "Loader cannot be null"); @@ -34,6 +76,113 @@ this.dataFolder = dataFolder; this.file = file; + // Cauldron start + + String pluginName = this.description.getName(); + + // configure default remapper settings + boolean useCustomClassLoader = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.custom-class-loader", true); + debug = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.debug", false); + boolean remapNMS1710 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-nms-v1_7_R4", true); + boolean remapNMS179 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-nms-v1_7_R3", true); + boolean remapNMS172 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-nms-v1_7_R1", true); + boolean remapNMS164 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-nms-v1_6_R3", true); + boolean remapNMS152 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-nms-v1_5_R3", true); + String remapNMSPre = MinecraftServer.getServer().cauldronConfig.getString("plugin-settings.default.remap-nms-pre", "false"); + boolean remapOBC1710 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-obc-v1_7_R4", true); + boolean remapOBC179 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-obc-v1_7_R3", true); + boolean remapOBC172 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-obc-v1_7_R1", true); + boolean remapOBC164 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-obc-v1_6_R3", true); + boolean remapOBC152 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-obc-v1_5_R3", true); + boolean remapOBCPre = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-obc-pre", false); + boolean globalInherit = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.global-inheritance", true); + boolean pluginInherit = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.plugin-inheritance", true); + boolean reflectFields = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-reflect-field", true); + boolean reflectClass = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-reflect-class", true); + boolean allowFuture = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings.default.remap-allow-future", false); + + // plugin-specific overrides + useCustomClassLoader = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".custom-class-loader", useCustomClassLoader, false); + debug = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".debug", debug, false); + remapNMS1710 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-nms-v1_7_R4", remapNMS1710, false); + remapNMS179 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-nms-v1_7_R3", remapNMS179, false); + remapNMS172 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-nms-v1_7_R1", remapNMS172, false); + remapNMS164 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-nms-v1_6_R3", remapNMS164, false); + remapNMS152 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-nms-v1_5_R3", remapNMS152, false); + remapNMSPre = MinecraftServer.getServer().cauldronConfig.getString("plugin-settings."+pluginName+".remap-nms-pre", remapNMSPre, false); + remapOBC1710 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-obc-v1_7_R4", remapOBC1710, false); + remapOBC179 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-obc-v1_7_R3", remapOBC179, false); + remapOBC172 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-obc-v1_7_R1", remapOBC172, false); + remapOBC164 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-obc-v1_6_R3", remapOBC164, false); + remapOBC152 = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-obc-v1_5_R3", remapOBC152, false); + remapOBCPre = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-obc-pre", remapOBCPre, false); + globalInherit = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".global-inheritance", globalInherit, false); + pluginInherit = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".plugin-inheritance", pluginInherit, false); + reflectFields = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-reflect-field", reflectFields, false); + reflectClass = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-reflect-class", reflectClass, false); + allowFuture = MinecraftServer.getServer().cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-allow-future", allowFuture, false); + + if (debug) { + System.out.println("PluginClassLoader debugging enabled for "+pluginName); + } + + if (!useCustomClassLoader) { + remapper = null; + return; + } + + int flags = 0; + if (remapNMS1710) flags |= F_REMAP_NMS1710; + if (remapNMS179) flags |= F_REMAP_NMS179; + if (remapNMS172) flags |= F_REMAP_NMS172; + if (remapNMS164) flags |= F_REMAP_NMS164; + if (remapNMS152) flags |= F_REMAP_NMS152; + if (!remapNMSPre.equals("false")) { + if (remapNMSPre.equals("1.7.10")) flags |= 0x17100000; + else if (remapNMSPre.equals("1.7.9")) flags |= 0x01790000; + else if (remapNMSPre.equals("1.7.2")) flags |= 0x01720000; + else if (remapNMSPre.equals("1.6.4")) flags |= 0x01640000; + else if (remapNMSPre.equals("1.5.2")) flags |= 0x01520000; + else { + System.out.println("Unsupported nms-remap-pre version '"+remapNMSPre+"', disabling"); + } + } + if (remapOBC1710) flags |= F_REMAP_OBC1710; + if (remapOBC179) flags |= F_REMAP_OBC179; + if (remapOBC172) flags |= F_REMAP_OBC172; + if (remapOBC164) flags |= F_REMAP_OBC164; + if (remapOBC152) flags |= F_REMAP_OBC152; + if (remapOBCPre) flags |= F_REMAP_OBCPRE; + if (globalInherit) flags |= F_GLOBAL_INHERIT; + + remapFlags = flags; // used in findClass0 + JarMapping jarMapping = getJarMapping(flags); + + // Load inheritance map + if ((flags & F_GLOBAL_INHERIT) != 0) { + if (debug) { + System.out.println("Enabling global inheritance remapping"); + //ClassLoaderProvider.verbose = debug; // TODO: changed in https://github.com/md-5/SpecialSource/commit/132584eda4f0860c9d14f4c142e684a027a128b8#L3L48 + } + jarMapping.setInheritanceMap(loader.getGlobalInheritanceMap()); + jarMapping.setFallbackInheritanceProvider(new ClassLoaderProvider(this)); + } + + remapper = new JarRemapper(jarMapping); + + if (pluginInherit || reflectFields || reflectClass) { + remapperProcessor = new RemapperProcessor( + pluginInherit ? loader.getGlobalInheritanceMap() : null, + (reflectFields || reflectClass) ? jarMapping : null); + + remapperProcessor.setRemapReflectField(reflectFields); + remapperProcessor.setRemapReflectClass(reflectClass); + remapperProcessor.debug = debug; + } else { + remapperProcessor = null; + } + // Cauldron end + try { Class jarClass; try { @@ -58,34 +207,290 @@ } @Override - protected Class findClass(String name) throws ClassNotFoundException { + public Class findClass(String name) throws ClassNotFoundException { // Cauldron - public access for plugins to support CB NMS -> MCP class remap return findClass(name, true); } - Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { - if (name.startsWith("org.bukkit.") || name.startsWith("net.minecraft.")) { - throw new ClassNotFoundException(name); + // Cauldron start + /** + * Get the "native" obfuscation version, from our Maven shading version. + */ + public static String getNativeVersion() { + // see https://github.com/mbax/VanishNoPacket/blob/master/src/main/java/org/kitteh/vanish/compat/NMSManager.java + if (CauldronUtils.deobfuscatedEnvironment()) return "v1_7_R4"; // support plugins in deobf environment + final String packageName = org.bukkit.craftbukkit.CraftServer.class.getPackage().getName(); + return packageName.substring(packageName.lastIndexOf('.') + 1); + } + + /** + * Load NMS mappings from CraftBukkit mc-dev to repackaged srgnames for FML runtime deobf + * + * @param jarMapping An existing JarMappings instance to load into + * @param obfVersion CraftBukkit version with internal obfuscation counter identifier + * >=1.4.7 this is the major version + R#. v1_4_R1=1.4.7, v1_5_R1=1.5, v1_5_R2=1.5.1.. + * For older versions (including pre-safeguard) it is the full Minecraft version number + * @throws IOException + */ + private void loadNmsMappings(JarMapping jarMapping, String obfVersion) throws IOException { + Map relocations = new HashMap(); + // mc-dev jar to CB, apply version shading (aka plugin safeguard) + relocations.put("net.minecraft.server", "net.minecraft.server." + obfVersion); + + // support for running 1.7.10 plugins in Cauldron dev + if (CauldronUtils.deobfuscatedEnvironment() && obfVersion.equals("v1_7_R4")) + { + jarMapping.loadMappings( + new BufferedReader(new InputStreamReader(loader.getClass().getClassLoader().getResourceAsStream("mappings/"+obfVersion+"/cb2pkgmcp.srg"))), + new MavenShade(relocations), + null, false); + + jarMapping.loadMappings( + new BufferedReader(new InputStreamReader(loader.getClass().getClassLoader().getResourceAsStream("mappings/"+obfVersion+"/obf2pkgmcp.srg"))), + null, // no version relocation for obf + null, false); + // resolve naming conflict in FML/CB + jarMapping.methods.put("net/minecraft/server/"+obfVersion+"/PlayerConnection/getPlayer ()Lorg/bukkit/craftbukkit/entity/CraftPlayer;", "getPlayerB"); } - Class result = classes.get(name); + else + { + jarMapping.loadMappings( + new BufferedReader(new InputStreamReader(loader.getClass().getClassLoader().getResourceAsStream("mappings/"+obfVersion+"/cb2numpkg.srg"))), + new MavenShade(relocations), + null, false); - if (result == null) { - if (checkGlobal) { - result = loader.getClassByName(name); + if (obfVersion.equals("v1_7_R4")) { + jarMapping.loadMappings( + new BufferedReader(new InputStreamReader(loader.getClass().getClassLoader().getResourceAsStream("mappings/"+obfVersion+"/obf2numpkg.srg"))), + null, // no version relocation for obf + null, false); } + // resolve naming conflict in FML/CB + jarMapping.methods.put("net/minecraft/server/"+obfVersion+"/PlayerConnection/getPlayer ()Lorg/bukkit/craftbukkit/"+getNativeVersion()+"/entity/CraftPlayer;", "getPlayerB"); + } + // remap bouncycastle to Forge's included copy, not the vanilla obfuscated copy (not in Cauldron), see #133 + //jarMapping.packages.put("net/minecraft/"+obfVersion+"/org/bouncycastle", "org/bouncycastle"); No longer needed + } + + private JarMapping getJarMapping(int flags) { + JarMapping jarMapping = jarMappings.get(flags); + + if (jarMapping != null) { + if (debug) { + System.out.println("Mapping reused for "+Integer.toHexString(flags)); + } + return jarMapping; + } + + jarMapping = new JarMapping(); + try { + + // Guava 10 is part of the Bukkit API, so plugins can use it, but FML includes Guava 15 + // To resolve this conflict, remap plugin usages to Guava 10 in a separate package + // Most plugins should keep this enabled, unless they want a newer Guava + jarMapping.packages.put("com/google/common", "guava10/com/google/common"); + jarMapping.packages.put(org_bukkit_craftbukkit + "/libs/com/google/gson", "com/google/gson"); // Handle Gson being in a "normal" place + // Bukkit moves these packages to nms while we keep them in root so we must relocate them for plugins that rely on them + jarMapping.packages.put("net/minecraft/util/io", "io"); + jarMapping.packages.put("net/minecraft/util/com", "com"); + jarMapping.packages.put("net/minecraft/util/gnu", "gnu"); + jarMapping.packages.put("net/minecraft/util/org", "org"); + + if ((flags & F_REMAP_NMS1710) != 0) { + loadNmsMappings(jarMapping, "v1_7_R4"); + } + + if ((flags & F_REMAP_NMS179) != 0) { + loadNmsMappings(jarMapping, "v1_7_R3"); + } + + if ((flags & F_REMAP_NMS172) != 0) { + loadNmsMappings(jarMapping, "v1_7_R1"); + } + + if ((flags & F_REMAP_NMS164) != 0) { + loadNmsMappings(jarMapping, "v1_6_R3"); + } + + if ((flags & F_REMAP_NMS152) != 0) { + loadNmsMappings(jarMapping, "v1_5_R3"); + } + + if ((flags & F_REMAP_OBC1710) != 0) { + if (CauldronUtils.deobfuscatedEnvironment()) + jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_7_R4", org_bukkit_craftbukkit); + else jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_7_R4", org_bukkit_craftbukkit+"/"+getNativeVersion()); + } + + if ((flags & F_REMAP_OBC179) != 0) { + if (CauldronUtils.deobfuscatedEnvironment()) + jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_7_R3", org_bukkit_craftbukkit); + else jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_7_R3", org_bukkit_craftbukkit+"/"+getNativeVersion()); + } + + if ((flags & F_REMAP_OBC172) != 0) { + if (CauldronUtils.deobfuscatedEnvironment()) + jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_7_R1", org_bukkit_craftbukkit+"/"+getNativeVersion()); + else jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_7_R1", org_bukkit_craftbukkit+"/"+getNativeVersion()); + } + + if ((flags & F_REMAP_OBC164) != 0) { + jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_6_R3", org_bukkit_craftbukkit+"/"+getNativeVersion()); + } + + if ((flags & F_REMAP_OBC152) != 0) { + jarMapping.packages.put(org_bukkit_craftbukkit+"/v1_5_R3", org_bukkit_craftbukkit+"/"+getNativeVersion()); + } + + if ((flags & F_REMAP_OBCPRE) != 0) { + // enabling unversioned obc not currently compatible with versioned obc plugins (overmapped) - + // admins should enable remap-obc-pre on a per-plugin basis, as needed + // then map unversioned to current version + jarMapping.packages.put(org_bukkit_craftbukkit+"/libs/org/objectweb/asm", "org/objectweb/asm"); // ? + jarMapping.packages.put(org_bukkit_craftbukkit, org_bukkit_craftbukkit+"/"+getNativeVersion()); + } + + if ((flags & F_REMAP_NMSPRE_MASK) != 0) { + String obfVersion; + switch (flags & F_REMAP_NMSPRE_MASK) + { + case 0x17100000: obfVersion = "v1_7_R4"; break; + case 0x01790000: obfVersion = "v1_7_R3"; break; + case 0x01720000: obfVersion = "v1_7_R1"; break; + case 0x01640000: obfVersion = "v1_6_R3"; break; + case 0x01510000: obfVersion = "v1_5_R2"; break; + default: throw new IllegalArgumentException("Invalid unversioned mapping flags: "+Integer.toHexString(flags & F_REMAP_NMSPRE_MASK)+" in "+Integer.toHexString(flags)); + } + + jarMapping.loadMappings( + new BufferedReader(new InputStreamReader(loader.getClass().getClassLoader().getResourceAsStream("mappings/" + obfVersion + "/cb2numpkg.srg"))), + null, // no version relocation! + null, false); + } + + System.out.println("Mapping loaded "+jarMapping.packages.size()+" packages, "+jarMapping.classes.size()+" classes, "+jarMapping.fields.size()+" fields, "+jarMapping.methods.size()+" methods, flags "+Integer.toHexString(flags)); + + JarMapping currentJarMapping = jarMappings.putIfAbsent(flags, jarMapping); + return currentJarMapping == null ? jarMapping : currentJarMapping; + } catch (IOException ex) { + ex.printStackTrace(); + throw new RuntimeException(ex); + } + } + + Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { + // Cauldron start - remap any calls for classes with packaged nms version + if (name.startsWith("net.minecraft.")) + { + JarMapping jarMapping = this.getJarMapping(remapFlags); // grab from SpecialSource + String remappedClass = jarMapping.classes.get(name.replaceAll("\\.", "\\/")); // get remapped pkgmcp class name + Class clazz = ((net.minecraft.launchwrapper.LaunchClassLoader)MinecraftServer.getServer().getClass().getClassLoader()).findClass(remappedClass); + return clazz; + } + if (name.startsWith("org.bukkit.")) { + if (debug) { + System.out.println("Unexpected plugin findClass on OBC/NMS: name="+name+", checkGlobal="+checkGlobal+"; returning not found"); + } + throw new ClassNotFoundException(name); + } + // custom loader, if enabled, threadsafety + synchronized (name.intern()) { + Class result = classes.get(name); + if (result == null) { - result = super.findClass(name); + if (checkGlobal) { + result = loader.getClassByName(name); // Don't warn on deprecation, but maintain overridability + } + if (result == null) { + if (remapper == null) { + result = super.findClass(name); + } else { + result = remappedFindClass(name); + } + + if (result != null) { + loader.setClass(name, result); + } + } if (result != null) { - loader.setClass(name, result); + Class old = classes.putIfAbsent(name, result); + if (old != null && old != result) { + System.err.println("Defined class " + name + " twice as different classes, " + result + " and " + old); + result = old; + } } } - classes.put(name, result); + return result; } + // Cauldron end + } + private Class remappedFindClass(String name) throws ClassNotFoundException { + Class result = null; + try { + // Load the resource to the name + String path = name.replace('.', '/').concat(".class"); + URL url = this.findResource(path); + if (url != null) { + InputStream stream = url.openStream(); + if (stream != null) { + byte[] bytecode = null; + + // Reflection remap and inheritance extract + if (remapperProcessor != null) { + // add to inheritance map + bytecode = remapperProcessor.process(stream); + if (bytecode == null) stream = url.openStream(); + } + + /*if (bytecode == null) { + bytecode = Streams.readAll(stream); + }*/ + + // Remap the classes + byte[] remappedBytecode = remapper.remapClassFile(bytecode, RuntimeRepo.getInstance()); + + if (debug) { + File file = new File("remapped-plugin-classes/"+name+".class"); + file.getParentFile().mkdirs(); + try { + FileOutputStream fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(remappedBytecode); + fileOutputStream.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + // Define (create) the class using the modified byte code + // The top-child class loader is used for this to prevent access violations + // Set the codesource to the jar, not within the jar, for compatibility with + // plugins that do new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI())) + // instead of using getResourceAsStream - see https://github.com/MinecraftPortCentral/Cauldron-Plus/issues/75 + JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); // parses only + URL jarURL = jarURLConnection.getJarFileURL(); + CodeSource codeSource = new CodeSource(jarURL, new CodeSigner[0]); + + result = this.defineClass(name, remappedBytecode, 0, remappedBytecode.length, codeSource); + if (result != null) { + // Resolve it - sets the class loader of the class + this.resolveClass(result); + } + } + } + } catch (Throwable t) { + if (debug) { + System.out.println("remappedFindClass("+name+") exception: "+t); + t.printStackTrace(); + } + throw new ClassNotFoundException("Failed to remap class "+name, t); + } + return result; } + // Cauldron end Set getClasses() { return classes.keySet();