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.Lists; 043import com.google.common.collect.Maps; 044import com.google.common.collect.Sets; 045import com.google.common.collect.ImmutableBiMap.Builder; 046import com.google.common.io.CharStreams; 047import com.google.common.io.InputSupplier; 048 049import cpw.mods.fml.common.FMLLog; 050import cpw.mods.fml.relauncher.FMLRelaunchLog; 051import cpw.mods.fml.relauncher.RelaunchClassLoader; 052 053public class FMLDeobfuscatingRemapper extends Remapper { 054 public static final FMLDeobfuscatingRemapper INSTANCE = new FMLDeobfuscatingRemapper(); 055 056 private BiMap<String, String> classNameBiMap; 057 private BiMap<String, String> mcpNameBiMap; 058 059 private Map<String,Map<String,String>> rawFieldMaps; 060 private Map<String,Map<String,String>> rawMethodMaps; 061 062 private Map<String,Map<String,String>> fieldNameMaps; 063 private Map<String,Map<String,String>> methodNameMaps; 064 065 private RelaunchClassLoader classLoader; 066 067 private FMLDeobfuscatingRemapper() 068 { 069 classNameBiMap=ImmutableBiMap.of(); 070 mcpNameBiMap=ImmutableBiMap.of(); 071 } 072 073 public void setup(File mcDir, RelaunchClassLoader classLoader, String deobfFileName) 074 { 075 this.classLoader = classLoader; 076 try 077 { 078 File libDir = new File(mcDir, "lib"); 079 File mapData = new File(libDir, deobfFileName); 080 mapData = mapData.getCanonicalFile(); 081 ZipFile mapZip = new ZipFile(mapData); 082 ZipEntry classData = mapZip.getEntry("joined.srg"); 083 ZipInputSupplier zis = new ZipInputSupplier(mapZip, classData); 084 InputSupplier<InputStreamReader> srgSupplier = CharStreams.newReaderSupplier(zis,Charsets.UTF_8); 085 List<String> srgList = CharStreams.readLines(srgSupplier); 086 rawMethodMaps = Maps.newHashMap(); 087 rawFieldMaps = Maps.newHashMap(); 088 Builder<String, String> builder = ImmutableBiMap.<String,String>builder(); 089 Builder<String, String> mcpBuilder = ImmutableBiMap.<String,String>builder(); 090 Splitter splitter = Splitter.on(CharMatcher.anyOf(": ")).omitEmptyStrings().trimResults(); 091 for (String line : srgList) 092 { 093 String[] parts = Iterables.toArray(splitter.split(line),String.class); 094 String typ = parts[0]; 095 if ("CL".equals(typ)) 096 { 097 parseClass(builder, parts); 098 parseMCPClass(mcpBuilder,parts); 099 } 100 else if ("MD".equals(typ)) 101 { 102 parseMethod(parts); 103 } 104 else if ("FD".equals(typ)) 105 { 106 parseField(parts); 107 } 108 } 109 classNameBiMap = builder.build(); 110 // Special case some mappings for modloader mods 111 mcpBuilder.put("BaseMod","net/minecraft/src/BaseMod"); 112 mcpBuilder.put("ModLoader","net/minecraft/src/ModLoader"); 113 mcpBuilder.put("EntityRendererProxy","net/minecraft/src/EntityRendererProxy"); 114 mcpBuilder.put("MLProp","net/minecraft/src/MLProp"); 115 mcpBuilder.put("TradeEntry","net/minecraft/src/TradeEntry"); 116 mcpNameBiMap = mcpBuilder.build(); 117 } 118 catch (IOException ioe) 119 { 120 FMLRelaunchLog.log(Level.SEVERE, ioe, "An error occurred loading the deobfuscation map data"); 121 } 122 methodNameMaps = Maps.newHashMapWithExpectedSize(rawMethodMaps.size()); 123 fieldNameMaps = Maps.newHashMapWithExpectedSize(rawFieldMaps.size()); 124 } 125 126 public boolean isRemappedClass(String className) 127 { 128 className = className.replace('.', '/'); 129 return classNameBiMap.containsKey(className) || mcpNameBiMap.containsKey(className) || (!classNameBiMap.isEmpty() && className.indexOf('/') == -1); 130 } 131 132 private void parseField(String[] parts) 133 { 134 String oldSrg = parts[1]; 135 int lastOld = oldSrg.lastIndexOf('/'); 136 String cl = oldSrg.substring(0,lastOld); 137 String oldName = oldSrg.substring(lastOld+1); 138 String newSrg = parts[2]; 139 int lastNew = newSrg.lastIndexOf('/'); 140 String newName = newSrg.substring(lastNew+1); 141 if (!rawFieldMaps.containsKey(cl)) 142 { 143 rawFieldMaps.put(cl, Maps.<String,String>newHashMap()); 144 } 145 rawFieldMaps.get(cl).put(oldName, newName); 146 } 147 148 private void parseClass(Builder<String, String> builder, String[] parts) 149 { 150 builder.put(parts[1],parts[2]); 151 } 152 153 private void parseMCPClass(Builder<String, String> builder, String[] parts) 154 { 155 int clIdx = parts[2].lastIndexOf('/'); 156 builder.put("net/minecraft/src/"+parts[2].substring(clIdx+1),parts[2]); 157 } 158 159 private void parseMethod(String[] parts) 160 { 161 String oldSrg = parts[1]; 162 int lastOld = oldSrg.lastIndexOf('/'); 163 String cl = oldSrg.substring(0,lastOld); 164 String oldName = oldSrg.substring(lastOld+1); 165 String sig = parts[2]; 166 String newSrg = parts[3]; 167 int lastNew = newSrg.lastIndexOf('/'); 168 String newName = newSrg.substring(lastNew+1); 169 if (!rawMethodMaps.containsKey(cl)) 170 { 171 rawMethodMaps.put(cl, Maps.<String,String>newHashMap()); 172 } 173 rawMethodMaps.get(cl).put(oldName+sig, newName); 174 } 175 176 @Override 177 public String mapFieldName(String owner, String name, String desc) 178 { 179 if (classNameBiMap == null || classNameBiMap.isEmpty()) 180 { 181 return name; 182 } 183 Map<String, String> fieldMap = getFieldMap(owner); 184 return fieldMap!=null && fieldMap.containsKey(name) ? fieldMap.get(name) : name; 185 } 186 187 @Override 188 public String map(String typeName) 189 { 190 if (classNameBiMap == null || classNameBiMap.isEmpty()) 191 { 192 return typeName; 193 } 194 195 int dollarIdx = typeName.indexOf('$'); 196 String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName; 197 String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : ""; 198 199 String result = classNameBiMap.containsKey(realType) ? classNameBiMap.get(realType) : mcpNameBiMap.containsKey(realType) ? mcpNameBiMap.get(realType) : realType; 200 result = dollarIdx > -1 ? result+"$"+subType : result; 201// System.out.printf("Mapping %s=>%s\n",typeName,result); 202 return result; 203 } 204 205 public String unmap(String typeName) 206 { 207 if (classNameBiMap == null || classNameBiMap.isEmpty()) 208 { 209 return typeName; 210 } 211 int dollarIdx = typeName.indexOf('$'); 212 String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName; 213 String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : ""; 214 215 216 String result = classNameBiMap.containsValue(realType) ? classNameBiMap.inverse().get(realType) : mcpNameBiMap.containsValue(realType) ? mcpNameBiMap.inverse().get(realType) : realType; 217 result = dollarIdx > -1 ? result+"$"+subType : result; 218// System.out.printf("Unmapping %s=>%s\n",typeName,result); 219 return result; 220 } 221 222 223 @Override 224 public String mapMethodName(String owner, String name, String desc) 225 { 226 if (classNameBiMap==null || classNameBiMap.isEmpty()) 227 { 228 return name; 229 } 230 Map<String, String> methodMap = getMethodMap(owner); 231 String methodDescriptor = name+desc; 232 return methodMap!=null && methodMap.containsKey(methodDescriptor) ? methodMap.get(methodDescriptor) : name; 233 } 234 235 private Map<String,String> getFieldMap(String className) 236 { 237 if (!fieldNameMaps.containsKey(className)) 238 { 239 findAndMergeSuperMaps(className); 240 } 241 return fieldNameMaps.get(className); 242 } 243 244 private Map<String,String> getMethodMap(String className) 245 { 246 if (!methodNameMaps.containsKey(className)) 247 { 248 findAndMergeSuperMaps(className); 249 } 250 return methodNameMaps.get(className); 251 } 252 253 private void findAndMergeSuperMaps(String name) 254 { 255 try 256 { 257 byte[] classBytes = classLoader.getClassBytes(name); 258 if (classBytes == null) 259 { 260 return; 261 } 262 ClassReader cr = new ClassReader(classBytes); 263 String superName = cr.getSuperName(); 264 String[] interfaces = cr.getInterfaces(); 265 if (interfaces == null) 266 { 267 interfaces = new String[0]; 268 } 269 mergeSuperMaps(name, superName, interfaces); 270 } 271 catch (IOException e) 272 { 273 e.printStackTrace(); 274 } 275 } 276 public void mergeSuperMaps(String name, String superName, String[] interfaces) 277 { 278// System.out.printf("Computing super maps for %s: %s %s\n", name, superName, Arrays.asList(interfaces)); 279 if (classNameBiMap == null || classNameBiMap.isEmpty()) 280 { 281 return; 282 } 283 // Skip Object 284 if (Strings.isNullOrEmpty(superName)) 285 { 286 return; 287 } 288 289 List<String> allParents = ImmutableList.<String>builder().add(superName).addAll(Arrays.asList(interfaces)).build(); 290 // generate maps for all parent objects 291 for (String parentThing : allParents) 292 { 293 if (!methodNameMaps.containsKey(parentThing)) 294 { 295 findAndMergeSuperMaps(parentThing); 296 } 297 } 298 Map<String, String> methodMap = Maps.<String,String>newHashMap(); 299 Map<String, String> fieldMap = Maps.<String,String>newHashMap(); 300 for (String parentThing : allParents) 301 { 302 if (methodNameMaps.containsKey(parentThing)) 303 { 304 methodMap.putAll(methodNameMaps.get(parentThing)); 305 } 306 if (fieldNameMaps.containsKey(parentThing)) 307 { 308 fieldMap.putAll(fieldNameMaps.get(parentThing)); 309 } 310 } 311 if (rawMethodMaps.containsKey(name)) 312 { 313 methodMap.putAll(rawMethodMaps.get(name)); 314 } 315 if (rawFieldMaps.containsKey(name)) 316 { 317 fieldMap.putAll(rawFieldMaps.get(name)); 318 } 319 methodNameMaps.put(name, ImmutableMap.copyOf(methodMap)); 320 fieldNameMaps.put(name, ImmutableMap.copyOf(fieldMap)); 321// System.out.printf("Maps: %s %s\n", name, methodMap); 322 } 323}