001/* 002 * Forge Mod Loader 003 * Copyright (c) 2012-2013 cpw. 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser Public License v2.1 006 * which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 008 * 009 * Contributors: 010 * cpw - implementation 011 */ 012 013package cpw.mods.fml.relauncher; 014 015import java.io.ByteArrayOutputStream; 016import java.io.File; 017import java.io.FileOutputStream; 018import java.io.IOException; 019import java.io.InputStream; 020import java.io.OutputStream; 021import java.net.JarURLConnection; 022import java.net.URL; 023import java.net.URLClassLoader; 024import java.net.URLConnection; 025import java.security.CodeSigner; 026import java.security.CodeSource; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Locale; 034import java.util.Map; 035import java.util.Set; 036import java.util.jar.Attributes.Name; 037import java.util.jar.Attributes; 038import java.util.jar.JarEntry; 039import java.util.jar.JarFile; 040import java.util.jar.Manifest; 041import java.util.logging.Level; 042 043import cpw.mods.fml.common.FMLLog; 044 045public class RelaunchClassLoader extends URLClassLoader 046{ 047 private List<URL> sources; 048 private ClassLoader parent; 049 050 private List<IClassTransformer> transformers; 051 private Map<String, Class> cachedClasses; 052 private Set<String> invalidClasses; 053 054 private Set<String> classLoaderExceptions = new HashSet<String>(); 055 private Set<String> transformerExceptions = new HashSet<String>(); 056 private Map<Package,Manifest> packageManifests = new HashMap<Package,Manifest>(); 057 private IClassNameTransformer renameTransformer; 058 059 private static Manifest EMPTY = new Manifest(); 060 061 private ThreadLocal<byte[]> loadBuffer = new ThreadLocal<byte[]>(); 062 063 private static final String[] RESERVED = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}; 064 065 private static final boolean DEBUG_CLASSLOADING = Boolean.parseBoolean(System.getProperty("fml.debugClassLoading", "false")); 066 private static final boolean DEBUG_CLASSLOADING_FINER = DEBUG_CLASSLOADING && Boolean.parseBoolean(System.getProperty("fml.debugClassLoadingFiner", "false")); 067 private static final boolean DEBUG_CLASSLOADING_SAVE = DEBUG_CLASSLOADING && Boolean.parseBoolean(System.getProperty("fml.debugClassLoadingSave", "false")); 068 private static File temp_folder = null; 069 070 public RelaunchClassLoader(URL[] sources) 071 { 072 super(sources, null); 073 this.sources = new ArrayList<URL>(Arrays.asList(sources)); 074 this.parent = getClass().getClassLoader(); 075 this.cachedClasses = new HashMap<String,Class>(1000); 076 this.invalidClasses = new HashSet<String>(1000); 077 this.transformers = new ArrayList<IClassTransformer>(2); 078// ReflectionHelper.setPrivateValue(ClassLoader.class, null, this, "scl"); 079 Thread.currentThread().setContextClassLoader(this); 080 081 // standard classloader exclusions 082 addClassLoaderExclusion("java."); 083 addClassLoaderExclusion("sun."); 084 addClassLoaderExclusion("org.lwjgl."); 085 addClassLoaderExclusion("cpw.mods.fml.relauncher."); 086 addClassLoaderExclusion("net.minecraftforge.classloading."); 087 088 // standard transformer exclusions 089 addTransformerExclusion("javax."); 090 addTransformerExclusion("argo."); 091 addTransformerExclusion("org.objectweb.asm."); 092 addTransformerExclusion("com.google.common."); 093 addTransformerExclusion("org.bouncycastle."); 094 addTransformerExclusion("cpw.mods.fml.common.asm.transformers.deobf."); 095 096 if (DEBUG_CLASSLOADING_SAVE) 097 { 098 int x = 1; 099 temp_folder = new File(FMLRelaunchLog.minecraftHome, "CLASSLOADER_TEMP"); 100 while(temp_folder.exists() && x <= 10) 101 { 102 temp_folder = new File(FMLRelaunchLog.minecraftHome, "CLASSLOADER_TEMP" + x++); 103 } 104 105 if (temp_folder.exists()) 106 { 107 FMLRelaunchLog.info("DEBUG_CLASSLOADING_SAVE enabled, but 10 temp directories already exist, clean them and try again."); 108 temp_folder = null; 109 } 110 else 111 { 112 FMLRelaunchLog.info("DEBUG_CLASSLOADING_SAVE Enabled, saving all classes to \"%s\"", temp_folder.getAbsolutePath().replace('\\', '/')); 113 temp_folder.mkdirs(); 114 } 115 } 116 } 117 118 public void registerTransformer(String transformerClassName) 119 { 120 try 121 { 122 IClassTransformer transformer = (IClassTransformer) loadClass(transformerClassName).newInstance(); 123 transformers.add(transformer); 124 if (transformer instanceof IClassNameTransformer && renameTransformer == null) 125 { 126 renameTransformer = (IClassNameTransformer) transformer; 127 } 128 } 129 catch (Exception e) 130 { 131 FMLRelaunchLog.log(Level.SEVERE, e, "A critical problem occured registering the ASM transformer class %s", transformerClassName); 132 } 133 } 134 @Override 135 public Class<?> findClass(String name) throws ClassNotFoundException 136 { 137 if (invalidClasses.contains(name)) 138 { 139 throw new ClassNotFoundException(name); 140 } 141 for (String st : classLoaderExceptions) 142 { 143 if (name.startsWith(st)) 144 { 145 return parent.loadClass(name); 146 } 147 } 148 149 if (cachedClasses.containsKey(name)) 150 { 151 return cachedClasses.get(name); 152 } 153 154 for (String st : transformerExceptions) 155 { 156 if (name.startsWith(st)) 157 { 158 try 159 { 160 Class<?> cl = super.findClass(name); 161 cachedClasses.put(name, cl); 162 return cl; 163 } 164 catch (ClassNotFoundException e) 165 { 166 invalidClasses.add(name); 167 throw e; 168 } 169 } 170 } 171 172 try 173 { 174 CodeSigner[] signers = null; 175 String transformedName = transformName(name); 176 String untransformedName = untransformName(name); 177 int lastDot = untransformedName.lastIndexOf('.'); 178 String pkgname = lastDot == -1 ? "" : untransformedName.substring(0, lastDot); 179 String fName = untransformedName.replace('.', '/').concat(".class"); 180 String pkgPath = pkgname.replace('.', '/'); 181 URLConnection urlConnection = findCodeSourceConnectionFor(fName); 182 if (urlConnection instanceof JarURLConnection && lastDot > -1 && !untransformedName.startsWith("net.minecraft.")) 183 { 184 JarURLConnection jarUrlConn = (JarURLConnection)urlConnection; 185 JarFile jf = jarUrlConn.getJarFile(); 186 if (jf != null && jf.getManifest() != null) 187 { 188 Manifest mf = jf.getManifest(); 189 JarEntry ent = jf.getJarEntry(fName); 190 Package pkg = getPackage(pkgname); 191 getClassBytes(untransformedName); 192 signers = ent.getCodeSigners(); 193 if (pkg == null) 194 { 195 pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL()); 196 packageManifests.put(pkg, mf); 197 } 198 else 199 { 200 if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL())) 201 { 202 FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname); 203 } 204 else if (isSealed(pkgname, mf)) 205 { 206 FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname); 207 } 208 } 209 } 210 } 211 else if (lastDot > -1 && !untransformedName.startsWith("net.minecraft.")) 212 { 213 Package pkg = getPackage(pkgname); 214 if (pkg == null) 215 { 216 pkg = definePackage(pkgname, null, null, null, null, null, null, null); 217 packageManifests.put(pkg, EMPTY); 218 } 219 else if (pkg.isSealed()) 220 { 221 FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname); 222 } 223 } 224 byte[] basicClass = getClassBytes(untransformedName); 225 byte[] transformedClass = runTransformers(untransformedName, transformedName, basicClass); 226 saveTransformedClass(transformedClass, transformedName); 227 Class<?> cl = defineClass(transformedName, transformedClass, 0, transformedClass.length, (urlConnection == null ? null : new CodeSource(urlConnection.getURL(), signers))); 228 cachedClasses.put(transformedName, cl); 229 return cl; 230 } 231 catch (Throwable e) 232 { 233 invalidClasses.add(name); 234 if (DEBUG_CLASSLOADING) 235 { 236 FMLLog.log(Level.FINEST, e, "Exception encountered attempting classloading of %s", name); 237 } 238 throw new ClassNotFoundException(name, e); 239 } 240 } 241 242 private void saveTransformedClass(byte[] data, String transformedName) 243 { 244 if (!DEBUG_CLASSLOADING_SAVE || temp_folder == null) 245 { 246 return; 247 } 248 249 File outFile = new File(temp_folder, transformedName.replace('.', File.separatorChar) + ".class"); 250 File outDir = outFile.getParentFile(); 251 252 if (!outDir.exists()) 253 { 254 outDir.mkdirs(); 255 } 256 257 if (outFile.exists()) 258 { 259 outFile.delete(); 260 } 261 262 try 263 { 264 FMLRelaunchLog.fine("Saving transformed class \"%s\" to \"%s\"", transformedName, outFile.getAbsolutePath().replace('\\', '/')); 265 OutputStream output = new FileOutputStream(outFile); 266 output.write(data); 267 output.close(); 268 } 269 catch(IOException ex) 270 { 271 FMLRelaunchLog.log(Level.WARNING, ex, "Could not save transformed class \"%s\"", transformedName); 272 } 273 } 274 275 private String untransformName(String name) 276 { 277 if (renameTransformer != null) 278 { 279 return renameTransformer.unmapClassName(name); 280 } 281 else 282 { 283 return name; 284 } 285 } 286 287 private String transformName(String name) 288 { 289 if (renameTransformer != null) 290 { 291 return renameTransformer.remapClassName(name); 292 } 293 else 294 { 295 return name; 296 } 297 } 298 299 private boolean isSealed(String path, Manifest man) 300 { 301 Attributes attr = man.getAttributes(path); 302 String sealed = null; 303 if (attr != null) { 304 sealed = attr.getValue(Name.SEALED); 305 } 306 if (sealed == null) { 307 if ((attr = man.getMainAttributes()) != null) { 308 sealed = attr.getValue(Name.SEALED); 309 } 310 } 311 return "true".equalsIgnoreCase(sealed); 312 } 313 314 private URLConnection findCodeSourceConnectionFor(String name) 315 { 316 URL res = findResource(name); 317 if (res != null) 318 { 319 try 320 { 321 return res.openConnection(); 322 } 323 catch (IOException e) 324 { 325 throw new RuntimeException(e); 326 } 327 } 328 else 329 { 330 return null; 331 } 332 } 333 334 private byte[] runTransformers(String name, String transformedName, byte[] basicClass) 335 { 336 if (DEBUG_CLASSLOADING_FINER) 337 { 338 FMLRelaunchLog.finest("Beginning transform of %s (%s) Start Length: %d", name, transformedName, (basicClass == null ? 0 : basicClass.length)); 339 for (IClassTransformer transformer : transformers) 340 { 341 String transName = transformer.getClass().getName(); 342 FMLRelaunchLog.finest("Before Transformer %s: %d", transName, (basicClass == null ? 0 : basicClass.length)); 343 basicClass = transformer.transform(name, transformedName, basicClass); 344 FMLRelaunchLog.finest("After Transformer %s: %d", transName, (basicClass == null ? 0 : basicClass.length)); 345 } 346 FMLRelaunchLog.finest("Ending transform of %s (%s) Start Length: %d", name, transformedName, (basicClass == null ? 0 : basicClass.length)); 347 } 348 else 349 { 350 for (IClassTransformer transformer : transformers) 351 { 352 basicClass = transformer.transform(name, transformedName, basicClass); 353 } 354 } 355 return basicClass; 356 } 357 358 @Override 359 public void addURL(URL url) 360 { 361 super.addURL(url); 362 sources.add(url); 363 } 364 365 public List<URL> getSources() 366 { 367 return sources; 368 } 369 370 371 private byte[] readFully(InputStream stream) 372 { 373 try 374 { 375 byte[] buf = loadBuffer.get(); 376 if (buf == null) 377 { 378 loadBuffer.set(new byte[1 << 12]); 379 buf = loadBuffer.get(); 380 } 381 382 int r, totalLength = 0; 383 while ((r = stream.read(buf, totalLength, buf.length - totalLength)) != -1) 384 { 385 totalLength += r; 386 if (totalLength >= buf.length - 1) 387 { 388 byte[] oldbuf = buf; 389 buf = new byte[ oldbuf.length + (1 << 12 )]; 390 System.arraycopy(oldbuf, 0, buf, 0, oldbuf.length); 391 } 392 } 393 394 byte[] result = new byte[totalLength]; 395 System.arraycopy(buf, 0, result, 0, totalLength); 396 return result; 397 } 398 catch (Throwable t) 399 { 400 FMLRelaunchLog.log(Level.WARNING, t, "Problem loading class"); 401 return new byte[0]; 402 } 403 } 404 405 public List<IClassTransformer> getTransformers() 406 { 407 return Collections.unmodifiableList(transformers); 408 } 409 410 private void addClassLoaderExclusion(String toExclude) 411 { 412 classLoaderExceptions.add(toExclude); 413 } 414 415 void addTransformerExclusion(String toExclude) 416 { 417 transformerExceptions.add(toExclude); 418 } 419 420 public byte[] getClassBytes(String name) throws IOException 421 { 422 if (name.indexOf('.') == -1) 423 { 424 for (String res : RESERVED) 425 { 426 if (name.toUpperCase(Locale.ENGLISH).startsWith(res)) 427 { 428 byte[] data = getClassBytes("_" + name); 429 if (data != null) 430 { 431 return data; 432 } 433 } 434 } 435 } 436 437 InputStream classStream = null; 438 try 439 { 440 URL classResource = findResource(name.replace('.', '/').concat(".class")); 441 if (classResource == null) 442 { 443 if (DEBUG_CLASSLOADING) 444 { 445 FMLLog.finest("Failed to find class resource %s", name.replace('.', '/').concat(".class")); 446 } 447 return null; 448 } 449 classStream = classResource.openStream(); 450 if (DEBUG_CLASSLOADING) 451 { 452 FMLLog.finest("Loading class %s from resource %s", name, classResource.toString()); 453 } 454 return readFully(classStream); 455 } 456 finally 457 { 458 if (classStream != null) 459 { 460 try 461 { 462 classStream.close(); 463 } 464 catch (IOException e) 465 { 466 // Swallow the close exception 467 } 468 } 469 } 470 } 471}