/******************************************************************************* * Copyright (c) 2014 Jeff Martin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * Jeff Martin - initial API and implementation ******************************************************************************/ package cuchaz.enigma.bytecode; import java.util.Map; import java.util.Set; import javassist.ClassMap; import javassist.CtBehavior; import javassist.CtClass; import javassist.bytecode.ConstPool; import javassist.bytecode.Descriptor; import javassist.bytecode.InnerClassesAttribute; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cuchaz.enigma.mapping.ClassEntry; import cuchaz.enigma.mapping.SignatureUpdater; import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater; public class ClassRenamer { public static void renameClasses(CtClass c, Map map) { // build the map used by javassist ClassMap nameMap = new ClassMap(); for (Map.Entry entry : map.entrySet()) { nameMap.put(entry.getKey().getName(), entry.getValue().getName()); } c.replaceClassName(nameMap); // replace simple names in the InnerClasses attribute too ConstPool constants = c.getClassFile().getConstPool(); InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); if (attr != null) { for (int i = 0; i < attr.tableLength(); i++) { ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); if (attr.innerNameIndex(i) != 0) { attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnerClassName())); } /* DEBUG System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); */ } } } public static Set getAllClassEntries(final CtClass c) { // get the classes that javassist knows about final Set entries = Sets.newHashSet(); ClassMap map = new ClassMap() { @Override public Object get(Object obj) { if (obj instanceof String) { String str = (String)obj; // javassist throws a lot of weird things at this map // I either have to implement my on class scanner, or just try to filter out the weirdness // I'm opting to filter out the weirdness for now // skip anything with generic arguments if (str.indexOf('<') >= 0 || str.indexOf('>') >= 0 || str.indexOf(';') >= 0) { return null; } // convert path/to/class.inner to path/to/class$inner str = str.replace('.', '$'); // remember everything else entries.add(new ClassEntry(str)); } return null; } private static final long serialVersionUID = -202160293602070641L; }; c.replaceClassName(map); return entries; } public static void moveAllClassesOutOfDefaultPackage(CtClass c, String newPackageName) { // rename all classes Map map = Maps.newHashMap(); for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { if (classEntry.isInDefaultPackage()) { map.put(classEntry, new ClassEntry(newPackageName + "/" + classEntry.getName())); } } ClassRenamer.renameClasses(c, map); // TEMP for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { if (classEntry.isInDefaultPackage()) { throw new Error("!!! " + classEntry); } } // TEMP for (CtBehavior behavior : c.getDeclaredBehaviors()) { if (behavior.getSignature() == null) { continue; } SignatureUpdater.update(behavior.getSignature(), new ClassNameUpdater() { @Override public String update(String className) { ClassEntry classEntry = new ClassEntry(className); if (classEntry.isInDefaultPackage()) { throw new Error("!!! " + className); } return className; } }); } } }