001/* 002 * Forge Mod Loader 003 * Copyright (c) 2012-2013 cpw. 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser Public License v2.1 006 * which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 008 * 009 * Contributors: 010 * cpw - implementation 011 */ 012 013package cpw.mods.fml.common.asm.transformers.deobf; 014 015import java.io.File; 016import java.io.IOException; 017import java.io.InputStream; 018import java.io.InputStreamReader; 019import java.nio.charset.Charset; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.logging.Level; 026import java.util.zip.ZipEntry; 027import java.util.zip.ZipFile; 028 029import org.objectweb.asm.ClassReader; 030import org.objectweb.asm.commons.Remapper; 031 032import com.google.common.base.CharMatcher; 033import com.google.common.base.Charsets; 034import com.google.common.base.Splitter; 035import com.google.common.base.Strings; 036import com.google.common.collect.BiMap; 037import com.google.common.collect.HashBiMap; 038import com.google.common.collect.ImmutableBiMap; 039import com.google.common.collect.ImmutableList; 040import com.google.common.collect.ImmutableMap; 041import com.google.common.collect.Iterables; 042import com.google.common.collect.ListMultimap; 043import com.google.common.collect.Lists; 044import com.google.common.collect.Maps; 045import com.google.common.collect.Sets; 046import com.google.common.collect.ImmutableBiMap.Builder; 047import com.google.common.io.CharStreams; 048import com.google.common.io.InputSupplier; 049 050import cpw.mods.fml.common.FMLLog; 051import cpw.mods.fml.relauncher.FMLRelaunchLog; 052import cpw.mods.fml.relauncher.RelaunchClassLoader; 053import org.objectweb.asm.tree.ClassNode; 054import org.objectweb.asm.tree.FieldNode; 055 056public class FMLDeobfuscatingRemapper extends Remapper { 057 public static final FMLDeobfuscatingRemapper INSTANCE = new FMLDeobfuscatingRemapper(); 058 059 private BiMap<String, String> classNameBiMap; 060 private BiMap<String, String> mcpNameBiMap; 061 062 private Map<String,Map<String,String>> rawFieldMaps; 063 private Map<String,Map<String,String>> rawMethodMaps; 064 065 private Map<String,Map<String,String>> fieldNameMaps; 066 private Map<String,Map<String,String>> methodNameMaps; 067 068 private RelaunchClassLoader classLoader; 069 070 private FMLDeobfuscatingRemapper() 071 { 072 classNameBiMap=ImmutableBiMap.of(); 073 mcpNameBiMap=ImmutableBiMap.of(); 074 } 075 076 public void setup(File mcDir, RelaunchClassLoader classLoader, String deobfFileName) 077 { 078 this.classLoader = classLoader; 079 try 080 { 081 File libDir = new File(mcDir, "lib"); 082 File mapData = new File(libDir, deobfFileName); 083 mapData = mapData.getCanonicalFile(); 084 ZipFile mapZip = new ZipFile(mapData); 085 ZipEntry classData = mapZip.getEntry("joined.srg"); 086 ZipInputSupplier zis = new ZipInputSupplier(mapZip, classData); 087 InputSupplier<InputStreamReader> srgSupplier = CharStreams.newReaderSupplier(zis,Charsets.UTF_8); 088 List<String> srgList = CharStreams.readLines(srgSupplier); 089 rawMethodMaps = Maps.newHashMap(); 090 rawFieldMaps = Maps.newHashMap(); 091 Builder<String, String> builder = ImmutableBiMap.<String,String>builder(); 092 Builder<String, String> mcpBuilder = ImmutableBiMap.<String,String>builder(); 093 Splitter splitter = Splitter.on(CharMatcher.anyOf(": ")).omitEmptyStrings().trimResults(); 094 for (String line : srgList) 095 { 096 String[] parts = Iterables.toArray(splitter.split(line),String.class); 097 String typ = parts[0]; 098 if ("CL".equals(typ)) 099 { 100 parseClass(builder, parts); 101 parseMCPClass(mcpBuilder,parts); 102 } 103 else if ("MD".equals(typ)) 104 { 105 parseMethod(parts); 106 } 107 else if ("FD".equals(typ)) 108 { 109 parseField(parts); 110 } 111 } 112 classNameBiMap = builder.build(); 113 // Special case some mappings for modloader mods 114 mcpBuilder.put("BaseMod","net/minecraft/src/BaseMod"); 115 mcpBuilder.put("ModLoader","net/minecraft/src/ModLoader"); 116 mcpBuilder.put("EntityRendererProxy","net/minecraft/src/EntityRendererProxy"); 117 mcpBuilder.put("MLProp","net/minecraft/src/MLProp"); 118 mcpBuilder.put("TradeEntry","net/minecraft/src/TradeEntry"); 119 mcpNameBiMap = mcpBuilder.build(); 120 } 121 catch (IOException ioe) 122 { 123 FMLRelaunchLog.log(Level.SEVERE, ioe, "An error occurred loading the deobfuscation map data"); 124 } 125 methodNameMaps = Maps.newHashMapWithExpectedSize(rawMethodMaps.size()); 126 fieldNameMaps = Maps.newHashMapWithExpectedSize(rawFieldMaps.size()); 127 } 128 129 public boolean isRemappedClass(String className) 130 { 131 className = className.replace('.', '/'); 132 return classNameBiMap.containsKey(className) || mcpNameBiMap.containsKey(className) || (!classNameBiMap.isEmpty() && className.indexOf('/') == -1); 133 } 134 135 private void parseField(String[] parts) 136 { 137 String oldSrg = parts[1]; 138 int lastOld = oldSrg.lastIndexOf('/'); 139 String cl = oldSrg.substring(0,lastOld); 140 String oldName = oldSrg.substring(lastOld+1); 141 String newSrg = parts[2]; 142 int lastNew = newSrg.lastIndexOf('/'); 143 String newName = newSrg.substring(lastNew+1); 144 if (!rawFieldMaps.containsKey(cl)) 145 { 146 rawFieldMaps.put(cl, Maps.<String,String>newHashMap()); 147 } 148 rawFieldMaps.get(cl).put(oldName + ":" + getFieldType(cl, oldName), newName); 149 rawFieldMaps.get(cl).put(oldName + ":null", newName); 150 } 151 152 /* 153 * Cache the field descriptions for classes so we don't repeatedly reload the same data again and again 154 */ 155 private Map<String,Map<String,String>> fieldDescriptions = Maps.newHashMap(); 156 157 private String getFieldType(String owner, String name) 158 { 159 if (fieldDescriptions.containsKey(owner)) 160 { 161 return fieldDescriptions.get(owner).get(name); 162 } 163 synchronized (fieldDescriptions) 164 { 165 try 166 { 167 byte[] classBytes = classLoader.getClassBytes(owner); 168 if (classBytes == null) 169 { 170 return null; 171 } 172 ClassReader cr = new ClassReader(classBytes); 173 ClassNode classNode = new ClassNode(); 174 cr.accept(classNode, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 175 Map<String,String> resMap = Maps.newHashMap(); 176 for (FieldNode fieldNode : (List<FieldNode>) classNode.fields) { 177 resMap.put(fieldNode.name, fieldNode.desc); 178 } 179 fieldDescriptions.put(owner, resMap); 180 return resMap.get(name); 181 } 182 catch (IOException e) 183 { 184 FMLLog.log(Level.SEVERE,e, "A critical exception occured reading a class file %s", owner); 185 } 186 return null; 187 } 188 } 189 190 private void parseClass(Builder<String, String> builder, String[] parts) 191 { 192 builder.put(parts[1],parts[2]); 193 } 194 195 private void parseMCPClass(Builder<String, String> builder, String[] parts) 196 { 197 int clIdx = parts[2].lastIndexOf('/'); 198 builder.put("net/minecraft/src/"+parts[2].substring(clIdx+1),parts[2]); 199 } 200 201 private void parseMethod(String[] parts) 202 { 203 String oldSrg = parts[1]; 204 int lastOld = oldSrg.lastIndexOf('/'); 205 String cl = oldSrg.substring(0,lastOld); 206 String oldName = oldSrg.substring(lastOld+1); 207 String sig = parts[2]; 208 String newSrg = parts[3]; 209 int lastNew = newSrg.lastIndexOf('/'); 210 String newName = newSrg.substring(lastNew+1); 211 if (!rawMethodMaps.containsKey(cl)) 212 { 213 rawMethodMaps.put(cl, Maps.<String,String>newHashMap()); 214 } 215 rawMethodMaps.get(cl).put(oldName+sig, newName); 216 } 217 218 @Override 219 public String mapFieldName(String owner, String name, String desc) 220 { 221 if (classNameBiMap == null || classNameBiMap.isEmpty()) 222 { 223 return name; 224 } 225 Map<String, String> fieldMap = getFieldMap(owner); 226 return fieldMap!=null && fieldMap.containsKey(name+":"+desc) ? fieldMap.get(name+":"+desc) : name; 227 } 228 229 @Override 230 public String map(String typeName) 231 { 232 if (classNameBiMap == null || classNameBiMap.isEmpty()) 233 { 234 return typeName; 235 } 236 237 int dollarIdx = typeName.indexOf('$'); 238 String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName; 239 String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : ""; 240 241 String result = classNameBiMap.containsKey(realType) ? classNameBiMap.get(realType) : mcpNameBiMap.containsKey(realType) ? mcpNameBiMap.get(realType) : realType; 242 result = dollarIdx > -1 ? result+"$"+subType : result; 243// System.out.printf("Mapping %s=>%s\n",typeName,result); 244 return result; 245 } 246 247 public String unmap(String typeName) 248 { 249 if (classNameBiMap == null || classNameBiMap.isEmpty()) 250 { 251 return typeName; 252 } 253 int dollarIdx = typeName.indexOf('$'); 254 String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName; 255 String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : ""; 256 257 258 String result = classNameBiMap.containsValue(realType) ? classNameBiMap.inverse().get(realType) : mcpNameBiMap.containsValue(realType) ? mcpNameBiMap.inverse().get(realType) : realType; 259 result = dollarIdx > -1 ? result+"$"+subType : result; 260// System.out.printf("Unmapping %s=>%s\n",typeName,result); 261 return result; 262 } 263 264 265 @Override 266 public String mapMethodName(String owner, String name, String desc) 267 { 268 if (classNameBiMap==null || classNameBiMap.isEmpty()) 269 { 270 return name; 271 } 272 Map<String, String> methodMap = getMethodMap(owner); 273 String methodDescriptor = name+desc; 274 return methodMap!=null && methodMap.containsKey(methodDescriptor) ? methodMap.get(methodDescriptor) : name; 275 } 276 277 private Map<String,String> getFieldMap(String className) 278 { 279 if (!fieldNameMaps.containsKey(className)) 280 { 281 findAndMergeSuperMaps(className); 282 } 283 return fieldNameMaps.get(className); 284 } 285 286 private Map<String,String> getMethodMap(String className) 287 { 288 if (!methodNameMaps.containsKey(className)) 289 { 290 findAndMergeSuperMaps(className); 291 } 292 return methodNameMaps.get(className); 293 } 294 295 private void findAndMergeSuperMaps(String name) 296 { 297 try 298 { 299 byte[] classBytes = classLoader.getClassBytes(name); 300 if (classBytes == null) 301 { 302 return; 303 } 304 ClassReader cr = new ClassReader(classBytes); 305 String superName = cr.getSuperName(); 306 String[] interfaces = cr.getInterfaces(); 307 if (interfaces == null) 308 { 309 interfaces = new String[0]; 310 } 311 mergeSuperMaps(name, superName, interfaces); 312 } 313 catch (IOException e) 314 { 315 e.printStackTrace(); 316 } 317 } 318 public void mergeSuperMaps(String name, String superName, String[] interfaces) 319 { 320// System.out.printf("Computing super maps for %s: %s %s\n", name, superName, Arrays.asList(interfaces)); 321 if (classNameBiMap == null || classNameBiMap.isEmpty()) 322 { 323 return; 324 } 325 // Skip Object 326 if (Strings.isNullOrEmpty(superName)) 327 { 328 return; 329 } 330 331 List<String> allParents = ImmutableList.<String>builder().add(superName).addAll(Arrays.asList(interfaces)).build(); 332 // generate maps for all parent objects 333 for (String parentThing : allParents) 334 { 335 if (!methodNameMaps.containsKey(parentThing)) 336 { 337 findAndMergeSuperMaps(parentThing); 338 } 339 } 340 Map<String, String> methodMap = Maps.<String,String>newHashMap(); 341 Map<String, String> fieldMap = Maps.<String,String>newHashMap(); 342 for (String parentThing : allParents) 343 { 344 if (methodNameMaps.containsKey(parentThing)) 345 { 346 methodMap.putAll(methodNameMaps.get(parentThing)); 347 } 348 if (fieldNameMaps.containsKey(parentThing)) 349 { 350 fieldMap.putAll(fieldNameMaps.get(parentThing)); 351 } 352 } 353 if (rawMethodMaps.containsKey(name)) 354 { 355 methodMap.putAll(rawMethodMaps.get(name)); 356 } 357 if (rawFieldMaps.containsKey(name)) 358 { 359 fieldMap.putAll(rawFieldMaps.get(name)); 360 } 361 methodNameMaps.put(name, ImmutableMap.copyOf(methodMap)); 362 fieldNameMaps.put(name, ImmutableMap.copyOf(fieldMap)); 363// System.out.printf("Maps: %s %s\n", name, methodMap); 364 } 365}