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