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 (!modifiers.containsKey(name)) { return bytes; } 149 150 ClassNode classNode = new ClassNode(); 151 ClassReader classReader = new ClassReader(bytes); 152 classReader.accept(classNode, 0); 153 154 Collection<Modifier> mods = modifiers.get(name); 155 for (Modifier m : mods) 156 { 157 if (m.modifyClassVisibility) 158 { 159 classNode.access = getFixedAccess(classNode.access, m); 160 if (DEBUG) 161 { 162 System.out.println(String.format("Class: %s %s -> %s", name, toBinary(m.oldAccess), toBinary(m.newAccess))); 163 } 164 continue; 165 } 166 if (m.desc.isEmpty()) 167 { 168 for (FieldNode n : (List<FieldNode>) classNode.fields) 169 { 170 if (n.name.equals(m.name) || m.name.equals("*")) 171 { 172 n.access = getFixedAccess(n.access, m); 173 if (DEBUG) 174 { 175 System.out.println(String.format("Field: %s.%s %s -> %s", name, n.name, toBinary(m.oldAccess), toBinary(m.newAccess))); 176 } 177 178 if (!m.name.equals("*")) 179 { 180 break; 181 } 182 } 183 } 184 } 185 else 186 { 187 for (MethodNode n : (List<MethodNode>) classNode.methods) 188 { 189 if ((n.name.equals(m.name) && n.desc.equals(m.desc)) || m.name.equals("*")) 190 { 191 n.access = getFixedAccess(n.access, m); 192 if (DEBUG) 193 { 194 System.out.println(String.format("Method: %s.%s%s %s -> %s", name, n.name, n.desc, toBinary(m.oldAccess), toBinary(m.newAccess))); 195 } 196 197 if (!m.name.equals("*")) 198 { 199 break; 200 } 201 } 202 } 203 } 204 } 205 206 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 207 classNode.accept(writer); 208 return writer.toByteArray(); 209 } 210 211 private String toBinary(int num) 212 { 213 return String.format("%16s", Integer.toBinaryString(num)).replace(' ', '0'); 214 } 215 216 private int getFixedAccess(int access, Modifier target) 217 { 218 target.oldAccess = access; 219 int t = target.targetAccess; 220 int ret = (access & ~7); 221 222 switch (access & 7) 223 { 224 case ACC_PRIVATE: 225 ret |= t; 226 break; 227 case 0: // default 228 ret |= (t != ACC_PRIVATE ? t : 0 /* default */); 229 break; 230 case ACC_PROTECTED: 231 ret |= (t != ACC_PRIVATE && t != 0 /* default */? t : ACC_PROTECTED); 232 break; 233 case ACC_PUBLIC: 234 ret |= (t != ACC_PRIVATE && t != 0 /* default */&& t != ACC_PROTECTED ? t : ACC_PUBLIC); 235 break; 236 default: 237 throw new RuntimeException("The fuck?"); 238 } 239 240 // Clear the "final" marker on fields only if specified in control field 241 if (target.changeFinal && target.desc == "") 242 { 243 if (target.markFinal) 244 { 245 ret |= ACC_FINAL; 246 } 247 else 248 { 249 ret &= ~ACC_FINAL; 250 } 251 } 252 target.newAccess = ret; 253 return ret; 254 } 255 256 public static void main(String[] args) 257 { 258 if (args.length < 2) 259 { 260 System.out.println("Usage: AccessTransformer <JarPath> <MapFile> [MapFile2]... "); 261 System.exit(1); 262 } 263 264 boolean hasTransformer = false; 265 AccessTransformer[] trans = new AccessTransformer[args.length - 1]; 266 for (int x = 1; x < args.length; x++) 267 { 268 try 269 { 270 trans[x - 1] = new AccessTransformer(args[x]); 271 hasTransformer = true; 272 } 273 catch (IOException e) 274 { 275 System.out.println("Could not read Transformer Map: " + args[x]); 276 e.printStackTrace(); 277 } 278 } 279 280 if (!hasTransformer) 281 { 282 System.out.println("Culd not find a valid transformer to perform"); 283 System.exit(1); 284 } 285 286 File orig = new File(args[0]); 287 File temp = new File(args[0] + ".ATBack"); 288 if (!orig.exists() && !temp.exists()) 289 { 290 System.out.println("Could not find target jar: " + orig); 291 System.exit(1); 292 } 293 294 if (!orig.renameTo(temp)) 295 { 296 System.out.println("Could not rename file: " + orig + " -> " + temp); 297 System.exit(1); 298 } 299 300 try 301 { 302 processJar(temp, orig, trans); 303 } 304 catch (IOException e) 305 { 306 e.printStackTrace(); 307 System.exit(1); 308 } 309 310 if (!temp.delete()) 311 { 312 System.out.println("Could not delete temp file: " + temp); 313 } 314 } 315 316 private static void processJar(File inFile, File outFile, AccessTransformer[] transformers) throws IOException 317 { 318 ZipInputStream inJar = null; 319 ZipOutputStream outJar = null; 320 321 try 322 { 323 try 324 { 325 inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile))); 326 } 327 catch (FileNotFoundException e) 328 { 329 throw new FileNotFoundException("Could not open input file: " + e.getMessage()); 330 } 331 332 try 333 { 334 outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); 335 } 336 catch (FileNotFoundException e) 337 { 338 throw new FileNotFoundException("Could not open output file: " + e.getMessage()); 339 } 340 341 ZipEntry entry; 342 while ((entry = inJar.getNextEntry()) != null) 343 { 344 if (entry.isDirectory()) 345 { 346 outJar.putNextEntry(entry); 347 continue; 348 } 349 350 byte[] data = new byte[4096]; 351 ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream(); 352 353 int len; 354 do 355 { 356 len = inJar.read(data); 357 if (len > 0) 358 { 359 entryBuffer.write(data, 0, len); 360 } 361 } 362 while (len != -1); 363 364 byte[] entryData = entryBuffer.toByteArray(); 365 366 String entryName = entry.getName(); 367 368 if (entryName.endsWith(".class") && !entryName.startsWith(".")) 369 { 370 ClassNode cls = new ClassNode(); 371 ClassReader rdr = new ClassReader(entryData); 372 rdr.accept(cls, 0); 373 String name = cls.name.replace('/', '.').replace('\\', '.'); 374 375 for (AccessTransformer trans : transformers) 376 { 377 entryData = trans.transform(name, entryData); 378 } 379 } 380 381 ZipEntry newEntry = new ZipEntry(entryName); 382 outJar.putNextEntry(newEntry); 383 outJar.write(entryData); 384 } 385 } 386 finally 387 { 388 if (outJar != null) 389 { 390 try 391 { 392 outJar.close(); 393 } 394 catch (IOException e) 395 { 396 } 397 } 398 399 if (inJar != null) 400 { 401 try 402 { 403 inJar.close(); 404 } 405 catch (IOException e) 406 { 407 } 408 } 409 } 410 } 411 public void ensurePublicAccessFor(String modClazzName) 412 { 413 Modifier m = new Modifier(); 414 m.setTargetAccess("public"); 415 m.modifyClassVisibility = true; 416 modifiers.put(modClazzName, m); 417 } 418 }