├── LICENSE ├── README.md ├── build.gradle ├── currentmappings.txt ├── settings.gradle └── src └── net └── fybertech └── dynamicmappings ├── AccessUtil.java ├── DynamicMappings.java ├── DynamicRemap.java ├── InheritanceMap.java ├── Mapping.java ├── MappingsClass.java ├── MergeJars.java ├── MethodCallIterator.java ├── MethodCallVisitor.java ├── ModMappings.java ├── ParmParser.java ├── Tweaker.java └── mappers ├── ClientMappings.java ├── MappingsBase.java └── SharedMappings.java /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DynamicMappings 2 | 3 | This project programmatically determines class/field/method mappings for Minecraft snapshots. It began with Minecraft 1.9 and continues into 1.11 (and probably whatever is newest as of reading). It's in a constant state of evolution to keep up with changes from Mojang. 4 | 5 | Please note that this is not a complete set of mappings! It's not comparable to MCP or other static mappings projects. It doesn't use generic class comparison techniques, it uses targeted detection to ensure accuracy. Mapping detection routines are generally added on an as-needed basis, or when they're convenient to add into existing ones. They can also be difficult and time-consuming to write, so many mappings you're familiar with seeing may not presently exist. 6 | 7 | While not often, you will occasionally encounter names which are not always the same as MCP, because being on the bleeding edge of snapshots means we have to come up with our own names. They may be changed later for better code compatibility. 8 | 9 | 10 | 11 | 12 | 13 | #### Contributing 14 | 15 | You're welcome to contribute to this project, but please consider its nature beforehand. DynamicMappings is a house of cards. It's very hierarchical, with one mapping depending on other mappings before it. As Minecraft evolves, it generally breaks mapping detection routines, and thus many mappings further down the chain fall as well. This is good in a way, because it lets us see what's changed and how to adapt where necessary. Sometimes fixing a single mapping fixes an entire chain. 16 | 17 | The absolute worst case scenario is a mapping misdetection, where something is detected as something it isn't at some point in the future, which can lead to many confusing issues to track down and solve. We try to avoid this in particular when at all possible by trying to be reasonably certain of a mapping based on relative information. You might do this through values in the constant pool, class relations, code patterns, fields, methods, etc, etc, etc. Sometimes getting a mapping directly from its owner class is impossible to do with any degree of certainty, so you have to find locations elsewhere in the game where you can more accurately guarantee a match. 18 | 19 | Changing existing mappings routines is generally not necessary. Mappings are already time-consuming enough to write, much less to verify contributions, so try to stick to adding new mappings unless there's an instance where something can be noticeably improved. I'm open to discussing it in such a case. 20 | 21 | Try to keep pull requests small, otherwise it takes much longer to process. Your PR may be rejected if it doesn't appear to adequately verify what it's adding, depending on what that is. It's not personal, so don't take it that way. Mojang could change something at any time and break everything, so ensuring the integrity of mappings is always the priority. 22 | 23 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceSets { 4 | main { 5 | java { 6 | srcDirs = ['src'] 7 | } 8 | } 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | 14 | maven { 15 | url 'https://libraries.minecraft.net/' 16 | } 17 | 18 | maven { 19 | url 'http://www.fybertech.net/maven/' 20 | } 21 | } 22 | 23 | dependencies { 24 | //compile fileTree(dir:'../libs/', excludes:['minecraft/**']) 25 | //compile rootProject.project(':Meddle') 26 | compile 'net.minecraft:launchwrapper:1.11' 27 | compile 'org.ow2.asm:asm-debug-all:5.0.3' 28 | compile 'net.sf.jopt-simple:jopt-simple:4.6' 29 | compile 'net.fybertech:meddle:1.2.2' 30 | } 31 | 32 | gradle.projectsEvaluated { 33 | tasks.withType(JavaCompile) { 34 | options.compilerArgs << "-Xlint:-options" 35 | } 36 | } 37 | 38 | sourceCompatibility = '1.7' 39 | targetCompatibility = '1.7' 40 | 41 | jar { 42 | baseName = 'dynamicmappings' 43 | version = '028' 44 | 45 | manifest { 46 | attributes('TweakClass' : 'net.fybertech.dynamicmappings.Tweaker') 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FyberOptic/DynamicMappings/8373b95c2ced6230196fcdeefd1d55708b84b6f7/settings.gradle -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/AccessUtil.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.URL; 7 | import java.util.ArrayList; 8 | import java.util.Enumeration; 9 | import java.util.List; 10 | 11 | import net.minecraft.launchwrapper.Launch; 12 | 13 | import org.objectweb.asm.Opcodes; 14 | import org.objectweb.asm.tree.ClassNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | import org.objectweb.asm.tree.MethodNode; 17 | 18 | public class AccessUtil 19 | { 20 | public static final int allAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE; 21 | 22 | public List accessTransformerFields = new ArrayList<>(); 23 | public List accessTransformerMethods = new ArrayList<>(); 24 | public List accessTransformerClasses = new ArrayList<>(); // TODO 25 | 26 | private boolean debug = false; 27 | 28 | public void readAllTransformerConfigs() 29 | { 30 | System.out.println("Discovering access transformers..."); 31 | 32 | Enumeration urls = null; 33 | 34 | try { 35 | urls = AccessUtil.class.getClassLoader().getResources("accesstransformer.cfg"); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | if (urls == null) return; 40 | 41 | while (urls.hasMoreElements()) { 42 | URL url = urls.nextElement(); 43 | try { 44 | BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); 45 | 46 | System.out.println("Processing access transformer at " + url); 47 | 48 | String line = null; 49 | while ((line = reader.readLine()) != null) { 50 | // Ignore commented or short lines 51 | if (line.startsWith("#") || line.length() < 1) continue; 52 | 53 | String[] split = line.split(" ", 2); 54 | if (split.length < 2) continue; 55 | 56 | String mode = split[0].toUpperCase(); 57 | String ac = split[1]; 58 | 59 | if (mode.equals("F")) accessTransformerFields.add(ac); 60 | else if (mode.equals("M")) accessTransformerMethods.add(ac); 61 | else if (mode.equals("C")) accessTransformerClasses.add(ac); 62 | } 63 | 64 | reader.close(); 65 | } catch (IOException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | } 70 | 71 | 72 | public void transformDeobfuscatedClass(ClassNode cn) 73 | { 74 | for (String transformer : accessTransformerFields) { 75 | String[] split = transformer.split(" "); 76 | if (split.length != 4) continue; 77 | 78 | String className = split[0]; 79 | String fieldName = split[1]; 80 | String fieldDesc = split[2]; 81 | int access = 0; 82 | try { 83 | access = Integer.parseInt(split[3]); 84 | } 85 | catch (NumberFormatException e) {} 86 | if (access < 1) continue; 87 | 88 | if (!cn.name.equals(className)) continue; 89 | 90 | boolean wildcardName = fieldName.equals("*"); 91 | boolean wildcardDesc = fieldDesc.equals("*"); 92 | 93 | for (FieldNode field : cn.fields) { 94 | if (!field.name.equals(fieldName) && !wildcardName) continue; 95 | if (!field.desc.equals(fieldDesc) && !wildcardDesc) continue; 96 | field.access = (field.access & ~allAccess) | access; 97 | if (debug) System.out.println("Modifying access of " + className + " " + field.name + " " + field.desc); 98 | } 99 | } 100 | 101 | 102 | for (String transformer : accessTransformerMethods) { 103 | String[] split = transformer.split(" "); 104 | if (split.length != 4) continue; 105 | 106 | String className = split[0]; 107 | String methodName = split[1]; 108 | String methodDesc = split[2]; 109 | int access = 0; 110 | try { 111 | access = Integer.parseInt(split[3]); 112 | } 113 | catch (NumberFormatException e) {} 114 | if (access < 1) continue; 115 | 116 | if (!cn.name.equals(className)) continue; 117 | 118 | boolean wildcardName = methodName.equals("*"); 119 | boolean wildcardDesc = methodDesc.equals("*"); 120 | 121 | for (MethodNode method : cn.methods) { 122 | if (!method.name.equals(methodName) && !wildcardName) continue; 123 | if (!method.desc.equals(methodDesc) && !wildcardDesc) continue; 124 | method.access = (method.access & ~allAccess) | access; 125 | if (debug) System.out.println("Modifying access of " + className + " " + method.name + " " + method.desc); 126 | } 127 | } 128 | 129 | 130 | } 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/DynamicMappings.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.PrintWriter; 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Modifier; 12 | import java.net.JarURLConnection; 13 | import java.net.URL; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.Collections; 17 | import java.util.Enumeration; 18 | import java.util.HashMap; 19 | import java.util.HashSet; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Set; 24 | import java.util.jar.JarFile; 25 | 26 | import org.apache.logging.log4j.LogManager; 27 | import org.apache.logging.log4j.Logger; 28 | import org.objectweb.asm.ClassReader; 29 | import org.objectweb.asm.Type; 30 | import org.objectweb.asm.tree.AbstractInsnNode; 31 | import org.objectweb.asm.tree.ClassNode; 32 | import org.objectweb.asm.tree.FieldInsnNode; 33 | import org.objectweb.asm.tree.FieldNode; 34 | import org.objectweb.asm.tree.LdcInsnNode; 35 | import org.objectweb.asm.tree.MethodInsnNode; 36 | import org.objectweb.asm.tree.MethodNode; 37 | 38 | import net.fybertech.dynamicmappings.ParmParser.Parm; 39 | import net.fybertech.meddle.MeddleUtil; 40 | import net.minecraft.launchwrapper.Launch; 41 | 42 | 43 | public class DynamicMappings 44 | { 45 | public static final Logger LOGGER = LogManager.getLogger("Meddle"); 46 | 47 | public static List MAPPINGS_CLASSES = new ArrayList(Arrays.asList(new String[]{ 48 | "net.fybertech.dynamicmappings.mappers.SharedMappings", 49 | "net.fybertech.dynamicmappings.mappers.ClientMappings" 50 | })); 51 | 52 | /** Deobfuscated class -> obfuscated class */ 53 | public static final Map classMappings = new HashMap(); 54 | /** Obfuscated class -> deobfuscated class */ 55 | public static final Map reverseClassMappings = new HashMap(); 56 | 57 | /** Deobfuscated field -> obfuscated field */ 58 | public static final Map fieldMappings = new HashMap(); 59 | /** Obfuscated field -> deobfuscated field */ 60 | public static final Map reverseFieldMappings = new HashMap(); 61 | 62 | /** Deobfuscated method -> obfuscated method */ 63 | public static final Map methodMappings = new HashMap(); 64 | /** Obfuscated method -> deobfuscated method */ 65 | public static final Map reverseMethodMappings = new HashMap(); 66 | 67 | /** Only used when simulatedMappings is enabled, to help with ModMappings */ 68 | public static final Set clientMappingsSet = new HashSet<>(); 69 | /** Only used when simulatedMappings is enabled, to help with ModMappings */ 70 | public static final Set serverMappingsSet = new HashSet<>(); 71 | 72 | /** Used by getClassNode to avoid reloading the classes over and over */ 73 | private static Map cachedClassNodes = new HashMap(); 74 | 75 | 76 | /** 77 | * If set to true, fake mappings populate the map objects above, ignoring whether 78 | * they can actually be located in the Minecraft code. 79 | * 80 | * Used by ModMappings when determining all possible mappings provided without 81 | * needing a Minecraft jar to scan (since ModMappings needs a deobfuscated one, 82 | * which DynamicMappings can't process properly.) 83 | */ 84 | public static boolean simulatedMappings = false; 85 | 86 | 87 | 88 | /** 89 | * Called to initialize all mappings. 90 | */ 91 | public static void generateClassMappings() 92 | { 93 | generateClassLinkages(); 94 | 95 | for (String clazz : MAPPINGS_CLASSES) 96 | { 97 | Class c = null; 98 | try { 99 | c = Class.forName(clazz); 100 | } 101 | catch (ClassNotFoundException e) { 102 | System.out.println("[DynamicMappings] Error - Couldn't find mappings class \"" + clazz + "\""); 103 | } 104 | 105 | if (c != null) { 106 | DynamicMappings.registerMappingsClass(c); 107 | } 108 | 109 | } 110 | } 111 | 112 | 113 | 114 | /** 115 | * Stores relevant information about a mapper method in one structure. 116 | */ 117 | private static class MappingMethod 118 | { 119 | final Method method; 120 | final String[] provides; 121 | final String[] depends; 122 | 123 | final String[] providesMethods; 124 | final String[] dependsMethods; 125 | 126 | final String[] providesFields; 127 | final String[] dependsFields; 128 | 129 | public MappingMethod(Method m, Mapping mapping) 130 | { 131 | method = m; 132 | provides = mapping.provides(); 133 | depends = mapping.depends(); 134 | providesMethods = mapping.providesMethods(); 135 | dependsMethods = mapping.dependsMethods(); 136 | providesFields = mapping.providesFields(); 137 | dependsFields = mapping.dependsFields(); 138 | } 139 | } 140 | 141 | 142 | 143 | /** 144 | * Parses a class for dynamic mappings. 145 | * Methods adding mappings should use the @Mapping annotation. 146 | * 147 | * The class may optionally use @MappingsClass annotation if it includes 148 | * client or sever-side mappings. 149 | * 150 | * @param mappingsClass - The class to process for mapper methods. 151 | * @return True if all mappings were successfully discovered. 152 | */ 153 | public static boolean registerMappingsClass(Class mappingsClass) 154 | { 155 | List mappingMethods = new ArrayList(); 156 | 157 | boolean clientSide = false; 158 | boolean serverSide = false; 159 | 160 | if (mappingsClass.isAnnotationPresent(MappingsClass.class)) { 161 | MappingsClass mc = mappingsClass.getAnnotation(MappingsClass.class); 162 | clientSide = mc.clientSide(); 163 | serverSide = mc.serverSide(); 164 | } 165 | 166 | 167 | if (!simulatedMappings && clientSide && !MeddleUtil.isClientJar()) { 168 | System.out.println("[DynamicMappings] Ignoring client-side class " + mappingsClass.getName()); 169 | return false; 170 | } 171 | 172 | if (!simulatedMappings && serverSide && MeddleUtil.isClientJar()) { 173 | System.out.println("[DynamicMappings] Ignoring server-side class " + mappingsClass.getName()); 174 | return false; 175 | } 176 | 177 | System.out.println("[DynamicMappings] Processing class " + mappingsClass.getName()); 178 | 179 | 180 | for (Method method : mappingsClass.getMethods()) 181 | { 182 | if (!method.isAnnotationPresent(Mapping.class)) continue; 183 | Mapping mapping = method.getAnnotation(Mapping.class); 184 | mappingMethods.add(new MappingMethod(method, mapping)); 185 | } 186 | 187 | Object mappingsObject = null; 188 | try { 189 | Constructor mappingsConstructor = mappingsClass.getConstructor(new Class[0]); 190 | mappingsObject = mappingsConstructor.newInstance(); 191 | } catch (Exception e) { 192 | e.printStackTrace(); 193 | } 194 | 195 | 196 | while (true) 197 | { 198 | int startSize = mappingMethods.size(); 199 | for (Iterator it = mappingMethods.iterator(); it.hasNext();) 200 | { 201 | MappingMethod mm = it.next(); 202 | boolean isStatic = Modifier.isStatic(mm.method.getModifiers()); 203 | 204 | boolean hasDepends = true; 205 | for (String depend : mm.depends) { 206 | if (!classMappings.keySet().contains(depend)) hasDepends = false; 207 | } 208 | for (String depend : mm.dependsFields) { 209 | if (!fieldMappings.keySet().contains(depend)) hasDepends = false; 210 | } 211 | for (String depend : mm.dependsMethods) { 212 | if (!methodMappings.keySet().contains(depend)) hasDepends = false; 213 | } 214 | if (!hasDepends) continue; 215 | 216 | if (!simulatedMappings) { 217 | try { 218 | if (isStatic) mm.method.invoke(null); 219 | else mm.method.invoke(mappingsObject, (Object[])null); 220 | } catch (Exception e) { 221 | e.printStackTrace(); 222 | } 223 | } 224 | else { 225 | for (String s : mm.provides) { 226 | classMappings.put(s, "---"); 227 | if (clientSide) clientMappingsSet.add(s); 228 | if (serverSide) serverMappingsSet.add(s); 229 | } 230 | for (String s : mm.providesFields) { 231 | fieldMappings.put(s, "--- --- ---"); 232 | if (clientSide) clientMappingsSet.add(s); 233 | if (serverSide) serverMappingsSet.add(s); 234 | } 235 | for (String s : mm.providesMethods) { 236 | methodMappings.put(s, "--- --- ---"); 237 | if (clientSide) clientMappingsSet.add(s); 238 | if (serverSide) serverMappingsSet.add(s); 239 | } 240 | } 241 | 242 | for (String provider : mm.provides) 243 | { 244 | if (!classMappings.keySet().contains(provider)) 245 | System.out.println(mm.method.getName() + " didn't provide mapping for class " + provider); 246 | } 247 | 248 | for (String provider : mm.providesFields) 249 | { 250 | if (!fieldMappings.keySet().contains(provider)) 251 | System.out.println(mm.method.getName() + " didn't provide mapping for field " + provider); 252 | } 253 | 254 | for (String provider : mm.providesMethods) 255 | { 256 | if (!methodMappings.keySet().contains(provider)) 257 | System.out.println(mm.method.getName() + " didn't provide mapping for method " + provider); 258 | } 259 | 260 | it.remove(); 261 | } 262 | 263 | if (mappingMethods.size() == 0) return true; 264 | 265 | if (startSize == mappingMethods.size()) 266 | { 267 | System.out.println("Unmet mapping dependencies in " + mappingsClass.getName() + "!"); 268 | for (MappingMethod mm : mappingMethods) { 269 | System.out.println(" Mapper Method: " + mm.method.getName()); 270 | for (String depend : mm.depends) { 271 | if (!classMappings.keySet().contains(depend)) System.out.println(" Class: " + depend); 272 | } 273 | for (String depend : mm.dependsFields) { 274 | if (!fieldMappings.keySet().contains(depend)) System.out.println(" Field: " + depend); 275 | } 276 | for (String depend : mm.dependsMethods) { 277 | if (!methodMappings.keySet().contains(depend)) System.out.println(" Method: " + depend); 278 | } 279 | } 280 | return false; 281 | } 282 | } 283 | } 284 | 285 | 286 | public static Map> classDeps = new HashMap<>(); 287 | public static Map> classesExtendFrom = new HashMap<>(); 288 | public static Map> classesImplementFrom = new HashMap<>(); 289 | 290 | static String[] classSearchExceptions = new String[] { "java/", "javax/", "sun/", "com/google/", 291 | "org/apache/", "com/sun/", "io/netty/", "jdk/internal/", "org/xml/", 292 | "org/w3c/", "jdk/net/", "com/ibm/", "org/lwjgl/", "com/jcraft/", 293 | "joptsimple/", "net/java/", "paulscode/", "com/mojang/"}; 294 | 295 | public static void getConstantPoolClassesRecursive(String className) 296 | { 297 | if (startsWithAny(className, classSearchExceptions)) return; 298 | 299 | ClassReader reader = null; 300 | try { 301 | reader = new ClassReader(className); 302 | } catch (IOException e) {} 303 | if (reader != null) { 304 | String superName = reader.getSuperName(); 305 | Set extendSet = classesExtendFrom.get(superName); 306 | if (extendSet == null) { extendSet = new HashSet<>(); classesExtendFrom.put(superName, extendSet); } 307 | extendSet.add(className); 308 | 309 | for (String iface : reader.getInterfaces()) { 310 | Set implementSet = classesImplementFrom.get(iface); 311 | if (implementSet == null) { implementSet = new HashSet<>(); classesImplementFrom.put(iface, implementSet); } 312 | implementSet.add(className); 313 | } 314 | } 315 | 316 | Set classes = classDeps.get(className); 317 | if (classes == null) { 318 | classes = getConstantPoolClasses(className, true); 319 | if (classes == null) return; 320 | classDeps.put(className, classes); 321 | } 322 | //System.out.println(className + " " + (classes != null)); 323 | for (String s : classes) { 324 | if (!classDeps.containsKey(s)) getConstantPoolClassesRecursive(s); 325 | } 326 | } 327 | 328 | 329 | public static boolean startsWithAny(String string, String[] list) 330 | { 331 | for (String s : list) { 332 | if (string.startsWith(s)) return true; 333 | } 334 | return false; 335 | } 336 | 337 | 338 | public static Set getChildClasses(String className) 339 | { 340 | Set outSet = new HashSet<>(); 341 | Set tempSet = new HashSet<>(); 342 | if (classesExtendFrom.containsKey(className)) tempSet.addAll(classesExtendFrom.get(className)); 343 | if (classesImplementFrom.containsKey(className)) tempSet.addAll(classesImplementFrom.get(className)); 344 | outSet.addAll(tempSet); 345 | 346 | for (String s : tempSet) { 347 | outSet.addAll(getChildClasses(s)); 348 | } 349 | return outSet; 350 | } 351 | 352 | 353 | public static void generateClassLinkages() 354 | { 355 | System.out.print("[DynamicMappings] Generating linkages..."); 356 | getConstantPoolClassesRecursive("net/minecraft/server/MinecraftServer"); 357 | getConstantPoolClassesRecursive("net/minecraft/client/main/Main"); 358 | System.out.println("done"); 359 | } 360 | 361 | 362 | 363 | public static void log(boolean toConsole, PrintWriter writer, String text) 364 | { 365 | if (toConsole) System.out.println(text); 366 | if (writer != null) writer.println(text); 367 | } 368 | 369 | 370 | /** Used for debugging purposes, and to print out a full list 371 | * @throws FileNotFoundException */ 372 | public static void main(String[] args) 373 | { 374 | ParmParser pp = new ParmParser(); 375 | Parm clearMappersParm = pp.addParm("-clearmappers", 0); 376 | Parm addMapperParm = pp.addParm("-addmappers", 1); 377 | pp.processArgs(args); 378 | 379 | if (clearMappersParm.found) DynamicMappings.MAPPINGS_CLASSES.clear(); 380 | if (addMapperParm.found) { 381 | String split[] = addMapperParm.getFirstResult().split(":;,"); 382 | for (String mapper : split) DynamicMappings.MAPPINGS_CLASSES.add(mapper); 383 | } 384 | 385 | // If true, prints out the mappings 386 | boolean showMappings = false; 387 | // If true, saves mappings to currentmappings.txt 388 | boolean saveMappings = true; 389 | 390 | PrintWriter writer = null; 391 | 392 | try { 393 | if (saveMappings) writer = new PrintWriter("currentmappings.txt"); 394 | } catch (FileNotFoundException e) { 395 | e.printStackTrace(); 396 | } 397 | 398 | generateClassMappings(); 399 | 400 | log(true, writer, "[DynamicMappings] Minecraft version: " + MeddleUtil.findMinecraftVersion()); 401 | log(true, writer, "[DynamicMappings] Minecraft jar type: " + (MeddleUtil.isClientJar() ? "client" : "server")); 402 | 403 | if (!showMappings && !saveMappings) return; 404 | 405 | log(showMappings, writer, "\nCLASSES:"); 406 | 407 | List sorted = new ArrayList(); 408 | sorted.addAll(classMappings.keySet()); 409 | Collections.sort(sorted); 410 | for (String s : sorted) { 411 | log(showMappings, writer, s + " -> " + classMappings.get(s)); 412 | } 413 | 414 | log(showMappings, writer, "\nFIELDS:"); 415 | 416 | sorted.clear(); 417 | sorted.addAll(fieldMappings.keySet()); 418 | Collections.sort(sorted); 419 | for (String s : sorted) { 420 | log(showMappings, writer, s + " -> " + fieldMappings.get(s)); 421 | } 422 | 423 | log(showMappings, writer, "\nMETHODS:"); 424 | 425 | sorted.clear(); 426 | sorted.addAll(methodMappings.keySet()); 427 | Collections.sort(sorted); 428 | for (String s : sorted) { 429 | log(showMappings, writer, s + " -> " + methodMappings.get(s)); 430 | } 431 | 432 | writer.close(); 433 | } 434 | 435 | 436 | 437 | /** 438 | * Load a ClassNode by its name. This is for loading the original obfuscated 439 | * classes. 440 | * 441 | * Note: *Do not* edit classes you get from this. They're cached and used by 442 | * anyone doing analysis of vanilla class files. 443 | * 444 | * @param className - The normal (probably obfuscated) name of the class to load. 445 | * @return The ClassNode requested, or null if it doesn't exist. 446 | */ 447 | public static ClassNode getClassNode(String className) 448 | { 449 | if (className == null) return null; 450 | 451 | className = className.replace(".", "/"); 452 | if (cachedClassNodes.containsKey(className)) return cachedClassNodes.get(className); 453 | 454 | //InputStream stream = Launch.classLoader.getResourceAsStream(className + ".class"); 455 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 456 | if (stream == null) return null; 457 | 458 | ClassReader reader = null; 459 | try { 460 | reader = new ClassReader(stream); 461 | } catch (IOException e) { return null; } 462 | 463 | ClassNode cn = new ClassNode(); 464 | reader.accept(cn, 0); 465 | 466 | cachedClassNodes.put(className, cn); 467 | 468 | return cn; 469 | } 470 | 471 | 472 | /** 473 | * Get constant pool string that an LDC instruction is 474 | * loading. 475 | * 476 | * @param node The instruction node to check. 477 | * @return Returns the string if the LDC is accessing a String 478 | * constant pool item, else null. 479 | */ 480 | public static String getLdcString(AbstractInsnNode node) 481 | { 482 | if (!(node instanceof LdcInsnNode)) return null; 483 | LdcInsnNode ldc = (LdcInsnNode)node; 484 | if (!(ldc.cst instanceof String)) return null; 485 | return new String((String)ldc.cst); 486 | } 487 | 488 | 489 | /** 490 | * Get constant pool class that an LDC instruction is 491 | * referencing. 492 | * 493 | * @param node - The instruction node to check. 494 | * @return Returns the class name if the LDC is accessing a 495 | * Class constant pool item, else null. 496 | */ 497 | public static String getLdcClass(AbstractInsnNode node) 498 | { 499 | if (!(node instanceof LdcInsnNode)) return null; 500 | LdcInsnNode ldc = (LdcInsnNode)node; 501 | if (!(ldc.cst instanceof Type)) return null; 502 | return ((Type)ldc.cst).getClassName(); 503 | } 504 | 505 | 506 | /** 507 | * Get constant pool integer that an LDC instruction is 508 | * referencing. 509 | * 510 | * @param node - The instruction node to check. 511 | * @return Returns the class name if the LDC is accessing a 512 | * Class constant pool item, else null. 513 | */ 514 | public static Integer getLdcInteger(AbstractInsnNode node) 515 | { 516 | if (!(node instanceof LdcInsnNode)) return null; 517 | LdcInsnNode ldc = (LdcInsnNode)node; 518 | if (!(ldc.cst instanceof Integer)) return null; 519 | return (Integer)ldc.cst; 520 | } 521 | 522 | 523 | /** 524 | * Get constant pool float that an LDC instruction is 525 | * referencing. 526 | * 527 | * @param node - The instruction node to check. 528 | * @return Returns the class name if the LDC is accessing a 529 | * Class constant pool item, else null. 530 | */ 531 | public static Float getLdcFloat(AbstractInsnNode node) 532 | { 533 | if (!(node instanceof LdcInsnNode)) return null; 534 | LdcInsnNode ldc = (LdcInsnNode)node; 535 | if (!(ldc.cst instanceof Float)) return null; 536 | return (Float)ldc.cst; 537 | } 538 | 539 | 540 | /** 541 | * Check if an LDC instruction is loading the specified string. 542 | * 543 | * @param node The instruction node to check 544 | * @param string The string to compare to. 545 | * @return Returns true if the instruction is an LDC, is accessing 546 | * a String constant pool item, and the string matches the input string. 547 | */ 548 | public static boolean isLdcWithString(AbstractInsnNode node, String string) 549 | { 550 | String s = getLdcString(node); 551 | return (s != null && string.equals(s)); 552 | } 553 | 554 | 555 | /** 556 | * Check if an LDC instruction is loading specified int value. 557 | * 558 | * @param node The instruction node to check. 559 | * @param val The integer to compare to. 560 | * @return Returns true if the instruction is an LDC, is accessing 561 | * a Integer constant pool item, and its value matches the input value. 562 | */ 563 | public static boolean isLdcWithInteger(AbstractInsnNode node, int val) 564 | { 565 | if (!(node instanceof LdcInsnNode)) return false; 566 | LdcInsnNode ldc = (LdcInsnNode)node; 567 | if (!(ldc.cst instanceof Integer)) return false; 568 | return ((Integer)ldc.cst) == val; 569 | } 570 | 571 | 572 | /** 573 | * Check if an LDC instruction is loading specified float value. 574 | * 575 | * @param node The instruction node to check. 576 | * @param val The integer to compare to. 577 | * @return Returns true if the instruction is an LDC, is accessing 578 | * a Float constant pool item, and its value matches the input value. 579 | */ 580 | public static boolean isLdcWithFloat(AbstractInsnNode node, float val) 581 | { 582 | if (!(node instanceof LdcInsnNode)) return false; 583 | LdcInsnNode ldc = (LdcInsnNode)node; 584 | if (!(ldc.cst instanceof Float)) return false; 585 | return ((Float)ldc.cst) == val; 586 | } 587 | 588 | 589 | /** 590 | * Get the description of the specified field from a class. 591 | * 592 | * @param cn - The class to search. 593 | * @param fieldName - The name of the field. 594 | * @return The description of the field, or null if not found. 595 | */ 596 | public static String getFieldDesc(ClassNode cn, String fieldName) 597 | { 598 | for (FieldNode field : cn.fields) 599 | { 600 | if (field.name.equals(fieldName)) return field.desc; 601 | } 602 | return null; 603 | } 604 | 605 | 606 | /** 607 | * Get the specified field node from the class. 608 | * 609 | * @param cn - The class to search 610 | * @param fieldName - The name of the field to find. 611 | * @return The FieldNode if present, else null. 612 | */ 613 | public static FieldNode getFieldByName(ClassNode cn, String fieldName) 614 | { 615 | for (FieldNode field : cn.fields) 616 | { 617 | if (field.name.equals(fieldName)) return field; 618 | } 619 | return null; 620 | } 621 | 622 | 623 | /** 624 | * Searches a class's constant pool for the specified list of strings. 625 | * 626 | * NOTE: Constant pool strings are trimmed of whitespace! Take into 627 | * account when matching. 628 | * 629 | * @param className - The name of the class to search. 630 | * @param matchStrings - The list of strings to find. 631 | * @return True if all strings were found. 632 | */ 633 | public static boolean searchConstantPoolForStrings(String className, String... matchStrings) 634 | { 635 | if (className == null) return false; 636 | className = className.replace(".", "/"); 637 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 638 | if (stream == null) return false; 639 | 640 | ClassReader reader = null; 641 | try { 642 | reader = new ClassReader(stream); 643 | } catch (IOException e) { return false; } 644 | 645 | int itemCount = reader.getItemCount(); 646 | char[] buffer = new char[reader.getMaxStringLength()]; 647 | 648 | int matches = 0; 649 | 650 | for (int n = 1; n < itemCount; n++) { 651 | int pos = reader.getItem(n); 652 | if (pos == 0 || reader.b[pos - 1] != 8) continue; 653 | 654 | Arrays.fill(buffer, (char)0); 655 | String string = reader.readUTF8(pos, buffer).trim(); 656 | //String string = (new String(buffer)).trim(); 657 | 658 | for (int n2 = 0; n2 < matchStrings.length; n2++) { 659 | if (string.equals(matchStrings[n2].trim())) { matches++; break; } 660 | } 661 | } 662 | 663 | return (matches == matchStrings.length); 664 | } 665 | 666 | 667 | /** 668 | * Searches a class's constant pool for the specified list of class 669 | * references. 670 | * 671 | * @param className - The name of the class to search. 672 | * @param matchStrings - The list of class names to find. 673 | * @return True if all class names were found. 674 | */ 675 | public static boolean searchConstantPoolForClasses(String className, String... matchStrings) 676 | { 677 | className = className.replace(".", "/"); 678 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 679 | if (stream == null) return false; 680 | 681 | ClassReader reader = null; 682 | try { 683 | reader = new ClassReader(stream); 684 | } catch (IOException e) { return false; } 685 | 686 | int itemCount = reader.getItemCount(); 687 | char[] buffer = new char[reader.getMaxStringLength()]; 688 | 689 | int matches = 0; 690 | 691 | for (int n = 1; n < itemCount; n++) { 692 | int pos = reader.getItem(n); 693 | if (pos == 0 || reader.b[pos - 1] != 7) continue; 694 | 695 | Arrays.fill(buffer, (char)0); 696 | String string = reader.readUTF8(pos, buffer); 697 | //String string = (new String(buffer)).trim(); 698 | 699 | for (int n2 = 0; n2 < matchStrings.length; n2++) { 700 | if (string.equals(matchStrings[n2].replace(".", "/"))) { matches++; break; } 701 | } 702 | } 703 | 704 | return (matches == matchStrings.length); 705 | } 706 | 707 | 708 | /** 709 | * Returns a list of the 'String' types from the class's constant pool. 710 | * 711 | * @param className - Name of class to search. 712 | * @return The list of constant pool strings. 713 | */ 714 | public static List getConstantPoolStrings(String className) 715 | { 716 | List strings = new ArrayList(); 717 | 718 | className = className.replace(".", "/"); 719 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 720 | if (stream == null) return null; 721 | 722 | ClassReader reader = null; 723 | try { 724 | reader = new ClassReader(stream); 725 | } catch (IOException e) { return null; } 726 | 727 | int itemCount = reader.getItemCount(); 728 | char[] buffer = new char[reader.getMaxStringLength()]; 729 | 730 | for (int n = 1; n < itemCount; n++) { 731 | int pos = reader.getItem(n); 732 | if (pos == 0 || reader.b[pos - 1] != 8) continue; 733 | 734 | Arrays.fill(buffer, (char)0); 735 | String string = reader.readUTF8(pos, buffer); 736 | //String string = (new String(buffer)).trim(); 737 | 738 | strings.add(string); 739 | } 740 | 741 | return strings; 742 | } 743 | 744 | 745 | /** 746 | * Returns a set of the 'Class' types from the class's constant pool. 747 | * 748 | * @param className - Name of class to search. 749 | * @return The list of constant pool strings. 750 | */ 751 | public static Set getConstantPoolClasses(String className, boolean processArrays) 752 | { 753 | Set strings = new HashSet(); 754 | 755 | className = className.replace(".", "/"); 756 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 757 | if (stream == null) return null; 758 | 759 | ClassReader reader = null; 760 | try { 761 | reader = new ClassReader(stream); 762 | } catch (IOException e) { return null; } 763 | 764 | int itemCount = reader.getItemCount(); 765 | char[] buffer = new char[reader.getMaxStringLength()]; 766 | 767 | for (int n = 1; n < itemCount; n++) { 768 | int pos = reader.getItem(n); 769 | if (pos == 0 || reader.b[pos - 1] != 7) continue; 770 | 771 | Arrays.fill(buffer, (char)0); 772 | String string = reader.readUTF8(pos, buffer); 773 | //String string = (new String(buffer)).trim(); 774 | 775 | if (string.startsWith("[") && processArrays) { 776 | string = ModMappings.getArrayType(string); 777 | if (string == null) continue; 778 | } 779 | if (string.length() < 1) continue; 780 | 781 | strings.add(string); 782 | } 783 | 784 | return strings; 785 | } 786 | 787 | 788 | /** 789 | * Confirm the parameter types of a method's description. 790 | * 791 | * Uses org.objectweb.asm.Type for values. 792 | * 793 | * @param method - MethodNode to check. 794 | * @param types - Sequence of method parameter types. 795 | * @return True if the method description matches specified types. 796 | */ 797 | public static boolean checkMethodParameters(MethodNode method, int ... types) 798 | { 799 | Type t = Type.getMethodType(method.desc); 800 | Type[] args = t.getArgumentTypes(); 801 | if (args.length != types.length) return false; 802 | 803 | int len = args.length; 804 | for (int n = 0; n < len; n++) { 805 | if (args[n].getSort() != types[n]) return false; 806 | } 807 | 808 | return true; 809 | } 810 | 811 | 812 | /** 813 | * Finds all methods matching the specified name and/or description. 814 | * 815 | * @param cn - ClassNode to search. 816 | * @param name - Optional name of method(s) to find. 817 | * @param desc - Optional description of method(s) to find. 818 | * @return List of matching methods. 819 | */ 820 | public static List getMatchingMethods(ClassNode cn, String name, String desc) 821 | { 822 | List output = new ArrayList(); 823 | 824 | for (MethodNode method : cn.methods) { 825 | if ((name == null || (name != null && method.name.equals(name))) && 826 | (desc == null || (desc != null && method.desc.equals(desc)))) output.add(method); 827 | } 828 | 829 | return output; 830 | } 831 | 832 | 833 | /** 834 | * Finds all methods matching the specified return and argument types 835 | * 836 | * @param cn - ClassNode to search. 837 | * @param accessCode - The Opcode for the access of the method. 838 | * @param returnType - The return type as integer 839 | * @param parameterTypes - The arguments of the method as integer. Can be null if the method haven't arguments 840 | * @return List of matching methods. 841 | * @author canitzp 842 | */ 843 | public static List getMatchingMethods(ClassNode cn, int accessCode, int returnType, int... parameterTypes){ 844 | List methodNodes = new ArrayList<>(); 845 | 846 | for(MethodNode method : cn.methods){ 847 | Type rt = Type.getReturnType(method.desc); 848 | Type[] params = Type.getArgumentTypes(method.desc); 849 | if(returnType == rt.getSort() && (method.access & accessCode) != 0){ 850 | if(parameterTypes != null){ 851 | if(parameterTypes.length == params.length){ 852 | boolean isSame = true; 853 | for(int i = 0; i < parameterTypes.length; i++){ 854 | if(parameterTypes[i] != params[i].getSort()){ 855 | isSame = false; 856 | break; 857 | } 858 | } 859 | if(isSame){ 860 | methodNodes.add(method); 861 | } 862 | } 863 | } else if(params.length == 0){ 864 | methodNodes.add(method); 865 | } 866 | } 867 | } 868 | 869 | return methodNodes; 870 | } 871 | 872 | /** 873 | * Finds all fields matching the specified name and/or description. 874 | * 875 | * @param cn - ClassNode to search. 876 | * @param name - Optional name of field(s) to find. 877 | * @param desc - Optional description of field(s) to find. 878 | * @return List of matching fields. 879 | */ 880 | public static List getMatchingFields(ClassNode cn, String name, String desc) 881 | { 882 | List output = new ArrayList(); 883 | 884 | for (FieldNode field : cn.fields) { 885 | if ((name == null || (name != null && field.name.equals(name))) && 886 | (desc == null || (desc != null && field.desc.equals(desc)))) output.add(field); 887 | } 888 | 889 | return output; 890 | } 891 | 892 | 893 | /** 894 | * Gets a ClassNode after translating the specified deobfuscated class name to 895 | * the obfuscated name. 896 | * 897 | * @param deobfClass - Class name to translate and get. 898 | * @return The ClassNode, or null if it doesn't exist. 899 | */ 900 | public static ClassNode getClassNodeFromMapping(String deobfClass) 901 | { 902 | return getClassNode(getClassMapping(deobfClass)); 903 | } 904 | 905 | 906 | /** 907 | * Checks if the sequence of opcodes exists, starting at the specified 908 | * instruction. This ignores synthetic opcodes added by ASM, such as 909 | * labels. 910 | * 911 | * @param insn - Instruction node to start at. 912 | * @param opcodes - Sequence of opcodes to look for. 913 | * @return True if all opcodes are found and in the specifed order. 914 | */ 915 | public static boolean matchOpcodeSequence(AbstractInsnNode insn, int...opcodes) 916 | { 917 | for (int opcode : opcodes) { 918 | insn = getNextRealOpcode(insn); 919 | if (insn == null) return false; 920 | if (opcode != insn.getOpcode()) return false; 921 | insn = insn.getNext(); 922 | } 923 | 924 | return true; 925 | } 926 | 927 | 928 | /** 929 | * Identifies and extracts the specified opcode sequence as an array of 930 | * instruction nodes. Ignores synthetic opcodes added by ASM, such as 931 | * labels. 932 | * 933 | * @param insn - Instruction to start at. 934 | * @param opcodes - Sequence of opcodes to look for. 935 | * @return The list of instruction nodes, if one is found matching the 936 | * specified sequence. 937 | */ 938 | public static AbstractInsnNode[] getOpcodeSequenceArray(AbstractInsnNode insn, int...opcodes) 939 | { 940 | AbstractInsnNode[] outNodes = new AbstractInsnNode[opcodes.length]; 941 | int pos = 0; 942 | 943 | for (int opcode : opcodes) { 944 | insn = getNextRealOpcode(insn); 945 | if (insn == null) return null; 946 | if (opcode != insn.getOpcode()) return null; 947 | outNodes[pos++] = insn; 948 | insn = insn.getNext(); 949 | } 950 | 951 | return outNodes; 952 | } 953 | 954 | 955 | @SuppressWarnings("unchecked") 956 | public static boolean matchInsnNodeSequence(AbstractInsnNode insn, Class...nodeClasses) 957 | { 958 | for (Class nodeClass : nodeClasses) { 959 | insn = getNextRealOpcode(insn); 960 | if (insn == null) return false; 961 | if (nodeClass != insn.getClass()) return false; 962 | insn = insn.getNext(); 963 | } 964 | 965 | return true; 966 | } 967 | 968 | 969 | @SuppressWarnings("unchecked") 970 | public static AbstractInsnNode[] getInsnNodeSequenceArray(AbstractInsnNode insn, Class...nodeClasses) 971 | { 972 | AbstractInsnNode[] outNodes = new AbstractInsnNode[nodeClasses.length]; 973 | int pos = 0; 974 | 975 | for (Class nodeClass : nodeClasses) { 976 | insn = getNextRealOpcode(insn); 977 | if (insn == null) return null; 978 | if (nodeClass != insn.getClass()) return null; 979 | outNodes[pos++] = insn; 980 | insn = insn.getNext(); 981 | } 982 | 983 | return outNodes; 984 | } 985 | 986 | 987 | 988 | 989 | 990 | /** 991 | * Gets a list of all instruction nodes matching the specified class. 992 | * 993 | * @param startInsn - Instruction node to start at. 994 | * @param classType - Class to compare against. 995 | * @return The list of matching nodes. 996 | */ 997 | @SuppressWarnings("unchecked") 998 | public static List getAllInsnNodesOfType(AbstractInsnNode startInsn, Class classType) 999 | { 1000 | List list = new ArrayList<>(); 1001 | 1002 | for (AbstractInsnNode insn = startInsn; insn != null; insn = insn.getNext()) { 1003 | if (insn.getClass() == classType) list.add((T)insn); 1004 | } 1005 | 1006 | return list; 1007 | } 1008 | 1009 | 1010 | /** 1011 | * Gets the next matching instruction node matching the specified class. 1012 | * 1013 | * @param startInsn - Instruction node to start at. 1014 | * @param classType - Class to compare against. 1015 | * @return The first matching instruction node, or null if none. 1016 | */ 1017 | @SuppressWarnings("unchecked") 1018 | public static T getNextInsnNodeOfType(AbstractInsnNode startInsn, Class classType) 1019 | { 1020 | for (AbstractInsnNode insn = startInsn; insn != null; insn = insn.getNext()) { 1021 | if (insn.getClass() == classType) return (T) insn; 1022 | } 1023 | 1024 | return null; 1025 | } 1026 | 1027 | 1028 | 1029 | /** 1030 | * Gets the obfuscated class name from a deobfuscated input. 1031 | * 1032 | * @param deobfClassName - The deobfuscated class name. 1033 | * @return The obfuscated class name, or null 1034 | */ 1035 | public static String getClassMapping(String deobfClassName) 1036 | { 1037 | return classMappings.get(deobfClassName.replace(".", "/")); 1038 | } 1039 | 1040 | 1041 | /** 1042 | * Gets the deobfuscated class name from an obfuscated input. 1043 | * 1044 | * @param obfClassName - The obfuscated class name. 1045 | * @return The deobfuscated class name, or null 1046 | */ 1047 | public static String getReverseClassMapping(String obfClassName) 1048 | { 1049 | return reverseClassMappings.get(obfClassName.replace(".", "/")); 1050 | } 1051 | 1052 | 1053 | /** 1054 | * Adds a class mapping. 1055 | * 1056 | * @param deobfClassName - The deobfuscated class name. 1057 | * @param node - The obfuscated ClassNode. 1058 | */ 1059 | public static void addClassMapping(String deobfClassName, ClassNode node) 1060 | { 1061 | if (deobfClassName == null) return; 1062 | deobfClassName = deobfClassName.replace(".", "/"); 1063 | addClassMapping(deobfClassName, node.name); 1064 | } 1065 | 1066 | 1067 | /** 1068 | * Adds a class mapping. 1069 | * 1070 | * @param deobfClassName - The deobfuscated class name. 1071 | * @param obfClassName - The obfuscated class name. 1072 | */ 1073 | public static void addClassMapping(String deobfClassName, String obfClassName) 1074 | { 1075 | deobfClassName = deobfClassName.replace(".", "/"); 1076 | obfClassName = obfClassName.replace(".", "/"); 1077 | 1078 | if (classMappings.containsKey(deobfClassName) && !classMappings.get(deobfClassName).equals(obfClassName)) 1079 | System.out.println("WARNING: " + deobfClassName + " has been remapped from " + classMappings.get(deobfClassName) + " to " + obfClassName); 1080 | if (reverseClassMappings.containsKey(obfClassName) && !reverseClassMappings.get(obfClassName).equals(deobfClassName)) 1081 | System.out.println("WARNING: " + obfClassName + " has been remapped from " + reverseClassMappings.get(obfClassName) + " to " + deobfClassName); 1082 | 1083 | classMappings.put(deobfClassName, obfClassName); 1084 | reverseClassMappings.put(obfClassName, deobfClassName); 1085 | } 1086 | 1087 | 1088 | // Both inputs in the format of "class_name method_name method_desc" 1089 | public static void addMethodMapping(String deobfMethodDesc, String obfMethodDesc) 1090 | { 1091 | if (classMappings.containsKey(deobfMethodDesc) && !classMappings.get(deobfMethodDesc).equals(obfMethodDesc)) 1092 | System.out.println("WARNING: " + deobfMethodDesc + " has been remapped from " + classMappings.get(deobfMethodDesc) + " to " + obfMethodDesc); 1093 | if (reverseClassMappings.containsKey(obfMethodDesc) && !reverseClassMappings.get(obfMethodDesc).equals(deobfMethodDesc)) 1094 | System.out.println("WARNING: " + obfMethodDesc + " has been remapped from " + reverseClassMappings.get(obfMethodDesc) + " to " + deobfMethodDesc); 1095 | 1096 | methodMappings.put(deobfMethodDesc, obfMethodDesc); 1097 | reverseMethodMappings.put(obfMethodDesc, deobfMethodDesc); 1098 | } 1099 | 1100 | 1101 | // Both inputs in the format of "class_name field_name field_desc" 1102 | public static void addFieldMapping(String deobfFieldDesc, String obfFieldDesc) 1103 | { 1104 | if (classMappings.containsKey(deobfFieldDesc) && !classMappings.get(deobfFieldDesc).equals(obfFieldDesc)) 1105 | System.out.println("WARNING: " + deobfFieldDesc + " has been remapped from " + classMappings.get(deobfFieldDesc) + " to " + obfFieldDesc); 1106 | if (reverseClassMappings.containsKey(obfFieldDesc) && !reverseClassMappings.get(obfFieldDesc).equals(deobfFieldDesc)) 1107 | System.out.println("WARNING: " + obfFieldDesc + " has been remapped from " + reverseClassMappings.get(obfFieldDesc) + " to " + deobfFieldDesc); 1108 | 1109 | fieldMappings.put(deobfFieldDesc, obfFieldDesc); 1110 | reverseFieldMappings.put(obfFieldDesc, deobfFieldDesc); 1111 | } 1112 | 1113 | 1114 | public static String getMethodMapping(String className, String methodName, String methodDesc) 1115 | { 1116 | return methodMappings.get(className + " " + methodName + " " + methodDesc); 1117 | } 1118 | 1119 | 1120 | public static String getMethodMapping(String mapping) 1121 | { 1122 | return methodMappings.get(mapping); 1123 | } 1124 | 1125 | public static String getReverseMethodMapping(String obfMethod) 1126 | { 1127 | return reverseMethodMappings.get(obfMethod); 1128 | } 1129 | 1130 | 1131 | public static String getFieldMapping(String mapping) 1132 | { 1133 | return fieldMappings.get(mapping); 1134 | } 1135 | 1136 | 1137 | public static String getReverseFieldMapping(String obfField) 1138 | { 1139 | return reverseFieldMappings.get(obfField); 1140 | } 1141 | 1142 | 1143 | public static FieldNode getFieldNode(ClassNode cn, String obfMapping) 1144 | { 1145 | if (cn == null || obfMapping == null) return null; 1146 | 1147 | String[] split = obfMapping.split(" "); 1148 | if (split.length < 3) return null; 1149 | 1150 | for (FieldNode field : cn.fields) { 1151 | if (field.name.equals(split[1]) && field.desc.equals(split[2])) { 1152 | return field; 1153 | } 1154 | } 1155 | 1156 | return null; 1157 | } 1158 | 1159 | 1160 | public static FieldNode getFieldNodeFromMapping(ClassNode cn, String deobfMapping) 1161 | { 1162 | String mapping = getFieldMapping(deobfMapping); 1163 | if (cn == null || mapping == null) return null; 1164 | 1165 | String[] split = mapping.split(" "); 1166 | if (split.length < 3) return null; 1167 | 1168 | for (FieldNode field : cn.fields) { 1169 | if (field.name.equals(split[1]) && field.desc.equals(split[2])) { 1170 | return field; 1171 | } 1172 | } 1173 | 1174 | return null; 1175 | } 1176 | 1177 | 1178 | // Returns just the obfuscated name of a method matching the deobfuscated input 1179 | public static String getMethodMappingName(String className, String methodName, String methodDesc) 1180 | { 1181 | String mapping = getMethodMapping(className, methodName, methodDesc); 1182 | if (mapping == null) return null; 1183 | String [] split = mapping.split(" "); 1184 | return split.length >= 3 ? split[1] : null; 1185 | } 1186 | 1187 | 1188 | public static MethodNode getMethodNode(ClassNode cn, String obfMapping) 1189 | { 1190 | if (cn == null || obfMapping == null) return null; 1191 | 1192 | String[] split = obfMapping.split(" "); 1193 | if (split.length < 3) return null; 1194 | 1195 | for (MethodNode method : cn.methods) { 1196 | if (method.name.equals(split[1]) && method.desc.equals(split[2])) { 1197 | return method; 1198 | } 1199 | } 1200 | 1201 | return null; 1202 | } 1203 | 1204 | 1205 | public static MethodNode getMethodNodeFromMapping(ClassNode cn, String deobfMapping) 1206 | { 1207 | String mapping = getMethodMapping(deobfMapping); 1208 | if (cn == null || mapping == null) return null; 1209 | 1210 | String[] split = mapping.split(" "); 1211 | if (split.length < 3) return null; 1212 | 1213 | for (MethodNode method : cn.methods) { 1214 | if (method.name.equals(split[1]) && method.desc.equals(split[2])) { 1215 | return method; 1216 | } 1217 | } 1218 | 1219 | return null; 1220 | } 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | public static JarFile getMinecraftJar() 1228 | { 1229 | // For modern versions 1230 | URL url = MeddleUtil.class.getClassLoader().getResource("net/minecraft/server/MinecraftServer.class"); 1231 | // For older versions 1232 | if (url == null) url = MeddleUtil.class.getClassLoader().getResource("net/minecraft/client/Minecraft.class"); 1233 | if (url == null) return null; 1234 | 1235 | JarFile jar = null; 1236 | 1237 | if ("jar".equals(url.getProtocol())) { 1238 | JarURLConnection connection = null; 1239 | try { 1240 | connection = (JarURLConnection) url.openConnection(); 1241 | jar = connection.getJarFile(); 1242 | } catch (IOException e) {} 1243 | } 1244 | 1245 | return jar; 1246 | } 1247 | 1248 | 1249 | 1250 | public static boolean isSubclassOf(String className, String superClassName) 1251 | { 1252 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 1253 | if (stream == null) return false; 1254 | ClassReader reader = null; 1255 | try { 1256 | reader = new ClassReader(stream); 1257 | } catch (IOException e) {} 1258 | if (reader == null) return false; 1259 | 1260 | String superName = reader.getSuperName(); 1261 | if (superName.equals(superClassName)) return true; 1262 | if (superName.equals("java/lang/Object")) return false; 1263 | return isSubclassOf(superName, superClassName); 1264 | } 1265 | 1266 | 1267 | // Use untyped list in case someone compiles without debug version of ASM. 1268 | @SuppressWarnings({ "unchecked", "rawtypes" }) 1269 | public static List getMethodsWithDescriptor(List methods, String desc) 1270 | { 1271 | List list = new ArrayList(); 1272 | for (MethodNode method : (List)methods) { 1273 | if (method.desc.equals(desc)) list.add(method); 1274 | } 1275 | return list; 1276 | } 1277 | 1278 | 1279 | public static List removeMethodsWithFlags(List methods, int accFlags) 1280 | { 1281 | List outList = new ArrayList(); 1282 | for (MethodNode mn : methods) { 1283 | if ((mn.access & accFlags) == 0) outList.add(mn); 1284 | } 1285 | return outList; 1286 | } 1287 | 1288 | 1289 | public static List removeMethodsWithoutFlags(List methods, int accFlags) 1290 | { 1291 | List outList = new ArrayList(); 1292 | for (MethodNode mn : methods) { 1293 | if ((mn.access & accFlags) != 0) outList.add(mn); 1294 | } 1295 | return outList; 1296 | } 1297 | 1298 | 1299 | public static AbstractInsnNode findNextOpcodeNum(AbstractInsnNode insn, int opcode) 1300 | { 1301 | while (insn != null) { 1302 | if (insn.getOpcode() == opcode) break; 1303 | insn = insn.getNext(); 1304 | } 1305 | return insn; 1306 | } 1307 | 1308 | 1309 | public static AbstractInsnNode getNextRealOpcode(AbstractInsnNode insn) 1310 | { 1311 | while (insn != null && insn.getOpcode() < 0) insn = insn.getNext(); 1312 | return insn; 1313 | } 1314 | 1315 | 1316 | public static String assembleDescriptor(Object... objects) 1317 | { 1318 | String output = ""; 1319 | 1320 | for (Object o : objects) { 1321 | if (o instanceof String) output += (String)o; 1322 | else if (o instanceof ClassNode) output += "L" + ((ClassNode)o).name + ";"; 1323 | } 1324 | 1325 | return output; 1326 | } 1327 | 1328 | 1329 | public static boolean classHasInterfaces(ClassNode classNode, String... ifaces) 1330 | { 1331 | boolean implementsAll = true; 1332 | List implemented = classNode.interfaces; 1333 | 1334 | for (String iface : ifaces) { 1335 | if (!implemented.contains(iface)) { implementsAll = false; break; } 1336 | } 1337 | 1338 | return implementsAll; 1339 | } 1340 | 1341 | 1342 | public static boolean doesInheritFrom(String className, String inheritFrom) 1343 | { 1344 | if (className.equals(inheritFrom)) return true; 1345 | 1346 | ClassNode cn = getClassNode(className); 1347 | if (cn == null) return false; 1348 | 1349 | List classes = new ArrayList<>(); 1350 | if (cn.superName != null) classes.add(cn.superName); 1351 | classes.addAll(cn.interfaces); 1352 | 1353 | // First pass 1354 | for (String c : classes) { 1355 | if (c.equals(inheritFrom)) return true; 1356 | } 1357 | 1358 | // Deeper pass 1359 | for (String c : classes) { 1360 | if (doesInheritFrom(c, inheritFrom)) return true; 1361 | } 1362 | 1363 | return false; 1364 | } 1365 | 1366 | 1367 | public static List getStringsFromMethod(MethodNode method) 1368 | { 1369 | List list = new ArrayList<>(); 1370 | 1371 | for (AbstractInsnNode node : method.instructions.toArray()) { 1372 | String s = getLdcString(node); 1373 | if (s != null) list.add(s); 1374 | } 1375 | 1376 | return list; 1377 | } 1378 | 1379 | 1380 | public static boolean doesMethodContainString(MethodNode method, String string) 1381 | { 1382 | return getStringsFromMethod(method).contains(string); 1383 | } 1384 | 1385 | 1386 | public static List getIntegersFromMethod(MethodNode method) 1387 | { 1388 | List list = new ArrayList<>(); 1389 | 1390 | for (AbstractInsnNode node : method.instructions.toArray()) { 1391 | Integer i = getLdcInteger(node); 1392 | if (i != null) list.add(i); 1393 | } 1394 | 1395 | return list; 1396 | } 1397 | 1398 | 1399 | public static List getFloatsFromMethod(MethodNode method) 1400 | { 1401 | List list = new ArrayList<>(); 1402 | 1403 | for (AbstractInsnNode node : method.instructions.toArray()) { 1404 | Float f = getLdcFloat(node); 1405 | if (f != null) list.add(f); 1406 | } 1407 | 1408 | return list; 1409 | } 1410 | 1411 | 1412 | public static boolean doesMethodContainInteger(MethodNode method, int i) 1413 | { 1414 | return getIntegersFromMethod(method).contains(i); 1415 | } 1416 | 1417 | 1418 | public static List getMethodsContainingString(ClassNode cn, String string) 1419 | { 1420 | List list = new ArrayList<>(); 1421 | 1422 | for (MethodNode method : cn.methods) { 1423 | if (doesMethodContainString(method, string)) list.add(method); 1424 | } 1425 | 1426 | return list; 1427 | } 1428 | 1429 | 1430 | public static boolean doesMethodUseField(MethodNode method, String owner, String name, String desc) 1431 | { 1432 | List nodes = getAllInsnNodesOfType(method.instructions.getFirst(), FieldInsnNode.class); 1433 | 1434 | for (FieldInsnNode fn : nodes) { 1435 | if (fn.owner.equals(owner) && fn.name.equals(name) && fn.desc.equals(desc)) return true; 1436 | } 1437 | 1438 | return false; 1439 | } 1440 | 1441 | 1442 | public static boolean doesMethodUseMethod(MethodNode method, String owner, String name, String desc) 1443 | { 1444 | List nodes = getAllInsnNodesOfType(method.instructions.getFirst(), MethodInsnNode.class); 1445 | 1446 | for (MethodInsnNode mn : nodes) { 1447 | if (mn.owner.equals(owner) && mn.name.equals(name) && mn.desc.equals(desc)) return true; 1448 | } 1449 | 1450 | return false; 1451 | } 1452 | 1453 | 1454 | public static List filterMethodsUsingField(List paramMethods, String owner, String name, String desc) 1455 | { 1456 | List methods = new ArrayList<>(); 1457 | 1458 | for (MethodNode method : paramMethods) { 1459 | if (doesMethodUseField(method, owner, name, desc)) methods.add(method); 1460 | } 1461 | 1462 | return methods; 1463 | } 1464 | 1465 | 1466 | public static List filterMethodsUsingMethod(List paramMethods, String owner, String name, String desc) 1467 | { 1468 | List methods = new ArrayList<>(); 1469 | 1470 | for (MethodNode method : paramMethods) { 1471 | if (doesMethodUseMethod(method, owner, name, desc)) methods.add(method); 1472 | } 1473 | 1474 | return methods; 1475 | } 1476 | 1477 | 1478 | public static void reset() 1479 | { 1480 | classMappings.clear(); 1481 | reverseClassMappings.clear(); 1482 | fieldMappings.clear(); 1483 | reverseFieldMappings.clear(); 1484 | methodMappings.clear(); 1485 | reverseMethodMappings.clear(); 1486 | 1487 | clientMappingsSet.clear(); 1488 | serverMappingsSet.clear(); 1489 | 1490 | cachedClassNodes.clear(); 1491 | } 1492 | 1493 | 1494 | 1495 | public static void discoverMapperConfigs() 1496 | { 1497 | boolean clearMappers = false; 1498 | List mappers = new ArrayList<>(); 1499 | 1500 | try { 1501 | Enumeration configs = Launch.classLoader.findResources("dynamicmappings.init"); 1502 | while (configs.hasMoreElements()) { 1503 | URL url = configs.nextElement(); 1504 | 1505 | // Catch errors separately to continue parsing files 1506 | try { 1507 | DynamicMappings.LOGGER.info("[DynamicMappings] Parsing " + url.toString()); 1508 | BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); 1509 | while (true) { 1510 | String line = reader.readLine(); 1511 | if (line == null) break; 1512 | 1513 | String[] split = line.split(" "); 1514 | String cmd = split[0].toLowerCase(); 1515 | 1516 | switch (cmd) { 1517 | case "clearallmappers": clearMappers = true; break; 1518 | case "addmapper": if (split.length > 1) mappers.add(split[1]); break; 1519 | } 1520 | 1521 | } 1522 | reader.close(); 1523 | } 1524 | catch (IOException e) {} 1525 | } 1526 | } catch (IOException e) { } 1527 | 1528 | 1529 | if (clearMappers) { 1530 | DynamicMappings.LOGGER.info("[DynamicMappings] Clearing default class mappers"); 1531 | DynamicMappings.MAPPINGS_CLASSES.clear(); 1532 | } 1533 | 1534 | if (mappers.size() > 0) { 1535 | for (String mapper : mappers) { 1536 | DynamicMappings.LOGGER.info("[DynamicMappings] Adding custom class mapper: " + mapper); 1537 | DynamicMappings.MAPPINGS_CLASSES.add(mapper); 1538 | } 1539 | } 1540 | 1541 | } 1542 | 1543 | } 1544 | 1545 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/DynamicRemap.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.net.JarURLConnection; 8 | import java.net.URL; 9 | import java.util.Enumeration; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | import java.util.jar.JarOutputStream; 15 | import java.util.zip.ZipEntry; 16 | import java.util.zip.ZipFile; 17 | 18 | import org.objectweb.asm.Attribute; 19 | import org.objectweb.asm.ClassReader; 20 | import org.objectweb.asm.ClassVisitor; 21 | import org.objectweb.asm.ClassWriter; 22 | import org.objectweb.asm.Opcodes; 23 | import org.objectweb.asm.commons.Remapper; 24 | import org.objectweb.asm.commons.RemappingClassAdapter; 25 | import org.objectweb.asm.tree.ClassNode; 26 | import org.objectweb.asm.tree.FieldNode; 27 | import org.objectweb.asm.tree.LocalVariableNode; 28 | import org.objectweb.asm.tree.MethodNode; 29 | 30 | import net.fybertech.dynamicmappings.InheritanceMap; 31 | import net.fybertech.dynamicmappings.InheritanceMap.FieldHolder; 32 | import net.fybertech.dynamicmappings.InheritanceMap.MethodHolder; 33 | import net.fybertech.dynamicmappings.ParmParser.Parm; 34 | import net.fybertech.meddle.MeddleUtil; 35 | 36 | 37 | public class DynamicRemap 38 | { 39 | 40 | Map classMappings; 41 | Map fieldMappings; 42 | Map methodMappings; 43 | 44 | public static String unpackagedPrefix = "net/minecraft/class_"; 45 | public static String unpackagedInnerPrefix = "innerclass_"; 46 | 47 | public InheritanceMap inheritanceMapper = new InheritanceMap(); 48 | 49 | 50 | 51 | public DynamicRemap(Map cm, Map fm, Map mm) 52 | { 53 | classMappings = cm; 54 | fieldMappings = fm; 55 | methodMappings = mm; 56 | } 57 | 58 | 59 | public ClassNode getClassNode(String className) 60 | { 61 | return DynamicMappings.getClassNode(className); 62 | } 63 | 64 | 65 | 66 | private boolean isObfInner(String s) 67 | { 68 | //if (s.length() > 1) return false; 69 | 70 | try { 71 | Integer.parseInt(s); 72 | return false; 73 | } 74 | catch (NumberFormatException e) 75 | { 76 | return true; 77 | } 78 | } 79 | 80 | 81 | private class MyRemapper extends Remapper 82 | { 83 | 84 | boolean showMapMethod = false; 85 | final ClassNode classNode; 86 | 87 | public MyRemapper(ClassNode cn, boolean debug) { 88 | classNode = cn; 89 | showMapMethod = debug; 90 | } 91 | 92 | 93 | @Override 94 | public String map(String typeName) 95 | { 96 | boolean originallyUnpackaged = !typeName.contains("/"); 97 | 98 | if (classMappings.containsKey(typeName)) return classMappings.get(typeName); 99 | 100 | // Remap the parent class in the case of an inner class 101 | String[] split = typeName.split("\\$"); 102 | if (classMappings.containsKey(split[0])) split[0] = classMappings.get(split[0]); 103 | typeName = split[0]; 104 | 105 | for (int n = 1; n < split.length; n++) { 106 | String inner = split[n]; 107 | if (originallyUnpackaged && isObfInner(inner) && unpackagedInnerPrefix != null) inner = unpackagedInnerPrefix + inner + n; 108 | typeName += "$" + inner; 109 | } 110 | 111 | if (!typeName.contains("/") && unpackagedPrefix != null) typeName = unpackagedPrefix + typeName; 112 | return super.map(typeName); 113 | } 114 | 115 | 116 | @Override 117 | public String mapFieldName(String owner, String name, String desc) 118 | { 119 | ClassNode cn = getClassNode(owner); 120 | if (cn == null) return super.mapFieldName(owner, name, desc); 121 | 122 | InheritanceMap map = null; 123 | try { 124 | map = inheritanceMapper.buildMap(cn); 125 | } catch (IOException e) { 126 | e.printStackTrace(); 127 | } 128 | Set fields = map.fields.get(name + " " + desc); 129 | 130 | if (fields == null) return super.mapFieldName(owner, name, desc); 131 | 132 | for (FieldHolder holder : fields) { 133 | String key = holder.cn.name + " " + holder.fn.name + " " + holder.fn.desc; 134 | if (fieldMappings.containsKey(key)) { 135 | String mapping = fieldMappings.get(key); 136 | String[] split = mapping.split(" "); 137 | return super.mapFieldName(owner, split[1], desc); 138 | } 139 | } 140 | 141 | return super.mapFieldName(owner, name, desc); 142 | } 143 | 144 | 145 | @Override 146 | public String mapMethodName(String owner, String name, String desc) 147 | { 148 | if (owner.startsWith("[") || name.startsWith("<")) return super.mapMethodName(owner, name, desc); 149 | 150 | if (showMapMethod) System.out.println("mapMethod: " + owner + " " + name + " " + desc); 151 | 152 | ClassNode cn = getClassNode(owner); 153 | if (cn == null) return super.mapMethodName(owner, name, desc); 154 | 155 | InheritanceMap map = null; 156 | try { 157 | map = inheritanceMapper.buildMap(cn); 158 | } catch (IOException e) { 159 | e.printStackTrace(); 160 | } 161 | 162 | Set methods = map.methods.get(name + " " + desc); 163 | if (methods == null) return super.mapMethodName(owner, name, desc); 164 | 165 | 166 | for (MethodHolder holder : methods) { 167 | String key = holder.cn.name + " " + holder.mn.name + " " + holder.mn.desc; 168 | if (showMapMethod) System.out.println("Key: " + key); 169 | 170 | if (methodMappings.containsKey(key)) { 171 | if (showMapMethod) System.out.println(" HAS KEY"); 172 | String mapping = methodMappings.get(key); 173 | //System.out.println(mapping); 174 | String[] split = mapping.split(" "); 175 | return super.mapMethodName(owner, split[1], desc); 176 | } 177 | } 178 | 179 | return super.mapMethodName(owner, name, desc); 180 | } 181 | } 182 | 183 | 184 | public ClassNode remapClass(String className) 185 | { 186 | if (className == null) return null; 187 | 188 | InputStream stream = getClass().getClassLoader().getResourceAsStream(className + ".class"); 189 | return remapClass(stream); 190 | } 191 | 192 | 193 | public ClassNode remapClass(InputStream stream) 194 | { 195 | if (stream == null) return null; 196 | 197 | ClassReader reader = null; 198 | try { 199 | reader = new ClassReader(stream); 200 | } catch (IOException e) { 201 | e.printStackTrace(); 202 | } 203 | 204 | return remapClass(reader); 205 | } 206 | 207 | 208 | public byte[] remapClass(byte[] basicClass) 209 | { 210 | if (basicClass == null) return null; 211 | 212 | ClassReader reader = new ClassReader(basicClass); 213 | 214 | ClassNode cn = remapClass(reader); 215 | if (cn == null) return null; 216 | 217 | ClassWriter cw = new ClassWriter(0); 218 | cn.accept(cw); 219 | return cw.toByteArray(); 220 | } 221 | 222 | 223 | private class CustomRemappingClassAdapter extends RemappingClassAdapter 224 | { 225 | public CustomRemappingClassAdapter(ClassVisitor cv, Remapper remapper) { 226 | super(cv, remapper); 227 | } 228 | 229 | @Override 230 | public void visitInnerClass(String name, String outerName, String innerName, int access) 231 | { 232 | // TODO - Might need to handle for nested inner classes 233 | if (classMappings.containsKey(name)) { 234 | String outer = classMappings.get(outerName); 235 | if (outer == null) outer = outerName; 236 | outerName = outer; 237 | 238 | name = classMappings.get(name); 239 | innerName = name.substring(name.lastIndexOf("$") + 1); 240 | //System.out.println("INNER: " + outerName + " " + innerName); 241 | 242 | super.visitInnerClass(name, outerName, innerName, access); 243 | return; 244 | } 245 | 246 | if (!name.contains("/") && innerName != null && unpackagedInnerPrefix != null && isObfInner(innerName)) 247 | { 248 | innerName = unpackagedInnerPrefix + innerName; 249 | } 250 | 251 | super.visitInnerClass(name, outerName, innerName, access); 252 | } 253 | 254 | } 255 | 256 | 257 | public ClassNode remapClass(ClassReader reader) 258 | { 259 | boolean showDebug = false; 260 | 261 | ClassNode cn = new ClassNode(); 262 | reader.accept(new CustomRemappingClassAdapter(cn, new MyRemapper(cn, showDebug)), ClassReader.EXPAND_FRAMES); 263 | 264 | // Fix obfuscation of local variable names 265 | for (MethodNode method : cn.methods) 266 | { 267 | int paramCount = 0; 268 | int varCount = 0; 269 | 270 | if (method.localVariables != null) 271 | for (LocalVariableNode lvn : method.localVariables) 272 | { 273 | if (!lvn.name.equals("\u2603")) continue; 274 | if (lvn.start == method.instructions.getFirst()) lvn.name = "param" + paramCount++; 275 | else lvn.name = "var" + varCount++; 276 | } 277 | } 278 | 279 | if (showDebug) { 280 | System.out.println(cn.name); 281 | for (MethodNode method : cn.methods) { 282 | System.out.println(" " + method.name + " " + method.desc); 283 | } 284 | } 285 | 286 | return cn; 287 | } 288 | 289 | 290 | public static byte[] getFileFromZip(ZipEntry entry, ZipFile zipFile) 291 | { 292 | byte[] buffer = null; 293 | 294 | if (entry != null) 295 | { 296 | try { 297 | InputStream stream = zipFile.getInputStream(entry); 298 | int pos = 0; 299 | buffer = new byte[(int)entry.getSize()]; 300 | while (true) 301 | { 302 | int read = stream.read(buffer, pos, Math.min(1024, (int)entry.getSize() - pos)); 303 | pos += read; 304 | if (read < 1) break; 305 | } 306 | } catch (IOException e) { 307 | e.printStackTrace(); 308 | } 309 | } 310 | 311 | return buffer; 312 | } 313 | 314 | 315 | 316 | /** 317 | * Parses the jar to find any unmapped child classes of any of the specified 318 | * classes, then gives them generic names and repackages them. 319 | * 320 | * @param mcJar - The obfuscated Minecraft jar 321 | * @param baseClasses - List of deobfuscated class names to discover children for 322 | */ 323 | public static void remapUnknownChildren(JarFile mcJar, String ... baseClasses ) 324 | { 325 | for (Enumeration enumerator = mcJar.entries(); enumerator.hasMoreElements();) 326 | { 327 | JarEntry entry = enumerator.nextElement(); 328 | String filename = entry.getName(); 329 | if (!filename.endsWith(".class")) continue; 330 | String className = filename.substring(0, filename.length() - 6); 331 | 332 | if (className.contains("$")) continue; 333 | 334 | for (String mappedClass : baseClasses) { 335 | 336 | String baseClass = DynamicMappings.getClassMapping(mappedClass); 337 | String classPrefix = mappedClass + "Unknown_"; 338 | 339 | if (baseClass != null && DynamicMappings.doesInheritFrom(className, baseClass) && !DynamicMappings.reverseClassMappings.containsKey(className)) { 340 | DynamicMappings.addClassMapping(classPrefix + className, className); 341 | break; 342 | } 343 | } 344 | } 345 | } 346 | 347 | 348 | 349 | public static void main(String[] args) 350 | { 351 | ParmParser pp = new ParmParser(); 352 | Parm outputParm = pp.addParm("-o", 1); // Output location 353 | Parm clearMappersParm = pp.addParm("-clearmappers", 0); 354 | Parm addMapperParm = pp.addParm("-addmappers", 1); 355 | pp.processArgs(args); 356 | 357 | File outputFile = new File("mcremapped.jar"); 358 | if (outputParm.found) outputFile = new File(outputParm.getFirstResult()); 359 | 360 | if (clearMappersParm.found) DynamicMappings.MAPPINGS_CLASSES.clear(); 361 | if (addMapperParm.found) { 362 | String split[] = addMapperParm.getFirstResult().split(":;,"); 363 | for (String mapper : split) DynamicMappings.MAPPINGS_CLASSES.add(mapper); 364 | } 365 | 366 | DynamicMappings.generateClassMappings(); 367 | 368 | AccessUtil accessUtil = new AccessUtil(); 369 | accessUtil.readAllTransformerConfigs(); 370 | 371 | JarFile mcJar = DynamicMappings.getMinecraftJar(); 372 | 373 | // Moves unmapped blocks, items, etc to the appropriate packages 374 | if (mcJar != null) { 375 | remapUnknownChildren(mcJar, "net/minecraft/block/Block", "net/minecraft/item/Item", "net/minecraft/entity/monster/EntityMob", 376 | "net/minecraft/entity/Entity", "net/minecraft/tileentity/TileEntity", "net/minecraft/inventory/Container", 377 | "net/minecraft/client/gui/inventory/GuiContainer", "net/minecraft/client/gui/Gui", "net/minecraft/stats/StatBase"); 378 | 379 | /*try { 380 | mcJar.close(); 381 | } catch (IOException e) {}*/ 382 | } 383 | 384 | 385 | DynamicRemap remapper = new DynamicRemap( 386 | DynamicMappings.reverseClassMappings, 387 | DynamicMappings.reverseFieldMappings, 388 | DynamicMappings.reverseMethodMappings); 389 | 390 | 391 | JarFile jar = mcJar; 392 | /*URL url = DynamicRemap.class.getClassLoader().getResource("net/minecraft/server/MinecraftServer.class"); 393 | if (url == null) { System.out.println("Couldn't locate server class!"); return; } 394 | 395 | 396 | JarFile jar = null; 397 | if ("jar".equals(url.getProtocol())) { 398 | JarURLConnection connection = null; 399 | try { 400 | connection = (JarURLConnection) url.openConnection(); 401 | jar = connection.getJarFile(); 402 | } catch (IOException e) { 403 | e.printStackTrace(); 404 | } 405 | }*/ 406 | 407 | if (jar == null) { System.out.println("Couldn't locate Minecraft jar!"); return; } 408 | 409 | 410 | JarOutputStream outJar = null; 411 | try { 412 | outJar = new JarOutputStream(new FileOutputStream(outputFile)); 413 | } catch (Exception e) { 414 | e.printStackTrace(); 415 | } 416 | 417 | for (Enumeration enumerator = jar.entries(); enumerator.hasMoreElements();) 418 | { 419 | JarEntry entry = enumerator.nextElement(); 420 | String name = entry.getName(); 421 | byte[] bytes = null; 422 | 423 | if (name.startsWith("META-INF/")) { 424 | if (name.endsWith(".RSA") || name.endsWith(".SF")) continue; 425 | } 426 | 427 | if (name.endsWith(".class")) { 428 | name = name.substring(0, name.length() - 6); 429 | ClassNode mapped = remapper.remapClass(name); 430 | 431 | // Correct the source filename 432 | if (mapped.sourceFile != null && mapped.sourceFile.equals("SourceFile")) { 433 | String sourceName = mapped.name; 434 | if (sourceName.indexOf('$') >= 0) 435 | sourceName = sourceName.substring(0, sourceName.indexOf('$')); 436 | mapped.sourceFile = sourceName + ".java"; 437 | } 438 | 439 | accessUtil.transformDeobfuscatedClass(mapped); 440 | ClassWriter writer = new ClassWriter(0); 441 | mapped.accept(writer); 442 | name = mapped.name + ".class"; 443 | bytes = writer.toByteArray(); 444 | } 445 | else bytes = getFileFromZip(entry, jar); 446 | 447 | ZipEntry ze = new ZipEntry(name); 448 | try { 449 | outJar.putNextEntry(ze); 450 | outJar.write(bytes); 451 | } catch (IOException e) { 452 | e.printStackTrace(); 453 | } 454 | } 455 | 456 | try { 457 | outJar.close(); 458 | jar.close(); 459 | } catch (IOException e) { 460 | e.printStackTrace(); 461 | } 462 | 463 | 464 | 465 | 466 | } 467 | 468 | 469 | 470 | 471 | } 472 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/InheritanceMap.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.DataInputStream; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.Enumeration; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | 15 | import org.objectweb.asm.ClassReader; 16 | import org.objectweb.asm.Opcodes; 17 | import org.objectweb.asm.tree.ClassNode; 18 | import org.objectweb.asm.tree.FieldNode; 19 | import org.objectweb.asm.tree.MethodNode; 20 | 21 | public class InheritanceMap 22 | { 23 | //public static List libraries = new ArrayList(); 24 | public Map mapCache = new HashMap(); 25 | //public static Map classCache = new HashMap(); 26 | 27 | public List privatefields = new ArrayList(); 28 | public List privatemethods = new ArrayList(); 29 | 30 | 31 | public static class FieldHolder 32 | { 33 | public ClassNode cn; 34 | public FieldNode fn; 35 | public FieldHolder(ClassNode c, FieldNode f) 36 | { 37 | cn = c; 38 | fn = f; 39 | } 40 | } 41 | 42 | public static class MethodHolder 43 | { 44 | public ClassNode cn; 45 | public MethodNode mn; 46 | public MethodHolder(ClassNode c, MethodNode m) 47 | { 48 | cn = c; 49 | mn = m; 50 | } 51 | } 52 | 53 | public String className; 54 | public Map> fields = new HashMap>(); 55 | public Map> methods = new HashMap>(); 56 | 57 | 58 | public InheritanceMap() 59 | { 60 | } 61 | 62 | 63 | 64 | 65 | private InheritanceMap(ClassNode jc) 66 | { 67 | this.className = jc.name; 68 | 69 | for (FieldNode jf : jc.fields) 70 | { 71 | String fielddesc = jf.name + " " + jf.desc; 72 | 73 | if ((jf.access & Opcodes.ACC_PRIVATE) > 0) privatefields.add(fielddesc); 74 | 75 | HashSet fieldslist = this.fields.get(fielddesc); 76 | if (fieldslist == null) 77 | { 78 | fieldslist = new HashSet(); 79 | this.fields.put(fielddesc, fieldslist); 80 | } 81 | fieldslist.add(new FieldHolder(jc, jf)); 82 | } 83 | 84 | for (MethodNode jm : jc.methods) 85 | { 86 | String methoddesc = jm.name + " " + jm.desc; 87 | 88 | if ((jm.access & Opcodes.ACC_PRIVATE) > 0) privatemethods.add(methoddesc); 89 | 90 | HashSet methodslist = this.methods.get(methoddesc); 91 | if (methodslist == null) 92 | { 93 | methodslist = new HashSet(); 94 | this.methods.put(methoddesc, methodslist); 95 | } 96 | methodslist.add(new MethodHolder(jc, jm)); 97 | } 98 | } 99 | 100 | private void mergeMap(InheritanceMap cm) 101 | { 102 | for (String fielddesc : cm.fields.keySet()) 103 | { 104 | if (cm.privatefields.contains(fielddesc)) continue; 105 | 106 | if (this.fields.get(fielddesc) != null) this.fields.get(fielddesc).addAll(cm.fields.get(fielddesc)); 107 | else 108 | { 109 | HashSet f = new HashSet(); 110 | f.addAll(cm.fields.get(fielddesc)); 111 | this.fields.put(fielddesc, f); 112 | } 113 | } 114 | 115 | for (String methoddesc : cm.methods.keySet()) 116 | { 117 | if (cm.privatemethods.contains(methoddesc)) continue; 118 | 119 | if (this.methods.get(methoddesc) != null) this.methods.get(methoddesc).addAll(cm.methods.get(methoddesc)); 120 | else 121 | { 122 | HashSet m = new HashSet(); 123 | m.addAll(cm.methods.get(methoddesc)); 124 | this.methods.put(methoddesc, m); 125 | } 126 | } 127 | } 128 | 129 | 130 | public InheritanceMap buildMap(String paramClassname) throws IOException 131 | { 132 | ClassNode jc = locateClass(paramClassname); 133 | if (jc == null) return null; 134 | return buildMap(jc); 135 | } 136 | 137 | 138 | public InheritanceMap buildMap(ClassNode paramClass) throws IOException 139 | { 140 | InheritanceMap classmap = mapCache.get(paramClass.name); 141 | if (classmap != null) return classmap; 142 | 143 | //System.out.println("Begin buildMap: " + paramClass.getClassName()); 144 | 145 | classmap = new InheritanceMap(paramClass); 146 | 147 | for (String interfaceclassname : paramClass.interfaces) 148 | { 149 | ClassNode interfaceclass = locateClass(interfaceclassname); 150 | if (interfaceclass == null) { System.out.println("ERROR: Unable to locate " + interfaceclassname); continue; } 151 | classmap.mergeMap(buildMap(interfaceclass)); 152 | } 153 | 154 | if (paramClass.superName != null) 155 | { 156 | String superclassname = paramClass.superName; 157 | ClassNode superclass = locateClass(superclassname); 158 | if (superclass == null) System.out.println("ERROR: Unable to locate " + superclassname); 159 | else classmap.mergeMap(buildMap(superclass)); 160 | } 161 | //System.out.println("Finish buildMap: " + paramClass.getClassName()); 162 | 163 | mapCache.put(paramClass.name, classmap); 164 | 165 | return classmap; 166 | } 167 | 168 | 169 | 170 | public static ClassNode locateClassInJAR(String classname, String filename) throws IOException 171 | { 172 | JarFile jf = new JarFile(filename); 173 | 174 | for (Enumeration e = jf.entries(); e.hasMoreElements();) 175 | { 176 | JarEntry je = e.nextElement(); 177 | String name = je.getName(); 178 | if (!name.endsWith(".class")) continue; 179 | 180 | if (!classname.equals(name.replace(".class", ""))) continue; 181 | 182 | ClassReader reader = new ClassReader(jf.getInputStream(je)); 183 | ClassNode jc = new ClassNode(); 184 | reader.accept(jc, 0); 185 | 186 | if (jc.name.equals(classname)) { jf.close(); return jc; } 187 | } 188 | 189 | jf.close(); 190 | return null; 191 | } 192 | 193 | public ClassNode locateClass(String classname) throws IOException 194 | { 195 | return DynamicMappings.getClassNode(classname); 196 | 197 | /*ClassNode jc = InheritanceMap.classCache.get(classname); 198 | if (jc != null) return jc; 199 | 200 | for (String jarfile : InheritanceMap.libraries) 201 | { 202 | jc = locateClassInJAR(classname, jarfile); 203 | if (jc != null) 204 | { 205 | InheritanceMap.classCache.put(classname, jc); 206 | return jc; 207 | } 208 | } 209 | 210 | return null;*/ 211 | } 212 | 213 | 214 | /*public static void addSystemLibrary() 215 | { 216 | String systemJAR = System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar"; 217 | InheritanceMap.libraries.add(systemJAR); 218 | } 219 | 220 | public static void addLibrary(String paramLib) 221 | { 222 | InheritanceMap.libraries.add(paramLib); 223 | }*/ 224 | 225 | /*public static void addLibraryDir(String libDir) 226 | { 227 | List jars = FyddleUtil.walkDirForExt(libDir, ".jar"); 228 | for (String jar : jars) InheritanceMap.addLibrary(jar); 229 | } 230 | 231 | public static void addClassPool(ClassPool classPool) 232 | { 233 | InheritanceMap.classCache.putAll(classPool.getPool()); 234 | }*/ 235 | 236 | //public static void reset() 237 | //{ 238 | //InheritanceMap.libraries.clear(); 239 | //InheritanceMap.classCache.clear(); 240 | //InheritanceMap.mapCache.clear(); 241 | //} 242 | 243 | 244 | // For debugging 245 | public static void main(String[] args) throws IOException 246 | { 247 | InheritanceMap base = new InheritanceMap(); 248 | 249 | InheritanceMap map = base.buildMap("ake"); 250 | System.out.println("Fields: "); 251 | for (String key : map.fields.keySet()) { 252 | System.out.println(" " + key); 253 | for (FieldHolder key2 : map.fields.get(key)) { 254 | System.out.println(" " + key2.cn.name); 255 | } 256 | } 257 | 258 | System.out.println("Methods: "); 259 | for (String key : map.methods.keySet()) { 260 | System.out.println(" " + key); 261 | for (MethodHolder key2 : map.methods.get(key)) { 262 | System.out.println(" " + key2.cn.name); 263 | } 264 | } 265 | } 266 | 267 | } 268 | 269 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/Mapping.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ ElementType.METHOD }) 10 | public @interface Mapping 11 | { 12 | String[] provides() default {}; 13 | String[] depends() default {}; 14 | 15 | String[] providesFields() default {}; 16 | String[] dependsFields() default {}; 17 | 18 | String[] providesMethods() default {}; 19 | String[] dependsMethods() default {}; 20 | } -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/MappingsClass.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ ElementType.TYPE }) 10 | public @interface MappingsClass { 11 | boolean clientSide() default false; 12 | boolean serverSide() default false; 13 | } 14 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/MergeJars.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.util.ArrayList; 8 | import java.util.Enumeration; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | import java.util.jar.JarOutputStream; 15 | import java.util.zip.ZipEntry; 16 | 17 | import org.objectweb.asm.ClassReader; 18 | import org.objectweb.asm.ClassWriter; 19 | import org.objectweb.asm.Opcodes; 20 | import org.objectweb.asm.tree.AnnotationNode; 21 | import org.objectweb.asm.tree.ClassNode; 22 | import org.objectweb.asm.tree.FieldNode; 23 | import org.objectweb.asm.tree.MethodNode; 24 | 25 | 26 | public class MergeJars 27 | { 28 | public static Map classes = new HashMap(); 29 | public static Map miscFiles = new HashMap(); 30 | 31 | public static boolean quiet = false; 32 | 33 | public static final int CLIENT = 1; 34 | public static final int SERVER = 2; 35 | public static final int BOTH = 3; 36 | 37 | 38 | public static class MiscInfo 39 | { 40 | String name; 41 | 42 | int onClientServer = 0; 43 | } 44 | 45 | public static class ClassInfo 46 | { 47 | String name; 48 | Map fields = new HashMap(); 49 | Map methods = new HashMap(); 50 | 51 | int onClientServer = 0; 52 | } 53 | 54 | public static class FieldInfo 55 | { 56 | String name; 57 | String desc; 58 | 59 | int onClientServer = 0; 60 | } 61 | 62 | public static class MethodInfo 63 | { 64 | String name; 65 | String desc; 66 | 67 | int onClientServer = 0; 68 | } 69 | 70 | 71 | public static String[] excluded = new String[] { "META-INF/", "org/", "com/", "gnu/", "io/", "javax/", "org/" }; 72 | 73 | 74 | public static JarFile discoverClasses(File filename, int clientOrServer) 75 | { 76 | 77 | JarFile jarFile = null; 78 | try { 79 | jarFile = new JarFile(filename); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | 84 | Enumeration entries = jarFile.entries(); 85 | while (entries.hasMoreElements()) 86 | { 87 | JarEntry entry = entries.nextElement(); 88 | String entryName = entry.getName(); 89 | 90 | boolean isExcluded = false; 91 | for (String s : excluded) if (entryName.startsWith(s)) { isExcluded = true; break; } 92 | if (isExcluded) continue; 93 | 94 | if (!entryName.endsWith(".class")) 95 | { 96 | MiscInfo info = miscFiles.get(entryName); 97 | if (info == null) 98 | { 99 | info = new MiscInfo(); 100 | info.name = entryName; 101 | miscFiles.put(entryName, info); 102 | } 103 | info.onClientServer |= clientOrServer; 104 | 105 | continue; 106 | } 107 | 108 | byte[] buffer = getFileFromJar(entry, jarFile); 109 | 110 | ClassReader reader = new ClassReader(buffer); 111 | ClassNode cn = new ClassNode(); 112 | reader.accept(cn, 0); 113 | 114 | ClassInfo info = classes.get(cn.name); 115 | if (info == null) { info = new ClassInfo(); classes.put(cn.name, info); } 116 | info.name = cn.name; 117 | info.onClientServer |= clientOrServer; 118 | 119 | for (FieldNode field : (List)cn.fields) 120 | { 121 | FieldInfo finfo = info.fields.get(field.name); 122 | if (finfo == null) 123 | { 124 | finfo = new FieldInfo(); 125 | finfo.name = field.name; 126 | finfo.desc = field.desc; 127 | info.fields.put(field.name, finfo); 128 | } 129 | finfo.onClientServer |= clientOrServer; 130 | } 131 | 132 | for (MethodNode method : (List)cn.methods) 133 | { 134 | MethodInfo minfo = info.methods.get(method.name + method.desc); 135 | if (minfo == null) 136 | { 137 | minfo = new MethodInfo(); 138 | minfo.name = method.name; 139 | minfo.desc = method.desc; 140 | info.methods.put(method.name + method.desc, minfo); 141 | } 142 | minfo.onClientServer |= clientOrServer; 143 | 144 | } 145 | 146 | } 147 | 148 | return jarFile; 149 | 150 | } 151 | 152 | 153 | public static byte[] getFileFromJar(ZipEntry entry, JarFile jarFile) 154 | { 155 | byte[] buffer = null; 156 | 157 | if (entry != null) 158 | { 159 | try { 160 | InputStream stream = jarFile.getInputStream(entry); 161 | int pos = 0; 162 | buffer = new byte[(int)entry.getSize()]; 163 | while (true) 164 | { 165 | int read = stream.read(buffer, pos, Math.min(1024, (int)entry.getSize() - pos)); 166 | pos += read; 167 | if (read < 1) break; 168 | } 169 | } catch (IOException e) { 170 | e.printStackTrace(); 171 | } 172 | } 173 | 174 | return buffer; 175 | } 176 | 177 | 178 | public static byte[] getFileFromJar(String filename, JarFile jarFile) 179 | { 180 | return getFileFromJar(jarFile.getEntry(filename), jarFile); 181 | } 182 | 183 | 184 | public static byte[] getFileFromJar(String filename, String jarName) 185 | { 186 | byte[] buffer = null; 187 | 188 | JarFile jarFile = null; 189 | try { 190 | jarFile = new JarFile(jarName); 191 | } catch (IOException e) { 192 | e.printStackTrace(); 193 | } 194 | 195 | buffer = getFileFromJar(filename, jarFile); 196 | 197 | try { 198 | jarFile.close(); 199 | } catch (IOException e) { 200 | e.printStackTrace(); 201 | } 202 | 203 | return buffer; 204 | } 205 | 206 | 207 | public static ClassNode getClassFromJar(String className, String jarName) 208 | { 209 | ClassNode node = null; 210 | 211 | className = className.replace(".", "/"); 212 | byte[] buffer = getFileFromJar(className + ".class", jarName); 213 | 214 | if (buffer != null) 215 | { 216 | ClassReader reader = new ClassReader(buffer); 217 | node = new ClassNode(); 218 | reader.accept(node, 0); 219 | } 220 | 221 | return node; 222 | } 223 | 224 | 225 | public static ClassNode getClassFromJar(String className, JarFile jarFile) 226 | { 227 | ClassNode node = null; 228 | 229 | className = className.replace(".", "/"); 230 | byte[] buffer = getFileFromJar(className + ".class", jarFile); 231 | 232 | if (buffer != null) 233 | { 234 | ClassReader reader = new ClassReader(buffer); 235 | node = new ClassNode(); 236 | reader.accept(node, 0); 237 | } 238 | 239 | return node; 240 | } 241 | 242 | 243 | public static void writeToFile(String filename, byte[] data) 244 | { 245 | File f = new File(filename); 246 | f.toPath().getParent().toFile().mkdirs(); 247 | 248 | try { 249 | FileOutputStream stream = new FileOutputStream(filename); 250 | stream.write(data); 251 | stream.close(); 252 | } catch (IOException e) { 253 | e.printStackTrace(); 254 | } 255 | } 256 | 257 | 258 | 259 | public static void mergeJars(File clientJarFile, File serverJarFile, File outputFile) 260 | { 261 | if (!quiet) System.out.println("> Parsing client"); 262 | JarFile clientJar = discoverClasses(clientJarFile, CLIENT); 263 | if (!quiet) System.out.println("> Parsing server"); 264 | JarFile serverJar = discoverClasses(serverJarFile, SERVER); 265 | 266 | JarOutputStream outputJar = null; 267 | try { 268 | outputJar = new JarOutputStream(new FileOutputStream(outputFile)); 269 | } catch (IOException e) { 270 | e.printStackTrace(); 271 | } 272 | 273 | if (!quiet) System.out.println("> Merging client and server"); 274 | 275 | 276 | for (MiscInfo info : miscFiles.values()) 277 | { 278 | if ((info.onClientServer & CLIENT) == CLIENT) 279 | { 280 | ZipEntry entry = clientJar.getEntry(info.name); 281 | try { 282 | outputJar.putNextEntry(new ZipEntry(entry)); 283 | outputJar.write(getFileFromJar(entry, clientJar)); 284 | } catch (IOException e) { 285 | e.printStackTrace(); 286 | } 287 | } 288 | else 289 | { 290 | ZipEntry entry = serverJar.getEntry(info.name); 291 | try { 292 | outputJar.putNextEntry(new ZipEntry(entry)); 293 | outputJar.write(getFileFromJar(entry, serverJar)); 294 | } catch (IOException e) { 295 | e.printStackTrace(); 296 | } 297 | } 298 | } 299 | 300 | 301 | for (ClassInfo info : classes.values()) 302 | { 303 | if (info.onClientServer == CLIENT) 304 | { 305 | ClassNode clientClass = getClassFromJar(info.name, clientJar); 306 | 307 | if (clientClass.visibleAnnotations == null) clientClass.visibleAnnotations = new ArrayList(); 308 | clientClass.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ClientOnly;")); 309 | 310 | ClassWriter writer = new ClassWriter(Opcodes.ASM4); 311 | clientClass.accept(writer); 312 | 313 | byte[] classData = writer.toByteArray(); 314 | //writeToFile(outputFilename + File.separator + info.name + ".class", classData); 315 | try { 316 | outputJar.putNextEntry(new ZipEntry(info.name + ".class")); 317 | outputJar.write(classData); 318 | } 319 | catch (IOException e) { 320 | e.printStackTrace(); 321 | } 322 | } 323 | else if (info.onClientServer == SERVER) 324 | { 325 | ClassNode serverClass = getClassFromJar(info.name, serverJar); 326 | 327 | if (serverClass.visibleAnnotations == null) serverClass.visibleAnnotations = new ArrayList(); 328 | serverClass.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ServerOnly;")); 329 | 330 | ClassWriter writer = new ClassWriter(Opcodes.ASM4); 331 | serverClass.accept(writer); 332 | 333 | byte[] classData = writer.toByteArray(); 334 | 335 | //writeToFile(outputFilename + File.separator + info.name + ".class", classData); 336 | try { 337 | outputJar.putNextEntry(new ZipEntry(info.name + ".class")); 338 | outputJar.write(classData); 339 | } 340 | catch (IOException e) { 341 | e.printStackTrace(); 342 | } 343 | } 344 | else 345 | { 346 | ClassNode clientClass = getClassFromJar(info.name, clientJar); 347 | ClassNode serverClass = getClassFromJar(info.name, serverJar); 348 | 349 | for (FieldInfo field : info.fields.values()) 350 | { 351 | if (field.onClientServer == SERVER) 352 | { 353 | FieldNode serverField = null; 354 | for (FieldNode fn : (List)serverClass.fields) 355 | { 356 | if (fn.name.equals(field.name)) { serverField = fn; break; } 357 | } 358 | 359 | if (serverField.visibleAnnotations == null) serverField.visibleAnnotations = new ArrayList(); 360 | serverField.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ServerOnly;")); 361 | clientClass.fields.add(serverField); 362 | } 363 | else if (field.onClientServer == CLIENT) 364 | { 365 | FieldNode clientField = null; 366 | for (FieldNode fn : (List)clientClass.fields) 367 | { 368 | if (fn.name.equals(field.name)) { clientField = fn; break; } 369 | } 370 | 371 | if (clientField.visibleAnnotations == null) clientField.visibleAnnotations = new ArrayList(); 372 | clientField.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ClientOnly;")); 373 | } 374 | } 375 | 376 | for (MethodInfo method : info.methods.values()) 377 | { 378 | if (method.onClientServer == SERVER) 379 | { 380 | MethodNode serverMethod = null; 381 | for (MethodNode fn : (List)serverClass.methods) 382 | { 383 | if (fn.name.equals(method.name) && fn.desc.equals(method.desc)) { serverMethod = fn; break; } 384 | } 385 | 386 | if (serverMethod.visibleAnnotations == null) serverMethod.visibleAnnotations = new ArrayList(); 387 | serverMethod.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ServerOnly;")); 388 | clientClass.methods.add(serverMethod); 389 | } 390 | else if (method.onClientServer == CLIENT) 391 | { 392 | MethodNode clientMethod = null; 393 | for (MethodNode fn : (List)clientClass.methods) 394 | { 395 | if (fn.name.equals(method.name) && fn.desc.equals(method.desc)) { clientMethod = fn; break; } 396 | } 397 | 398 | if (clientMethod.visibleAnnotations == null) clientMethod.visibleAnnotations = new ArrayList(); 399 | clientMethod.visibleAnnotations.add(new AnnotationNode("Lnet/fybertech/meddleapi/side/ClientOnly;")); 400 | } 401 | } 402 | 403 | ClassWriter writer = new ClassWriter(Opcodes.ASM4); 404 | clientClass.accept(writer); 405 | //writeToFile(outputFilename + File.separator + info.name + ".class", writer.toByteArray()); 406 | try { 407 | outputJar.putNextEntry(new ZipEntry(info.name + ".class")); 408 | outputJar.write(writer.toByteArray()); 409 | } 410 | catch (IOException e) { 411 | e.printStackTrace(); 412 | } 413 | } 414 | } 415 | 416 | 417 | try { 418 | clientJar.close(); 419 | serverJar.close(); 420 | outputJar.close(); 421 | } catch (IOException e) { 422 | e.printStackTrace(); 423 | } 424 | } 425 | 426 | 427 | public static void main(String[] args) 428 | { 429 | String clientJarFilename = null; 430 | String serverJarFilename = null; 431 | String outputFilename = null; 432 | 433 | int nextVal = 0; 434 | for (String arg : args) 435 | { 436 | if (nextVal > 0) 437 | { 438 | if (nextVal == 1) clientJarFilename = arg; 439 | else if (nextVal == 2) serverJarFilename = arg; 440 | else outputFilename = arg; 441 | 442 | nextVal = 0; 443 | continue; 444 | } 445 | 446 | if (arg.equalsIgnoreCase("-c")) nextVal = 1; 447 | else if (arg.equalsIgnoreCase("-s")) nextVal = 2; 448 | else if (arg.equalsIgnoreCase("-o")) nextVal = 3; 449 | else if (arg.equalsIgnoreCase("-q")) quiet = true; 450 | } 451 | 452 | if (clientJarFilename == null) { System.err.println("Error: Client jar not specified"); System.exit(1); } 453 | if (serverJarFilename == null) { System.err.println("Error: Server jar not specified"); System.exit(2); } 454 | if (outputFilename == null) { System.err.println("Error: Output jar not specified"); System.exit(3); } 455 | 456 | File clientJarFile = new File(clientJarFilename); 457 | File serverJarFile = new File(serverJarFilename); 458 | if (!clientJarFile.exists()) { System.err.println("Error: Client jar not found"); System.exit(4); } 459 | if (!serverJarFile.exists()) { System.err.println("Error: Server jar not found"); System.exit(5); } 460 | 461 | mergeJars(clientJarFile, serverJarFile, new File(outputFilename)); 462 | } 463 | 464 | 465 | } 466 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/MethodCallIterator.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.util.Iterator; 4 | 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.FieldInsnNode; 8 | import org.objectweb.asm.tree.InsnNode; 9 | import org.objectweb.asm.tree.IntInsnNode; 10 | import org.objectweb.asm.tree.LabelNode; 11 | import org.objectweb.asm.tree.LdcInsnNode; 12 | import org.objectweb.asm.tree.LineNumberNode; 13 | import org.objectweb.asm.tree.MethodInsnNode; 14 | import org.objectweb.asm.tree.MethodNode; 15 | import org.objectweb.asm.tree.TypeInsnNode; 16 | import org.objectweb.asm.tree.VarInsnNode; 17 | 18 | public class MethodCallIterator implements Iterator { 19 | 20 | private MethodNode method; 21 | private AbstractInsnNode insn; 22 | private MethodCallIterator.MethodCall next; 23 | 24 | private boolean printWarnings; 25 | private boolean printDebug; 26 | private int index; 27 | private Object[] stack; 28 | private Object[] vars; 29 | private int sp; 30 | 31 | public MethodCallIterator(MethodNode method, boolean printWarnings, boolean printDebug) { 32 | this.method = method; 33 | this.insn = this.method.instructions.getFirst(); 34 | this.stack = new Object[100]; 35 | this.vars = new Object[100]; 36 | this.sp = 0; 37 | this.index = 0; 38 | this.printWarnings = printWarnings; 39 | this.printDebug = printDebug; 40 | this.next = null; 41 | this.loadNext(); 42 | } 43 | 44 | @Override 45 | public boolean hasNext() { 46 | return this.next != null; 47 | } 48 | 49 | @Override 50 | public MethodCallIterator.MethodCall next() { 51 | MethodCallIterator.MethodCall result = this.next; 52 | this.loadNext(); 53 | return result; 54 | } 55 | 56 | private boolean loadNext() { 57 | // Loop through the contents of registerBlocks and pull out any calls to registerBlock. 58 | // Then map the block ids and class names based on the parameters to registerBlock from 59 | // the mapping provided above. 60 | 61 | boolean foundNext = false; 62 | for (; insn != null && !foundNext; insn = insn.getNext()) 63 | { 64 | if (insn instanceof LabelNode || insn instanceof LineNumberNode) { 65 | // skip label/line numbers 66 | index++; 67 | continue; 68 | } 69 | 70 | if (insn instanceof InsnNode) { 71 | // handle integer / float constants, just push to virtual stack 72 | 73 | int opCode = ((InsnNode)insn).getOpcode(); 74 | switch (opCode) { 75 | case Opcodes.ICONST_0: 76 | stack[sp++] = 0; 77 | break; 78 | case Opcodes.ICONST_1: 79 | stack[sp++] = 1; 80 | break; 81 | case Opcodes.ICONST_2: 82 | stack[sp++] = 2; 83 | break; 84 | case Opcodes.ICONST_3: 85 | stack[sp++] = 3; 86 | break; 87 | case Opcodes.ICONST_4: 88 | stack[sp++] = 4; 89 | break; 90 | case Opcodes.ICONST_5: 91 | stack[sp++] = 5; 92 | break; 93 | case Opcodes.DUP: // duplicate top of stack 94 | stack[sp] = stack[sp - 1]; 95 | sp++; 96 | break; 97 | case Opcodes.FCONST_0: 98 | stack[sp++] = 0f; 99 | break; 100 | case Opcodes.FCONST_1: 101 | stack[sp++] = 1f; 102 | break; 103 | case Opcodes.FCONST_2: 104 | stack[sp++] = 2f; 105 | break; 106 | default: 107 | if (printWarnings) System.out.println("WARNING: discoverBlocks Unhandled InsnNode opcode: " + opCode); 108 | break; 109 | } 110 | } else if (insn instanceof FieldInsnNode) { 111 | // handle static field references, just push to virtual stack 112 | 113 | FieldInsnNode fi = (FieldInsnNode)insn; 114 | int opCode = fi.getOpcode(); 115 | switch (opCode) { 116 | case Opcodes.GETSTATIC: 117 | stack[sp++] = fi.name; 118 | break; 119 | default: 120 | if (printWarnings) System.out.println("WARNING: discoverBlocks Unhandled FieldInsnNode opcode: " + opCode); 121 | break; 122 | } 123 | 124 | } else if (insn instanceof TypeInsnNode) { 125 | // handle type references, push new instance to virtual stack 126 | 127 | TypeInsnNode ti = (TypeInsnNode)insn; 128 | int opCode = ti.getOpcode(); 129 | switch (opCode) { 130 | case Opcodes.NEW: 131 | stack[sp++] = ti.desc; 132 | break; 133 | case Opcodes.ANEWARRAY: 134 | stack[sp++] = ti.desc; 135 | break; 136 | default: 137 | if (printWarnings) System.out.println("WARNING: discoverBlocks Unhandled TypeInsnNode opcode: " + opCode); 138 | break; 139 | } 140 | 141 | } else if (insn instanceof MethodInsnNode) { 142 | // handle method invocations, pop appropriate number of arguments off of virtual stack 143 | // and check if method is registerBlock; if registerBlock is called then check class 144 | // mapping and add if block id is found. 145 | 146 | MethodInsnNode mi = (MethodInsnNode)insn; 147 | int argCount = argCount(mi.desc); 148 | 149 | if (insn.getOpcode() == Opcodes.INVOKESPECIAL) argCount++; // constructor consumes one item from the stack 150 | 151 | if (printDebug) System.out.print(index + ": " + mi.name + " ("); 152 | Object[] tempArgs = new Object[argCount]; 153 | for (int i = argCount - 1; i >= 0; i--) { 154 | tempArgs[i] = stack[--sp]; 155 | if (printDebug) System.out.print(stack[sp] + ","); 156 | } 157 | if (printDebug) System.out.println(")"); 158 | 159 | this.next = new MethodCallIterator.MethodCall(mi, tempArgs); 160 | foundNext = true; 161 | 162 | } else if (insn instanceof LdcInsnNode) { 163 | // handle load constant value references, just push to virtual stack 164 | 165 | LdcInsnNode li = (LdcInsnNode)insn; 166 | stack[sp++] = li.cst; 167 | 168 | } else if (insn instanceof VarInsnNode) { 169 | // handle variable references, if it is a LOAD instruction then push to stack, 170 | // if STORE instruction then pop from stack into virtual variable storage 171 | 172 | VarInsnNode vi = (VarInsnNode)insn; 173 | int opCode = vi.getOpcode(); 174 | switch (opCode) { 175 | case Opcodes.ALOAD: 176 | stack[sp++] = vars[vi.var]; 177 | //System.out.println("ALOAD: " + vars[vi.var]); 178 | break; 179 | case Opcodes.ASTORE: 180 | vars[vi.var] = stack[--sp]; 181 | //System.out.println("var" + vi.var + " = " + stack[sp]); 182 | break; 183 | default: 184 | if (printWarnings) System.out.println("WARNING: discoverBlocks Unhandled VarInsnNode opcode: " + opCode); 185 | } 186 | 187 | } else if (insn instanceof IntInsnNode) { 188 | // handle integer types, just push value to virtual stack 189 | 190 | IntInsnNode ii = (IntInsnNode)insn; 191 | int opCode = ii.getOpcode(); 192 | switch (opCode) { 193 | case Opcodes.BIPUSH: 194 | stack[sp++] = ii.operand; 195 | break; 196 | case Opcodes.SIPUSH: 197 | stack[sp++] = ii.operand; 198 | break; 199 | default: 200 | if (printWarnings) System.out.println("WARNING: discoverBlocks Unhandled IntInsnNode opcode: " + opCode); 201 | } 202 | 203 | } else { 204 | // unhandled type, display warning for troubleshooting. 205 | 206 | if (printWarnings) System.out.println("WARNING: Unhandled IsnsNode " + index + ": " + insn.toString()); 207 | 208 | } 209 | 210 | } 211 | 212 | if (!foundNext) this.next = null; 213 | return foundNext; 214 | } 215 | 216 | /** 217 | * Counts the number of arguments that must be passed into a method 218 | * based on the method signature provided. 219 | * @param methodDesc 220 | * @return 221 | */ 222 | public static int argCount(String methodDesc) { 223 | int count = 0; 224 | boolean complexType = false; 225 | for (int i = 1; i < methodDesc.length() && methodDesc.charAt(i) != ')'; i++) { 226 | if (complexType) { 227 | if (methodDesc.charAt(i) == ';') { 228 | complexType = false; 229 | count++; 230 | } 231 | continue; 232 | } 233 | if (methodDesc.charAt(i) == '[' || methodDesc.charAt(i) == 'L') 234 | complexType = true; 235 | else 236 | count++; 237 | } 238 | return count; 239 | } 240 | 241 | public class MethodCall { 242 | public MethodInsnNode methodNode; 243 | public Object[] args; 244 | 245 | public MethodCall(MethodInsnNode methodNode, Object[] args) { 246 | this.methodNode = methodNode; 247 | this.args = args; 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/MethodCallVisitor.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.util.Iterator; 4 | 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.FieldInsnNode; 8 | import org.objectweb.asm.tree.FrameNode; 9 | import org.objectweb.asm.tree.InsnNode; 10 | import org.objectweb.asm.tree.IntInsnNode; 11 | import org.objectweb.asm.tree.LabelNode; 12 | import org.objectweb.asm.tree.LdcInsnNode; 13 | import org.objectweb.asm.tree.LineNumberNode; 14 | import org.objectweb.asm.tree.MethodInsnNode; 15 | import org.objectweb.asm.tree.MethodNode; 16 | import org.objectweb.asm.tree.TypeInsnNode; 17 | import org.objectweb.asm.tree.VarInsnNode; 18 | 19 | public class MethodCallVisitor implements Iterable { 20 | 21 | private MethodNode method; 22 | private boolean printWarnings; 23 | 24 | public MethodCallVisitor(MethodNode method, boolean printWarnings) { 25 | this.method = method; 26 | this.printWarnings = printWarnings; 27 | } 28 | 29 | @Override 30 | public Iterator iterator() { 31 | return new MethodCallIterator(this.method, this.printWarnings, false); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/ModMappings.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.PrintWriter; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | import net.fybertech.dynamicmappings.InheritanceMap.FieldHolder; 17 | import net.fybertech.dynamicmappings.InheritanceMap.MethodHolder; 18 | 19 | import org.objectweb.asm.ClassReader; 20 | import org.objectweb.asm.Type; 21 | import org.objectweb.asm.tree.ClassNode; 22 | import org.objectweb.asm.tree.FieldNode; 23 | import org.objectweb.asm.tree.MethodNode; 24 | 25 | 26 | public class ModMappings { 27 | 28 | 29 | /*public static List walkTreeForClasses(File initialDir, File dir) 30 | { 31 | List files = new ArrayList<>(); 32 | for (File f : dir.listFiles()) { 33 | if (f.isDirectory()) files.addAll(walkTreeForClasses(initialDir, f)); 34 | else if (f.getName().endsWith(".class")) { 35 | String className = initialDir.toURI().relativize(f.toURI()).getPath(); 36 | className = className.substring(0, className.length() - 6); 37 | files.add(className); 38 | } 39 | } 40 | return files; 41 | }*/ 42 | 43 | 44 | static class Holder 45 | { 46 | public int type; 47 | public String owner; 48 | public String name; 49 | public String desc; 50 | 51 | public Holder(int t, String o, String n, String d) { 52 | type = t; 53 | owner = o; 54 | name = n; 55 | desc = d; 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | int result = 17; 61 | result = 31 * result + type; 62 | result = 31 * result + owner.hashCode(); 63 | result = 31 * result + name.hashCode(); 64 | result = 31 * result + desc.hashCode(); 65 | return result; 66 | } 67 | 68 | @Override 69 | public boolean equals(Object obj) { 70 | return obj.hashCode() == this.hashCode(); 71 | } 72 | } 73 | 74 | 75 | static String[] exclusions = new String[] { 76 | "org/objectweb/", 77 | "java/", 78 | "net/minecraft/launchwrapper/", 79 | "net/fybertech/dynamicmappings/", 80 | "org/apache/", 81 | "net/fybertech/meddle/", 82 | "net/fybertech/meddleapi/" 83 | }; 84 | 85 | 86 | 87 | public static boolean isExcluded(String owner) 88 | { 89 | for (String exclusion : exclusions) if (owner.startsWith(exclusion)) return true; 90 | return false; 91 | } 92 | 93 | 94 | public static List walkTreeForClasses(File dir) 95 | { 96 | List files = new ArrayList<>(); 97 | for (File f : dir.listFiles()) { 98 | if (f.isDirectory()) files.addAll(walkTreeForClasses(f)); 99 | else if (f.getName().endsWith(".class")) files.add(f); 100 | } 101 | return files; 102 | } 103 | 104 | 105 | public static Set searchConstantPoolForClasses(File classFile) 106 | { 107 | Set classes = new HashSet<>(); 108 | 109 | InputStream stream; 110 | ClassReader reader; 111 | try { 112 | stream = new FileInputStream(classFile); 113 | reader = new ClassReader(stream); 114 | } catch (Exception e1) { return classes; } 115 | 116 | int itemCount = reader.getItemCount(); 117 | char[] buffer = new char[reader.getMaxStringLength()]; 118 | 119 | for (int n = 1; n < itemCount; n++) { 120 | int pos = reader.getItem(n); 121 | if (pos == 0 || reader.b[pos - 1] != 7) continue; 122 | 123 | Arrays.fill(buffer, (char)0); 124 | reader.readUTF8(pos, buffer); 125 | String string = (new String(buffer)).trim(); 126 | if (string.length() < 1) continue; 127 | 128 | while (string != null && string.startsWith("[")) string = getArrayType(string); 129 | if (string == null) continue; 130 | 131 | if (!isExcluded(string)) classes.add(string); 132 | } 133 | 134 | return classes; 135 | } 136 | 137 | 138 | public static Set searchConstantPoolForFields(File classFile) 139 | { 140 | Set fields = new HashSet<>(); 141 | 142 | InputStream stream; 143 | ClassReader reader; 144 | try { 145 | stream = new FileInputStream(classFile); 146 | reader = new ClassReader(stream); 147 | } catch (Exception e1) { return fields; } 148 | 149 | int itemCount = reader.getItemCount(); 150 | char[] buffer = new char[reader.getMaxStringLength()]; 151 | 152 | for (int n = 1; n < itemCount; n++) { 153 | int pos = reader.getItem(n); 154 | if (pos == 0) continue; 155 | int cpType = reader.b[pos - 1]; 156 | if (cpType != 9) continue; 157 | 158 | String owner = reader.readClass(pos, buffer); 159 | int cpIndex = reader.getItem(reader.readUnsignedShort(pos + 2)); 160 | String name = reader.readUTF8(cpIndex, buffer); 161 | String desc = reader.readUTF8(cpIndex + 2, buffer); 162 | if (!isExcluded(owner)) fields.add(new Holder(cpType, owner, name, desc)); 163 | } 164 | 165 | return fields; 166 | } 167 | 168 | public static Set searchConstantPoolForMethods(File classFile) 169 | { 170 | Set fields = new HashSet<>(); 171 | 172 | InputStream stream; 173 | ClassReader reader; 174 | try { 175 | stream = new FileInputStream(classFile); 176 | reader = new ClassReader(stream); 177 | } catch (Exception e1) { return fields; } 178 | 179 | int itemCount = reader.getItemCount(); 180 | char[] buffer = new char[reader.getMaxStringLength()]; 181 | 182 | for (int n = 1; n < itemCount; n++) { 183 | int pos = reader.getItem(n); 184 | if (pos == 0) continue; 185 | int cpType = reader.b[pos - 1]; 186 | if (cpType != 10 && cpType != 11) continue; 187 | 188 | String owner = reader.readClass(pos, buffer); 189 | int cpIndex = reader.getItem(reader.readUnsignedShort(pos + 2)); 190 | String name = reader.readUTF8(cpIndex, buffer); 191 | String desc = reader.readUTF8(cpIndex + 2, buffer); 192 | if (!isExcluded(owner)) fields.add(new Holder(cpType, owner, name, desc)); 193 | } 194 | 195 | return fields; 196 | } 197 | 198 | 199 | public static Set getClassFields(File classFile) 200 | { 201 | Set fields = new HashSet<>(); 202 | 203 | InputStream stream; 204 | ClassReader reader; 205 | ClassNode cn = new ClassNode(); 206 | try { 207 | stream = new FileInputStream(classFile); 208 | reader = new ClassReader(stream); 209 | } catch (Exception e1) { return fields; } 210 | 211 | reader.accept(cn, 0); 212 | 213 | for (FieldNode field : cn.fields) { 214 | fields.add(new Holder(9, cn.name, field.name, field.desc)); 215 | } 216 | 217 | return fields; 218 | } 219 | 220 | 221 | public static Set getClassMethods(File classFile) 222 | { 223 | Set methods = new HashSet<>(); 224 | 225 | InputStream stream; 226 | ClassReader reader; 227 | ClassNode cn = new ClassNode(); 228 | try { 229 | stream = new FileInputStream(classFile); 230 | reader = new ClassReader(stream); 231 | } catch (Exception e1) { return methods; } 232 | 233 | reader.accept(cn, 0); 234 | 235 | for (MethodNode method : cn.methods) { 236 | methods.add(new Holder(10, cn.name, method.name, method.desc)); 237 | } 238 | 239 | return methods; 240 | } 241 | 242 | 243 | public static String getArrayType(String array) 244 | { 245 | Type t = Type.getType(array); 246 | if (t.getSort() != Type.ARRAY) return array; 247 | t = t.getElementType(); 248 | if (t.getSort() != Type.OBJECT) return null; 249 | return t.getClassName().replace(".", "/"); 250 | } 251 | 252 | 253 | public static int getMappingSide(String mapping) 254 | { 255 | if (DynamicMappings.clientMappingsSet.contains(mapping)) return 1; 256 | if (DynamicMappings.serverMappingsSet.contains(mapping)) return 2; 257 | return 3; 258 | } 259 | 260 | 261 | public static void main(String[] args) 262 | { 263 | //System.out.println(System.getProperty("java.class.path")); 264 | 265 | if (args.length < 1) { 266 | System.out.println("No input directory specified!"); 267 | return; 268 | } 269 | 270 | File classDir = new File(args[0]); 271 | if (!classDir.exists() || !classDir.isDirectory()) { 272 | System.out.println("Doesn't exist or isn't directory: " + args[0]); 273 | return; 274 | } 275 | 276 | 277 | File outputFile = null; 278 | 279 | if (args.length < 2) { 280 | System.out.println("No output file specified, printing to console."); 281 | } 282 | else outputFile = new File(args[1]); 283 | 284 | 285 | 286 | 287 | 288 | 289 | // Put DynamicMappings in simulated mode so that we can get the full list of mappings they 290 | // might provide, while allowing this application to use a remapped jar for its inheritance 291 | // maps. 292 | DynamicMappings.simulatedMappings = true; 293 | DynamicMappings.generateClassMappings(); 294 | 295 | 296 | //System.out.println(classDir.getAbsolutePath()); 297 | 298 | Set classes = new HashSet<>(); 299 | Set fields = new HashSet<>(); 300 | Set methods = new HashSet<>(); 301 | 302 | List classFiles = walkTreeForClasses(classDir); 303 | for (File f : classFiles) { 304 | classes.addAll(searchConstantPoolForClasses(f)); 305 | 306 | fields.addAll(searchConstantPoolForFields(f)); 307 | fields.addAll(getClassFields(f)); 308 | 309 | methods.addAll(searchConstantPoolForMethods(f)); 310 | methods.addAll(getClassMethods(f)); 311 | } 312 | 313 | InheritanceMap inheritanceMap = new InheritanceMap(); 314 | 315 | List output = new ArrayList<>(); 316 | 317 | //System.out.println("Classes:"); 318 | for (String s : classes) { 319 | boolean hasMapping = DynamicMappings.classMappings.containsKey(s); 320 | //System.out.println(" " + (hasMapping ? "TRUE " :"") + s); 321 | if (hasMapping) output.add("C " + getMappingSide(s) + " " + s); 322 | } 323 | 324 | //System.out.println("Fields:"); 325 | for (Holder holder : fields) { 326 | //System.out.println(" " + holder.owner + " " + holder.name + " " + holder.desc); 327 | InheritanceMap im = null; 328 | try { 329 | im = inheritanceMap.buildMap(holder.owner); 330 | } catch (IOException e) { 331 | e.printStackTrace(); 332 | } 333 | if (im == null) { System.out.println("COULDN'T BUILD MAP: " + holder.owner); continue; } 334 | 335 | HashSet fh = im.fields.get(holder.name + " " + holder.desc); 336 | for (FieldHolder f : fh) { 337 | String mapping = f.cn.name + " " + f.fn.name + " " + f.fn.desc; 338 | boolean hasMapping = DynamicMappings.fieldMappings.containsKey(mapping); 339 | //System.out.println(" " + (hasMapping ? "TRUE " :"") + f.cn.name + " " + f.fn.name); 340 | if (hasMapping) output.add("F " + getMappingSide(mapping) + " " + mapping); 341 | } 342 | } 343 | 344 | //System.out.println("Methods:"); 345 | for (Holder holder : methods) { 346 | //System.out.println(" " + holder.owner + " " + holder.name + " " + holder.desc); 347 | 348 | InheritanceMap im = null; 349 | try { 350 | im = inheritanceMap.buildMap(holder.owner); 351 | } catch (IOException e) { 352 | e.printStackTrace(); 353 | } 354 | if (im == null) { System.out.println("COULDN'T BUILD MAP: " + holder.owner); continue; } 355 | 356 | HashSet mh = im.methods.get(holder.name + " " + holder.desc); 357 | if (mh == null) continue; // Shouldn't be possible in normal circumstances, but we'll do for safety 358 | 359 | for (MethodHolder m : mh) { 360 | String mapping = m.cn.name + " " + m.mn.name + " " + m.mn.desc; 361 | boolean hasMapping = DynamicMappings.methodMappings.containsKey(mapping); 362 | //System.out.println(" " + (hasMapping ? "TRUE " :"") + m.cn.name + " " + m.mn.name); 363 | if (hasMapping) output.add("M " + getMappingSide(mapping) + " " + mapping); 364 | } 365 | } 366 | 367 | if (outputFile != null) { 368 | outputFile.toPath().getParent().toFile().mkdirs(); 369 | 370 | PrintWriter pw; 371 | try { 372 | pw = new PrintWriter(new FileOutputStream(outputFile)); 373 | for (String line : output) pw.println(line); 374 | pw.close(); 375 | } catch (IOException e) { 376 | e.printStackTrace(); 377 | } 378 | } 379 | else 380 | for (String line : output) System.out.println(line); 381 | 382 | 383 | 384 | } 385 | 386 | } 387 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/ParmParser.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ParmParser 7 | { 8 | 9 | public class Parm 10 | { 11 | String key; 12 | int paramCount; 13 | boolean found; 14 | String[] results; 15 | 16 | public Parm(String key, int parms) 17 | { 18 | this.key = key; 19 | this.paramCount = parms; 20 | this.found = false; 21 | this.results = new String[parms]; 22 | for (int n = 0; n < parms; n++) this.results[n] = ""; 23 | } 24 | 25 | public String getFirstResult() 26 | { 27 | if (found && results.length > 0) return results[0]; 28 | return null; 29 | } 30 | } 31 | 32 | public Map parms = new HashMap(); 33 | 34 | Parm currentParm = null; 35 | int currentParmCount = 0; 36 | 37 | public Parm addParm(String key, int parms) 38 | { 39 | Parm p = new Parm(key, parms); 40 | this.parms.put(key, p); 41 | return p; 42 | } 43 | 44 | public Parm getParm(String key) 45 | { 46 | return this.parms.get(key); 47 | } 48 | 49 | public void processArgs(String[] args) 50 | { 51 | for (String arg : args) 52 | { 53 | if (currentParm == null) 54 | { 55 | currentParmCount = 0; 56 | currentParm = parms.get(arg); 57 | if (currentParm != null) 58 | { 59 | currentParm.found = true; 60 | if (currentParm.paramCount == 0) currentParm = null; 61 | } 62 | } 63 | else 64 | { 65 | currentParm.results[currentParmCount++] = arg; 66 | if (currentParmCount >= currentParm.paramCount) currentParm = null; 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/Tweaker.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | import net.fybertech.meddle.MeddleMod; 7 | import net.minecraft.launchwrapper.ITweaker; 8 | import net.minecraft.launchwrapper.LaunchClassLoader; 9 | 10 | 11 | @MeddleMod(id="dynamicmappings", name="Dynamic Mappings", author="FyberOptic", version="028") 12 | public class Tweaker implements ITweaker 13 | { 14 | 15 | @Override 16 | public void acceptOptions(List args, File gameDir, File assetsDir, String profile) 17 | { 18 | DynamicMappings.discoverMapperConfigs(); 19 | DynamicMappings.generateClassMappings(); 20 | } 21 | 22 | @Override 23 | public void injectIntoClassLoader(LaunchClassLoader classLoader) {} 24 | 25 | @Override 26 | public String getLaunchTarget() { 27 | return null; 28 | } 29 | 30 | @Override 31 | public String[] getLaunchArguments() { 32 | return new String[0]; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/net/fybertech/dynamicmappings/mappers/MappingsBase.java: -------------------------------------------------------------------------------- 1 | package net.fybertech.dynamicmappings.mappers; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import net.fybertech.dynamicmappings.DynamicMappings; 10 | 11 | import org.objectweb.asm.ClassReader; 12 | import org.objectweb.asm.tree.AbstractInsnNode; 13 | import org.objectweb.asm.tree.ClassNode; 14 | import org.objectweb.asm.tree.FieldInsnNode; 15 | import org.objectweb.asm.tree.FieldNode; 16 | import org.objectweb.asm.tree.MethodInsnNode; 17 | import org.objectweb.asm.tree.MethodNode; 18 | 19 | public class MappingsBase 20 | { 21 | public ClassNode getClassNode(String className) 22 | { 23 | return DynamicMappings.getClassNode(className); 24 | } 25 | 26 | public ClassNode getClassNodeFromMapping(String deobfClass) 27 | { 28 | return DynamicMappings.getClassNodeFromMapping(deobfClass); 29 | } 30 | 31 | public void addClassMapping(String deobfClassName, ClassNode node) 32 | { 33 | DynamicMappings.addClassMapping(deobfClassName, node); 34 | } 35 | 36 | public void addClassMapping(String deobfClassName, String obfClassName) 37 | { 38 | DynamicMappings.addClassMapping(deobfClassName, obfClassName); 39 | } 40 | 41 | public String getClassMapping(String deobfClassName) 42 | { 43 | return DynamicMappings.getClassMapping(deobfClassName); 44 | } 45 | 46 | public boolean searchConstantPoolForStrings(String className, String... matchStrings) 47 | { 48 | return DynamicMappings.searchConstantPoolForStrings(className, matchStrings); 49 | } 50 | 51 | public List getMatchingMethods(ClassNode cn, String name, String desc) 52 | { 53 | return DynamicMappings.getMatchingMethods(cn, name, desc); 54 | } 55 | 56 | public List getMatchingMethods(ClassNode cn, int accessCode, int returnType, int... parameterTypes) 57 | { 58 | return DynamicMappings.getMatchingMethods(cn, accessCode, returnType, parameterTypes); 59 | } 60 | 61 | public void addFieldMapping(String deobfFieldDesc, String obfFieldDesc) 62 | { 63 | DynamicMappings.addFieldMapping(deobfFieldDesc, obfFieldDesc); 64 | } 65 | 66 | public void addFieldMapping(String deobfFieldDesc, FieldInsnNode obfField) 67 | { 68 | DynamicMappings.addFieldMapping(deobfFieldDesc, obfField.owner + " " + obfField.name + " " + obfField.desc); 69 | } 70 | 71 | public void addMethodMapping(String deobfMethodDesc, String obfMethodDesc) 72 | { 73 | DynamicMappings.addMethodMapping(deobfMethodDesc, obfMethodDesc); 74 | } 75 | 76 | public void addMethodMapping(String deobfMethodDesc, MethodInsnNode obfMethod) 77 | { 78 | DynamicMappings.addMethodMapping(deobfMethodDesc, obfMethod.owner + " " + obfMethod.name + " " + obfMethod.desc); 79 | } 80 | 81 | public boolean matchOpcodeSequence(AbstractInsnNode insn, int...opcodes) 82 | { 83 | return DynamicMappings.matchOpcodeSequence(insn, opcodes); 84 | } 85 | 86 | public String getLdcString(AbstractInsnNode node) 87 | { 88 | return DynamicMappings.getLdcString(node); 89 | } 90 | 91 | public boolean checkMethodParameters(MethodNode method, int ... types) 92 | { 93 | return DynamicMappings.checkMethodParameters(method, types); 94 | } 95 | 96 | public List getMatchingFields(ClassNode cn, String name, String desc) 97 | { 98 | return DynamicMappings.getMatchingFields(cn, name, desc); 99 | } 100 | 101 | public List removeMethodsWithFlags(List methods, int accFlags) 102 | { 103 | return DynamicMappings.removeMethodsWithFlags(methods, accFlags); 104 | } 105 | 106 | public List removeMethodsWithoutFlags(List methods, int accFlags) 107 | { 108 | return DynamicMappings.removeMethodsWithoutFlags(methods, accFlags); 109 | } 110 | 111 | public boolean isLdcWithString(AbstractInsnNode node, String string) 112 | { 113 | return DynamicMappings.isLdcWithString(node, string); 114 | } 115 | 116 | public boolean isLdcWithInteger(AbstractInsnNode node, int val) 117 | { 118 | return DynamicMappings.isLdcWithInteger(node, val); 119 | } 120 | 121 | public boolean isLdcWithFloat(AbstractInsnNode node, float val) 122 | { 123 | return DynamicMappings.isLdcWithFloat(node, val); 124 | } 125 | 126 | public AbstractInsnNode getNextRealOpcode(AbstractInsnNode insn) 127 | { 128 | return DynamicMappings.getNextRealOpcode(insn); 129 | } 130 | 131 | public MethodNode getMethodNode(ClassNode cn, String obfMapping) 132 | { 133 | return DynamicMappings.getMethodNode(cn, obfMapping); 134 | } 135 | 136 | public MethodNode getMethodNode(ClassNode cn, MethodInsnNode obfMethod) 137 | { 138 | if (obfMethod == null) return null; 139 | return DynamicMappings.getMethodNode(cn, obfMethod.owner + " " + obfMethod.name + " " + obfMethod.desc); 140 | } 141 | 142 | public MethodNode getMethodNodeFromMapping(ClassNode cn, String deobfMapping) 143 | { 144 | return DynamicMappings.getMethodNodeFromMapping(cn, deobfMapping); 145 | } 146 | 147 | @SuppressWarnings("rawtypes") 148 | public List getMethodsWithDescriptor(List methods, String desc) 149 | { 150 | return DynamicMappings.getMethodsWithDescriptor(methods, desc); 151 | } 152 | 153 | public String assembleDescriptor(Object... objects) 154 | { 155 | return DynamicMappings.assembleDescriptor(objects); 156 | } 157 | 158 | public List getAllInsnNodesOfType(AbstractInsnNode startInsn, Class classType) 159 | { 160 | return DynamicMappings.getAllInsnNodesOfType(startInsn, classType); 161 | } 162 | 163 | public boolean searchConstantPoolForClasses(String className, String... matchStrings) 164 | { 165 | return DynamicMappings.searchConstantPoolForClasses(className, matchStrings); 166 | } 167 | 168 | public AbstractInsnNode[] getOpcodeSequenceArray(AbstractInsnNode insn, int...opcodes) 169 | { 170 | return DynamicMappings.getOpcodeSequenceArray(insn, opcodes); 171 | } 172 | 173 | @SuppressWarnings("unchecked") 174 | public AbstractInsnNode[] getInsnNodeSequenceArray(AbstractInsnNode insn, Class...nodeClasses) 175 | { 176 | return DynamicMappings.getInsnNodeSequenceArray(insn, nodeClasses); 177 | } 178 | 179 | public AbstractInsnNode findNextOpcodeNum(AbstractInsnNode insn, int opcode) 180 | { 181 | return DynamicMappings.findNextOpcodeNum(insn, opcode); 182 | } 183 | 184 | public T getNextInsnNodeOfType(AbstractInsnNode startInsn, Class classType) 185 | { 186 | return DynamicMappings.getNextInsnNodeOfType(startInsn, classType); 187 | } 188 | 189 | public String getLdcClass(AbstractInsnNode node) 190 | { 191 | return DynamicMappings.getLdcClass(node); 192 | } 193 | 194 | public FieldNode getFieldNode(ClassNode cn, String obfMapping) 195 | { 196 | return DynamicMappings.getFieldNode(cn, obfMapping); 197 | } 198 | 199 | public FieldNode getFieldNodeFromMapping(ClassNode cn, String deobfMapping) 200 | { 201 | return DynamicMappings.getFieldNodeFromMapping(cn, deobfMapping); 202 | } 203 | 204 | public boolean classHasInterfaces(ClassNode classNode, String... ifaces) 205 | { 206 | return DynamicMappings.classHasInterfaces(classNode, ifaces); 207 | } 208 | 209 | public boolean doesInheritFrom(String className, String inheritFrom) 210 | { 211 | return DynamicMappings.doesInheritFrom(className, inheritFrom); 212 | } 213 | 214 | 215 | public boolean searchConstantPoolForFields(String className, String...fields) 216 | { 217 | className = className.replace(".", "/"); 218 | InputStream stream = DynamicMappings.class.getClassLoader().getResourceAsStream(className + ".class"); 219 | if (stream == null) return false; 220 | 221 | ClassReader reader = null; 222 | try { 223 | reader = new ClassReader(stream); 224 | } catch (IOException e) { return false; } 225 | 226 | int itemCount = reader.getItemCount(); 227 | char[] buffer = new char[reader.getMaxStringLength()]; 228 | 229 | int matches = 0; 230 | 231 | for (int n = 1; n < itemCount; n++) { 232 | int pos = reader.getItem(n); 233 | if (pos == 0 || reader.b[pos - 1] != 9) continue; 234 | String owner = reader.readClass(pos, buffer); 235 | pos = reader.getItem(reader.readUnsignedShort(pos + 2)); 236 | String name = reader.readUTF8(pos, buffer); 237 | String desc = reader.readUTF8(pos + 2, buffer); 238 | 239 | String fieldRef = owner + " " + name + " " + desc; 240 | 241 | for (int n2 = 0; n2 < fields.length; n2++) { 242 | if (fieldRef.equals(fields[n2].replace(".", "/"))) { matches++; break; } 243 | } 244 | } 245 | 246 | return (matches == fields.length); 247 | } 248 | 249 | 250 | public String getSoundField(String sound) 251 | { 252 | String full = getSoundFieldFull(sound); 253 | if (full == null) return null; 254 | 255 | return full.split(" ")[1]; 256 | 257 | } 258 | 259 | public String getSoundFieldFull(String sound) 260 | { 261 | sound = sound.replace(".", "_"); 262 | return DynamicMappings.getFieldMapping("net/minecraft/init/Sounds " + sound + " Lnet/minecraft/util/Sound;"); 263 | } 264 | 265 | 266 | public List getAllInsnNodesOfType(MethodNode method, Class classType) 267 | { 268 | return DynamicMappings.getAllInsnNodesOfType(method.instructions.getFirst(), classType); 269 | } 270 | 271 | public List getAllInsnNodesOfType(ClassNode classNode, Class classType) 272 | { 273 | List output = new ArrayList<>(); 274 | if (classNode.methods == null) return output; 275 | 276 | for (MethodNode method : classNode.methods) { 277 | output.addAll(getAllInsnNodesOfType(method, classType)); 278 | } 279 | return output; 280 | } 281 | 282 | 283 | @SuppressWarnings("unchecked") 284 | public boolean matchInsnNodeSequence(AbstractInsnNode insn, Class...nodeClasses) 285 | { 286 | return DynamicMappings.matchInsnNodeSequence(insn, nodeClasses); 287 | } 288 | 289 | 290 | public List filterFieldInsnNodes(List list, String owner, String desc) 291 | { 292 | List output = new ArrayList<>(); 293 | for (FieldInsnNode fn : list) { 294 | if (owner != null && !fn.owner.equals(owner)) continue; 295 | if (desc != null && !fn.desc.equals(desc)) continue; 296 | output.add(fn); 297 | } 298 | return output; 299 | } 300 | 301 | 302 | public List filterMethodInsnNodes(List list, String owner, String name, String desc) 303 | { 304 | List output = new ArrayList<>(); 305 | for (MethodInsnNode mn : list) { 306 | if (owner != null && !mn.owner.equals(owner)) continue; 307 | if (name != null && !mn.name.equals(name)) continue; 308 | if (desc != null && !mn.desc.equals(desc)) continue; 309 | output.add(mn); 310 | } 311 | return output; 312 | } 313 | 314 | public List filterMethodInsnNodes(List list, String owner, String desc) 315 | { 316 | return filterMethodInsnNodes(list, owner, null, desc); 317 | } 318 | 319 | public List filterMethodInsnNodes(List list, String owner, MethodNode node) 320 | { 321 | return filterMethodInsnNodes(list, owner, node.name, node.desc); 322 | } 323 | 324 | 325 | public int countFieldsWithDesc(ClassNode cn, String obfDesc) 326 | { 327 | int count = 0; 328 | for (FieldNode fn : cn.fields) { 329 | if (fn.desc.equals(obfDesc)) count++; 330 | } 331 | return count; 332 | } 333 | 334 | 335 | public List getStringsFromMethod(MethodNode method) 336 | { 337 | return DynamicMappings.getStringsFromMethod(method); 338 | } 339 | 340 | 341 | public boolean doesMethodContainString(MethodNode method, String string) 342 | { 343 | return DynamicMappings.doesMethodContainString(method, string); 344 | } 345 | 346 | 347 | public List getMethodsContainingString(ClassNode cn, String string) 348 | { 349 | return DynamicMappings.getMethodsContainingString(cn, string); 350 | } 351 | 352 | 353 | public boolean doesMethodUseField(MethodNode method, String owner, String name, String desc) 354 | { 355 | return DynamicMappings.doesMethodUseField(method, owner, name, desc); 356 | } 357 | 358 | 359 | public boolean doesMethodUseMethod(MethodNode method, String owner, String name, String desc) 360 | { 361 | return DynamicMappings.doesMethodUseMethod(method, owner, name, desc); 362 | } 363 | 364 | 365 | public List filterMethodsUsingField(List paramMethods, String owner, String name, String desc) 366 | { 367 | return DynamicMappings.filterMethodsUsingField(paramMethods, owner, name, desc); 368 | } 369 | 370 | 371 | public Integer getLdcInteger(AbstractInsnNode node) 372 | { 373 | return DynamicMappings.getLdcInteger(node); 374 | } 375 | 376 | 377 | public Float getLdcFloat(AbstractInsnNode node) 378 | { 379 | return DynamicMappings.getLdcFloat(node); 380 | } 381 | 382 | 383 | public List getIntegersFromMethod(MethodNode method) 384 | { 385 | return DynamicMappings.getIntegersFromMethod(method); 386 | } 387 | 388 | 389 | public List getFloatsFromMethod(MethodNode method) 390 | { 391 | return DynamicMappings.getFloatsFromMethod(method); 392 | } 393 | 394 | 395 | public boolean doesMethodContainInteger(MethodNode method, int i) 396 | { 397 | return DynamicMappings.doesMethodContainInteger(method, i); 398 | } 399 | 400 | 401 | public List filterMethodsUsingMethod(List paramMethods, String owner, String name, String desc) 402 | { 403 | return DynamicMappings.filterMethodsUsingMethod(paramMethods, owner, name, desc); 404 | } 405 | } 406 | --------------------------------------------------------------------------------