001 package cpw.mods.fml.common.asm.transformers; 002 003 import java.io.BufferedInputStream; 004 import java.io.BufferedOutputStream; 005 import java.io.ByteArrayOutputStream; 006 import java.io.File; 007 import java.io.FileInputStream; 008 import java.io.FileNotFoundException; 009 import java.io.FileOutputStream; 010 import java.io.IOException; 011 import java.net.URL; 012 import java.util.List; 013 import java.util.zip.ZipEntry; 014 import java.util.zip.ZipInputStream; 015 import java.util.zip.ZipOutputStream; 016 017 import org.objectweb.asm.ClassReader; 018 import org.objectweb.asm.ClassWriter; 019 import org.objectweb.asm.tree.ClassNode; 020 021 import com.google.common.base.Charsets; 022 import com.google.common.base.Splitter; 023 import com.google.common.collect.ArrayListMultimap; 024 import com.google.common.collect.Iterables; 025 import com.google.common.collect.ListMultimap; 026 import com.google.common.collect.Lists; 027 import com.google.common.io.LineProcessor; 028 import com.google.common.io.Resources; 029 030 import cpw.mods.fml.relauncher.IClassTransformer; 031 032 public class MarkerTransformer implements IClassTransformer 033 { 034 private ListMultimap<String, String> markers = ArrayListMultimap.create(); 035 036 public MarkerTransformer() throws IOException 037 { 038 this("fml_marker.cfg"); 039 } 040 protected MarkerTransformer(String rulesFile) throws IOException 041 { 042 readMapFile(rulesFile); 043 } 044 045 private void readMapFile(String rulesFile) throws IOException 046 { 047 File file = new File(rulesFile); 048 URL rulesResource; 049 if (file.exists()) 050 { 051 rulesResource = file.toURI().toURL(); 052 } 053 else 054 { 055 rulesResource = Resources.getResource(rulesFile); 056 } 057 Resources.readLines(rulesResource, Charsets.UTF_8, new LineProcessor<Void>() 058 { 059 @Override 060 public Void getResult() 061 { 062 return null; 063 } 064 065 @Override 066 public boolean processLine(String input) throws IOException 067 { 068 String line = Iterables.getFirst(Splitter.on('#').limit(2).split(input), "").trim(); 069 if (line.length()==0) 070 { 071 return true; 072 } 073 List<String> parts = Lists.newArrayList(Splitter.on(" ").trimResults().split(line)); 074 if (parts.size()!=2) 075 { 076 throw new RuntimeException("Invalid config file line "+ input); 077 } 078 List<String> markerInterfaces = Lists.newArrayList(Splitter.on(",").trimResults().split(parts.get(1))); 079 for (String marker : markerInterfaces) 080 { 081 markers.put(parts.get(0), marker); 082 } 083 return true; 084 } 085 }); 086 } 087 088 @SuppressWarnings("unchecked") 089 @Override 090 public byte[] transform(String name, byte[] bytes) 091 { 092 if (!markers.containsKey(name)) { return bytes; } 093 094 ClassNode classNode = new ClassNode(); 095 ClassReader classReader = new ClassReader(bytes); 096 classReader.accept(classNode, 0); 097 098 for (String marker : markers.get(name)) 099 { 100 classNode.interfaces.add(marker); 101 } 102 103 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); 104 classNode.accept(writer); 105 return writer.toByteArray(); 106 } 107 108 public static void main(String[] args) 109 { 110 if (args.length < 2) 111 { 112 System.out.println("Usage: MarkerTransformer <JarPath> <MapFile> [MapFile2]... "); 113 return; 114 } 115 116 boolean hasTransformer = false; 117 MarkerTransformer[] trans = new MarkerTransformer[args.length - 1]; 118 for (int x = 1; x < args.length; x++) 119 { 120 try 121 { 122 trans[x - 1] = new MarkerTransformer(args[x]); 123 hasTransformer = true; 124 } 125 catch (IOException e) 126 { 127 System.out.println("Could not read Transformer Map: " + args[x]); 128 e.printStackTrace(); 129 } 130 } 131 132 if (!hasTransformer) 133 { 134 System.out.println("Culd not find a valid transformer to perform"); 135 return; 136 } 137 138 File orig = new File(args[0]); 139 File temp = new File(args[0] + ".ATBack"); 140 if (!orig.exists() && !temp.exists()) 141 { 142 System.out.println("Could not find target jar: " + orig); 143 return; 144 } 145 /* 146 if (temp.exists()) 147 { 148 if (orig.exists() && !orig.renameTo(new File(args[0] + (new SimpleDateFormat(".yyyy.MM.dd.HHmmss")).format(new Date())))) 149 { 150 System.out.println("Could not backup existing file: " + orig); 151 return; 152 } 153 if (!temp.renameTo(orig)) 154 { 155 System.out.println("Could not restore backup from previous run: " + temp); 156 return; 157 } 158 } 159 */ 160 if (!orig.renameTo(temp)) 161 { 162 System.out.println("Could not rename file: " + orig + " -> " + temp); 163 return; 164 } 165 166 try 167 { 168 processJar(temp, orig, trans); 169 } 170 catch (IOException e) 171 { 172 e.printStackTrace(); 173 } 174 175 if (!temp.delete()) 176 { 177 System.out.println("Could not delete temp file: " + temp); 178 } 179 } 180 181 private static void processJar(File inFile, File outFile, MarkerTransformer[] transformers) throws IOException 182 { 183 ZipInputStream inJar = null; 184 ZipOutputStream outJar = null; 185 186 try 187 { 188 try 189 { 190 inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile))); 191 } 192 catch (FileNotFoundException e) 193 { 194 throw new FileNotFoundException("Could not open input file: " + e.getMessage()); 195 } 196 197 try 198 { 199 outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile))); 200 } 201 catch (FileNotFoundException e) 202 { 203 throw new FileNotFoundException("Could not open output file: " + e.getMessage()); 204 } 205 206 ZipEntry entry; 207 while ((entry = inJar.getNextEntry()) != null) 208 { 209 if (entry.isDirectory()) 210 { 211 outJar.putNextEntry(entry); 212 continue; 213 } 214 215 byte[] data = new byte[4096]; 216 ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream(); 217 218 int len; 219 do 220 { 221 len = inJar.read(data); 222 if (len > 0) 223 { 224 entryBuffer.write(data, 0, len); 225 } 226 } 227 while (len != -1); 228 229 byte[] entryData = entryBuffer.toByteArray(); 230 231 String entryName = entry.getName(); 232 233 if (entryName.endsWith(".class") && !entryName.startsWith(".")) 234 { 235 ClassNode cls = new ClassNode(); 236 ClassReader rdr = new ClassReader(entryData); 237 rdr.accept(cls, 0); 238 String name = cls.name.replace('/', '.').replace('\\', '.'); 239 240 for (MarkerTransformer trans : transformers) 241 { 242 entryData = trans.transform(name, entryData); 243 } 244 } 245 246 ZipEntry newEntry = new ZipEntry(entryName); 247 outJar.putNextEntry(newEntry); 248 outJar.write(entryData); 249 } 250 } 251 finally 252 { 253 if (outJar != null) 254 { 255 try 256 { 257 outJar.close(); 258 } 259 catch (IOException e) 260 { 261 } 262 } 263 264 if (inJar != null) 265 { 266 try 267 { 268 inJar.close(); 269 } 270 catch (IOException e) 271 { 272 } 273 } 274 } 275 } 276 }