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