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 return classNameBiMap.containsKey(className) || mcpNameBiMap.containsKey(className); 129 } 130 131 private void parseField(String[] parts) 132 { 133 String oldSrg = parts[1]; 134 int lastOld = oldSrg.lastIndexOf('/'); 135 String cl = oldSrg.substring(0,lastOld); 136 String oldName = oldSrg.substring(lastOld+1); 137 String newSrg = parts[2]; 138 int lastNew = newSrg.lastIndexOf('/'); 139 String newName = newSrg.substring(lastNew+1); 140 if (!rawFieldMaps.containsKey(cl)) 141 { 142 rawFieldMaps.put(cl, Maps.<String,String>newHashMap()); 143 } 144 rawFieldMaps.get(cl).put(oldName, newName); 145 } 146 147 private void parseClass(Builder<String, String> builder, String[] parts) 148 { 149 builder.put(parts[1],parts[2]); 150 } 151 152 private void parseMCPClass(Builder<String, String> builder, String[] parts) 153 { 154 int clIdx = parts[2].lastIndexOf('/'); 155 builder.put("net/minecraft/src/"+parts[2].substring(clIdx+1),parts[2]); 156 } 157 158 private void parseMethod(String[] parts) 159 { 160 String oldSrg = parts[1]; 161 int lastOld = oldSrg.lastIndexOf('/'); 162 String cl = oldSrg.substring(0,lastOld); 163 String oldName = oldSrg.substring(lastOld+1); 164 String sig = parts[2]; 165 String newSrg = parts[3]; 166 int lastNew = newSrg.lastIndexOf('/'); 167 String newName = newSrg.substring(lastNew+1); 168 if (!rawMethodMaps.containsKey(cl)) 169 { 170 rawMethodMaps.put(cl, Maps.<String,String>newHashMap()); 171 } 172 rawMethodMaps.get(cl).put(oldName+sig, newName); 173 } 174 175 @Override 176 public String mapFieldName(String owner, String name, String desc) 177 { 178 if (classNameBiMap == null || classNameBiMap.isEmpty()) 179 { 180 return name; 181 } 182 Map<String, String> fieldMap = getFieldMap(owner); 183 return fieldMap!=null && fieldMap.containsKey(name) ? fieldMap.get(name) : name; 184 } 185 186 @Override 187 public String map(String typeName) 188 { 189 if (classNameBiMap == null || classNameBiMap.isEmpty()) 190 { 191 return typeName; 192 } 193 194 int dollarIdx = typeName.indexOf('$'); 195 String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName; 196 String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : ""; 197 198 String result = classNameBiMap.containsKey(realType) ? classNameBiMap.get(realType) : mcpNameBiMap.containsKey(realType) ? mcpNameBiMap.get(realType) : realType; 199 result = dollarIdx > -1 ? result+"$"+subType : result; 200// System.out.printf("Mapping %s=>%s\n",typeName,result); 201 return result; 202 } 203 204 public String unmap(String typeName) 205 { 206 if (classNameBiMap == null || classNameBiMap.isEmpty()) 207 { 208 return typeName; 209 } 210 int dollarIdx = typeName.indexOf('$'); 211 String realType = dollarIdx > -1 ? typeName.substring(0, dollarIdx) : typeName; 212 String subType = dollarIdx > -1 ? typeName.substring(dollarIdx+1) : ""; 213 214 215 String result = classNameBiMap.containsValue(realType) ? classNameBiMap.inverse().get(realType) : mcpNameBiMap.containsValue(realType) ? mcpNameBiMap.inverse().get(realType) : realType; 216 result = dollarIdx > -1 ? result+"$"+subType : result; 217// System.out.printf("Unmapping %s=>%s\n",typeName,result); 218 return result; 219 } 220 221 222 @Override 223 public String mapMethodName(String owner, String name, String desc) 224 { 225 if (classNameBiMap==null || classNameBiMap.isEmpty()) 226 { 227 return name; 228 } 229 Map<String, String> methodMap = getMethodMap(owner); 230 String methodDescriptor = name+desc; 231 return methodMap!=null && methodMap.containsKey(methodDescriptor) ? methodMap.get(methodDescriptor) : name; 232 } 233 234 private Map<String,String> getFieldMap(String className) 235 { 236 if (!fieldNameMaps.containsKey(className)) 237 { 238 findAndMergeSuperMaps(className); 239 } 240 return fieldNameMaps.get(className); 241 } 242 243 private Map<String,String> getMethodMap(String className) 244 { 245 if (!methodNameMaps.containsKey(className)) 246 { 247 findAndMergeSuperMaps(className); 248 } 249 return methodNameMaps.get(className); 250 } 251 252 private void findAndMergeSuperMaps(String name) 253 { 254 try 255 { 256 byte[] classBytes = classLoader.getClassBytes(name); 257 if (classBytes == null) 258 { 259 return; 260 } 261 ClassReader cr = new ClassReader(classBytes); 262 String superName = cr.getSuperName(); 263 String[] interfaces = cr.getInterfaces(); 264 if (interfaces == null) 265 { 266 interfaces = new String[0]; 267 } 268 mergeSuperMaps(name, superName, interfaces); 269 } 270 catch (IOException e) 271 { 272 e.printStackTrace(); 273 } 274 } 275 public void mergeSuperMaps(String name, String superName, String[] interfaces) 276 { 277// System.out.printf("Computing super maps for %s: %s %s\n", name, superName, Arrays.asList(interfaces)); 278 if (classNameBiMap == null || classNameBiMap.isEmpty()) 279 { 280 return; 281 } 282 // Skip Object 283 if (Strings.isNullOrEmpty(superName)) 284 { 285 return; 286 } 287 288 List<String> allParents = ImmutableList.<String>builder().add(superName).addAll(Arrays.asList(interfaces)).build(); 289 // generate maps for all parent objects 290 for (String parentThing : allParents) 291 { 292 if (!methodNameMaps.containsKey(parentThing)) 293 { 294 findAndMergeSuperMaps(parentThing); 295 } 296 } 297 Map<String, String> methodMap = Maps.<String,String>newHashMap(); 298 Map<String, String> fieldMap = Maps.<String,String>newHashMap(); 299 for (String parentThing : allParents) 300 { 301 if (methodNameMaps.containsKey(parentThing)) 302 { 303 methodMap.putAll(methodNameMaps.get(parentThing)); 304 } 305 if (fieldNameMaps.containsKey(parentThing)) 306 { 307 fieldMap.putAll(fieldNameMaps.get(parentThing)); 308 } 309 } 310 if (rawMethodMaps.containsKey(name)) 311 { 312 methodMap.putAll(rawMethodMaps.get(name)); 313 } 314 if (rawFieldMaps.containsKey(name)) 315 { 316 fieldMap.putAll(rawFieldMaps.get(name)); 317 } 318 methodNameMaps.put(name, ImmutableMap.copyOf(methodMap)); 319 fieldNameMaps.put(name, ImmutableMap.copyOf(fieldMap)); 320// System.out.printf("Maps: %s %s\n", name, methodMap); 321 } 322}