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.common.asm.transformers; 014 015import static org.objectweb.asm.Opcodes.ACC_FINAL; 016import static org.objectweb.asm.Opcodes.ACC_PRIVATE; 017import static org.objectweb.asm.Opcodes.ACC_PROTECTED; 018import static org.objectweb.asm.Opcodes.ACC_PUBLIC; 019 020import java.io.BufferedInputStream; 021import java.io.BufferedOutputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.FileNotFoundException; 026import java.io.FileOutputStream; 027import java.io.IOException; 028import java.net.URL; 029import java.util.Collection; 030import java.util.List; 031import java.util.zip.ZipEntry; 032import java.util.zip.ZipInputStream; 033import java.util.zip.ZipOutputStream; 034 035import org.objectweb.asm.ClassReader; 036import org.objectweb.asm.ClassWriter; 037import org.objectweb.asm.tree.ClassNode; 038import org.objectweb.asm.tree.FieldNode; 039import org.objectweb.asm.tree.MethodNode; 040 041import com.google.common.base.Charsets; 042import com.google.common.base.Splitter; 043import com.google.common.collect.ArrayListMultimap; 044import com.google.common.collect.Iterables; 045import com.google.common.collect.Lists; 046import com.google.common.collect.Multimap; 047import com.google.common.io.LineProcessor; 048import com.google.common.io.Resources; 049 050import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; 051import cpw.mods.fml.relauncher.IClassTransformer; 052 053public class AccessTransformer implements IClassTransformer 054{ 055 private static final boolean DEBUG = false; 056 private class Modifier 057 { 058 public String name = ""; 059 public String desc = ""; 060 public int oldAccess = 0; 061 public int newAccess = 0; 062 public int targetAccess = 0; 063 public boolean changeFinal = false; 064 public boolean markFinal = false; 065 protected boolean modifyClassVisibility; 066 067 private void setTargetAccess(String name) 068 { 069 if (name.startsWith("public")) targetAccess = ACC_PUBLIC; 070 else if (name.startsWith("private")) targetAccess = ACC_PRIVATE; 071 else if (name.startsWith("protected")) targetAccess = ACC_PROTECTED; 072 073 if (name.endsWith("-f")) 074 { 075 changeFinal = true; 076 markFinal = false; 077 } 078 else if (name.endsWith("+f")) 079 { 080 changeFinal = true; 081 markFinal = true; 082 } 083 } 084 } 085 086 private Multimap<String, Modifier> modifiers = ArrayListMultimap.create(); 087 088 public AccessTransformer() throws IOException 089 { 090 this("fml_at.cfg"); 091 } 092 protected AccessTransformer(String rulesFile) throws IOException 093 { 094 readMapFile(rulesFile); 095 } 096 097 private void readMapFile(String rulesFile) throws IOException 098 { 099 File file = new File(rulesFile); 100 URL rulesResource; 101 if (file.exists()) 102 { 103 rulesResource = file.toURI().toURL(); 104 } 105 else 106 { 107 rulesResource = Resources.getResource(rulesFile); 108 } 109 Resources.readLines(rulesResource, Charsets.UTF_8, new LineProcessor<Void>() 110 { 111 @Override 112 public Void getResult() 113 { 114 return null; 115 } 116 117 @Override 118 public boolean processLine(String input) throws IOException 119 { 120 String line = Iterables.getFirst(Splitter.on('#').limit(2).split(input), "").trim(); 121 if (line.length()==0) 122 { 123 return true; 124 } 125 List<String> parts = Lists.newArrayList(Splitter.on(" ").trimResults().split(line)); 126 if (parts.size()>2) 127 { 128 throw new RuntimeException("Invalid config file line "+ input); 129 } 130 Modifier m = new Modifier(); 131 m.setTargetAccess(parts.get(0)); 132 List<String> descriptor = Lists.newArrayList(Splitter.on(".").trimResults().split(parts.get(1))); 133 if (descriptor.size() == 1) 134 { 135 m.modifyClassVisibility = true; 136 } 137 else 138 { 139 String nameReference = descriptor.get(1); 140 int parenIdx = nameReference.indexOf('('); 141 if (parenIdx>0) 142 { 143 m.desc = nameReference.substring(parenIdx); 144 m.name = nameReference.substring(0,parenIdx); 145 } 146 else 147 { 148 m.name = nameReference; 149 } 150 } 151 modifiers.put(descriptor.get(0).replace('/', '.'), m); 152 return true; 153 } 154 }); 155 } 156 157 @SuppressWarnings("unchecked") 158 @Override 159 public byte[] transform(String name, String transformedName, byte[] bytes) 160 { 161 if (bytes == null) { return null; } 162 boolean makeAllPublic = FMLDeobfuscatingRemapper.INSTANCE.isRemappedClass(name); 163 164 if (DEBUG) 165 { 166 System.out.printf("Considering all methods and fields on %s (%s): %b\n", name, transformedName, makeAllPublic); 167 } 168 if (!makeAllPublic && !modifiers.containsKey(name)) { return bytes; } 169 170 ClassNode classNode = new ClassNode(); 171 ClassReader classReader = new ClassReader(bytes); 172 classReader.accept(classNode, 0); 173 174 if (makeAllPublic) 175 { 176 // class 177 Modifier m = new Modifier(); 178 m.targetAccess = ACC_PUBLIC; 179 m.modifyClassVisibility = true; 180 modifiers.put(name,m); 181 // fields 182 m = new Modifier(); 183 m.targetAccess = ACC_PUBLIC; 184 m.name = "*"; 185 modifiers.put(name,m); 186 // methods 187 m = new Modifier(); 188 m.targetAccess = ACC_PUBLIC; 189 m.name = "*"; 190 m.desc = "<dummy>"; 191 modifiers.put(name,m); 192 if (DEBUG) 193 { 194 System.out.printf("Injected all public modifiers for %s (%s)\n", name, transformedName); 195 } 196 } 197 198 Collection<Modifier> mods = modifiers.get(name); 199 for (Modifier m : mods) 200 { 201 if (m.modifyClassVisibility) 202 { 203 classNode.access = getFixedAccess(classNode.access, m); 204 if (DEBUG) 205 { 206 System.out.println(String.format("Class: %s %s -> %s", name, toBinary(m.oldAccess), toBinary(m.newAccess))); 207 } 208 continue; 209 } 210 if (m.desc.isEmpty()) 211 { 212 for (FieldNode n : (List<FieldNode>) classNode.fields) 213 { 214 if (n.name.equals(m.name) || m.name.equals("*")) 215 { 216 n.access = getFixedAccess(n.access, m); 217 if (DEBUG) 218 { 219 System.out.println(String.format("Field: %s.%s %s -> %s", name, n.name, toBinary(m.oldAccess), toBinary(m.newAccess))); 220 } 221 222 if (!m.name.equals("*")) 223 { 224 break; 225 } 226 } 227 } 228 } 229 else 230 { 231 for (MethodNode n : (List<MethodNode>) classNode.methods) 232 { 233 if ((n.name.equals(m.name) && n.desc.equals(m.desc)) || m.name.equals("*")) 234 { 235 n.access = getFixedAccess(n.access, m); 236 if (DEBUG) 237 { 238 System.out.println(String.format("Method: %s.%s%s %s -> %s", name, n.name, n.desc, toBinary(m.oldAccess), toBinary(m.newAccess))); 239 } 240 241 if (!m.name.equals("*")) 242 { 243 break; 244 } 245 } 246 } 247 } 248 } 249 250 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 251 classNode.accept(writer); 252 return writer.toByteArray(); 253 } 254 255 private String toBinary(int num) 256 { 257 return String.format("%16s", Integer.toBinaryString(num)).replace(' ', '0'); 258 } 259 260 private int getFixedAccess(int access, Modifier target) 261 { 262 target.oldAccess = access; 263 int t = target.targetAccess; 264 int ret = (access & ~7); 265 266 switch (access & 7) 267 { 268 case ACC_PRIVATE: 269 ret |= t; 270 break; 271 case 0: // default 272 ret |= (t != ACC_PRIVATE ? t : 0 /* default */); 273 break; 274 case ACC_PROTECTED: 275 ret |= (t != ACC_PRIVATE && t != 0 /* default */? t : ACC_PROTECTED); 276 break; 277 case ACC_PUBLIC: 278 ret |= (t != ACC_PRIVATE && t != 0 /* default */&& t != ACC_PROTECTED ? t : ACC_PUBLIC); 279 break; 280 default: 281 throw new RuntimeException("The fuck?"); 282 } 283 284 // Clear the "final" marker on fields only if specified in control field 285 if (target.changeFinal && target.desc == "") 286 { 287 if (target.markFinal) 288 { 289 ret |= ACC_FINAL; 290 } 291 else 292 { 293 ret &= ~ACC_FINAL; 294 } 295 } 296 target.newAccess = ret; 297 return ret; 298 } 299 300 public static void main(String[] args) 301 { 302 if (args.length < 2) 303 { 304 System.out.println("Usage: AccessTransformer <JarPath> <MapFile> [MapFile2]... "); 305 System.exit(1); 306 } 307 308 boolean hasTransformer = false; 309 AccessTransformer[] trans = new AccessTransformer[args.length - 1]; 310 for (int x = 1; x < args.length; x++) 311 { 312 try 313 { 314 trans[x - 1] = new AccessTransformer(args[x]); 315 hasTransformer = true; 316 } 317 catch (IOException e) 318 { 319 System.out.println("Could not read Transformer Map: " + args[x]); 320 e.printStackTrace(); 321 } 322 } 323 324 if (!hasTransformer) 325 { 326 System.out.println("Culd not find a valid transformer to perform"); 327 System.exit(1); 328 } 329 330 File orig = new File(args[0]); 331 File temp = new File(args[0] + ".ATBack"); 332 if (!orig.exists() && !temp.exists()) 333 { 334 System.out.println("Could not find target jar: " + orig); 335 System.exit(1); 336 } 337 338 if (!orig.renameTo(temp)) 339 { 340 System.out.println("Could not rename file: " + orig + " -> " + temp); 341 System.exit(1); 342 } 343 344 try 345 { 346 processJar(temp, orig, trans); 347 } 348 catch (IOException e) 349 { 350 e.printStackTrace(); 351 System.exit(1); 352 } 353 354 if (!temp.delete()) 355 { 356 System.out.println("Could not delete temp file: " + temp); 357 } 358 } 359 360 private static void processJar(File inFile, File outFile, AccessTransformer[] transformers) throws IOException 361 { 362 ZipInputStream inJar = null; 363 ZipOutputStream outJar = null; 364 365 try 366 { 367 try 368 { 369 inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile))); 370 } 371 catch (FileNotFoundException e) 372 { 373 throw new FileNotFoundException("Could not open input file: " + e.getMessage()); 374 } 375 376 try 377 { 378 outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); 379 } 380 catch (FileNotFoundException e) 381 { 382 throw new FileNotFoundException("Could not open output file: " + e.getMessage()); 383 } 384 385 ZipEntry entry; 386 while ((entry = inJar.getNextEntry()) != null) 387 { 388 if (entry.isDirectory()) 389 { 390 outJar.putNextEntry(entry); 391 continue; 392 } 393 394 byte[] data = new byte[4096]; 395 ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream(); 396 397 int len; 398 do 399 { 400 len = inJar.read(data); 401 if (len > 0) 402 { 403 entryBuffer.write(data, 0, len); 404 } 405 } 406 while (len != -1); 407 408 byte[] entryData = entryBuffer.toByteArray(); 409 410 String entryName = entry.getName(); 411 412 if (entryName.endsWith(".class") && !entryName.startsWith(".")) 413 { 414 ClassNode cls = new ClassNode(); 415 ClassReader rdr = new ClassReader(entryData); 416 rdr.accept(cls, 0); 417 String name = cls.name.replace('/', '.').replace('\\', '.'); 418 419 for (AccessTransformer trans : transformers) 420 { 421 entryData = trans.transform(name, name, entryData); 422 } 423 } 424 425 ZipEntry newEntry = new ZipEntry(entryName); 426 outJar.putNextEntry(newEntry); 427 outJar.write(entryData); 428 } 429 } 430 finally 431 { 432 if (outJar != null) 433 { 434 try 435 { 436 outJar.close(); 437 } 438 catch (IOException e) 439 { 440 } 441 } 442 443 if (inJar != null) 444 { 445 try 446 { 447 inJar.close(); 448 } 449 catch (IOException e) 450 { 451 } 452 } 453 } 454 } 455 public void ensurePublicAccessFor(String modClazzName) 456 { 457 Modifier m = new Modifier(); 458 m.setTargetAccess("public"); 459 m.modifyClassVisibility = true; 460 modifiers.put(modClazzName, m); 461 } 462}