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 }