All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.md_5.specialsource.JarMapping Maven / Gradle / Ivy

Go to download

A jar compare and renaming engine designed for comparing and remapping 2 jars of differnent obfuscation mappings. Can also be useful for reobfuscation.

The newest version!
/**
 * Copyright (c) 2012, md_5. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * The name of the author may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package net.md_5.specialsource;

import net.md_5.specialsource.util.FileLocator;
import net.md_5.specialsource.transformer.MavenShade;
import net.md_5.specialsource.writer.CompactSearge;
import net.md_5.specialsource.writer.Searge;
import net.md_5.specialsource.writer.MappingWriter;
import net.md_5.specialsource.provider.InheritanceProvider;
import net.md_5.specialsource.transformer.MinecraftCodersPack;
import net.md_5.specialsource.transformer.MethodDescriptor;
import net.md_5.specialsource.transformer.ChainingTransformer;
import net.md_5.specialsource.transformer.MappingTransformer;
import java.io.*;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.objectweb.asm.commons.Remapper;

public class JarMapping {

    public final LinkedHashMap packages = new LinkedHashMap();
    public final Map classes = new HashMap();
    public final Map fields = new HashMap();
    public final Map methods = new HashMap();
    private InheritanceMap inheritanceMap = new InheritanceMap();
    private InheritanceProvider fallbackInheritanceProvider = null;
    private Set excludedPackages = new HashSet();
    private String currentClass = null;

    public JarMapping() {
    }

    /**
     * Set the inheritance map used for caching superclass/interfaces. This call
     * be omitted to use a local cache, or set to your own global cache.
     */
    public void setInheritanceMap(InheritanceMap inheritanceMap) {
        this.inheritanceMap = inheritanceMap;
    }

    /**
     * Set the inheritance provider to be consulted if the inheritance map has
     * no information on the requested class (results will be cached in the
     * inheritance map).
     */
    public void setFallbackInheritanceProvider(InheritanceProvider fallbackInheritanceProvider) {
        this.fallbackInheritanceProvider = fallbackInheritanceProvider;
    }

    /**
     * Add a class name prefix to the mapping ignore list. Note: this only
     * applies before loading mappings, not after
     */
    public void addExcludedPackage(String packageName) {
        SpecialSource.log("Protecting Package: " + packageName);
        excludedPackages.add(packageName);
    }

    private boolean isExcludedPackage(String desc) {
        for (String packageName : excludedPackages) {
            if (desc.startsWith(packageName)) {
                return true;
            }
        }

        return false;
    }

    public String tryClimb(Map map, NodeType type, String owner, String name, String desc, int access) {
        String key = owner + "/" + name;

        String mapped = (desc != null) ? map.get(key + "/" + desc) : null;
        if (mapped == null) {
            mapped = map.get(key);
        }
        if (mapped == null && (access == -1 || (!Modifier.isPrivate(access) && !Modifier.isStatic(access)))) {
            Collection parents = null;

            if (inheritanceMap.hasParents(owner)) {
                parents = inheritanceMap.getParents(owner);
            } else if (fallbackInheritanceProvider != null) {
                parents = fallbackInheritanceProvider.getParents(owner);
                inheritanceMap.setParents(owner, parents);
            }

            if (parents != null) {
                // climb the inheritance tree
                for (String parent : parents) {
                    mapped = tryClimb(map, type, parent, name, desc, access);
                    if (mapped != null) {
                        return mapped;
                    }
                }
            }
        }
        return mapped;
    }

    /**
     * Load mappings from an MCP directory
     *
     * @param dirname MCP directory name, local file or remote URL ending in '/'
     * @param reverse If true, swap input and output
     * @param ignoreCsv If true, ignore fields.csv and methods.csv (but not
     * packages.csv)
     * @param numericSrgNames If true, load numeric "srg" names (num->mcp
     * instead of obf->mcp)
     */
    private void loadMappingsDir(String dirname, boolean reverse, boolean ignoreCsv, boolean numericSrgNames) throws IOException {
        File dir = new File(dirname);
        if (!FileLocator.isHTTPURL(dirname) && !dir.isDirectory()) {
            throw new IllegalArgumentException("loadMappingsDir(" + dir + "): not a directory");
        }

        String sep = System.getProperty("file.separator");

        List srgFiles = new ArrayList();

        File joinedSrg = FileLocator.getFile(dirname + sep + "joined.srg");
        if (joinedSrg.exists()) {
            // FML/MCP client/server joined
            srgFiles.add(joinedSrg);
        } else {
            // vanilla MCP separated sides
            File serverSrg = FileLocator.getFile(dirname + sep + "server.srg");
            File clientSrg = FileLocator.getFile(dirname + sep + "client.srg");
            if (serverSrg.exists()) {
                srgFiles.add(serverSrg);
            }
            if (clientSrg.exists()) {
                srgFiles.add(clientSrg);
            }
        }

        if (srgFiles.size() == 0) {
            throw new IOException("loadMappingsDir(" + dirname + "): no joined.srg, client.srg, or server.srg found");
        }

        // Read output names through csv mappings, if available & enabled
        File fieldsCsv = FileLocator.getFile(dirname + sep + "fields.csv");
        File methodsCsv = FileLocator.getFile(dirname + sep + "methods.csv");
        File packagesCsv = FileLocator.getFile(dirname + sep + "packages.csv"); // FML repackaging, optional

        MinecraftCodersPack outputTransformer;
        MappingTransformer inputTransformer;

        if (numericSrgNames) {
            // Wants numeric "srg" names -> descriptive "csv" names. To accomplish this:
            // 1. load obf->mcp (descriptive "csv") as chainMappings
            // 2. load again but chaining input (obf) through mcp, and ignoring csv on output
            // 3. result: mcp->srg, similar to MCP ./reobfuscate --srgnames
            JarMapping chainMappings = new JarMapping();
            chainMappings.loadMappingsDir(dirname, reverse, false/*ignoreCsv*/, false/*numeric*/);
            inputTransformer = new ChainingTransformer(new JarRemapper(chainMappings));
            ignoreCsv = true; // keep numeric srg as output
        } else {
            inputTransformer = null;
        }

        if (fieldsCsv.exists() && methodsCsv.exists()) {
            outputTransformer = new MinecraftCodersPack(ignoreCsv ? null : fieldsCsv, ignoreCsv ? null : methodsCsv, packagesCsv);
        } else {
            outputTransformer = null;
        }

        for (File srg : srgFiles) {
            loadMappings(new BufferedReader(new FileReader(srg)), inputTransformer, outputTransformer, reverse);
        }
    }

    public void loadMappings(File file) throws IOException {
        loadMappings(new FileInputStream(file));
    }

    public void loadMappings(InputStream stream) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
            loadMappings(reader, null, null, false);
        }
    }

    /**
     *
     * @param filename A filename of a .srg/.csrg or an MCP directory of
     * .srg+.csv, local or remote
     * @param reverse Swap input and output mappings
     * @param numericSrgNames When reading mapping directory, load numeric "srg"
     * instead obfuscated names
     * @param inShadeRelocation Apply relocation on mapping input
     * @param outShadeRelocation Apply relocation on mapping output
     * @throws IOException
     */
    public void loadMappings(String filename, boolean reverse, boolean numericSrgNames, String inShadeRelocation, String outShadeRelocation) throws IOException {
        // Optional shade relocation, on input or output names
        MappingTransformer inputTransformer = null;
        MappingTransformer outputTransformer = null;

        if (inShadeRelocation != null) {
            inputTransformer = new MavenShade(inShadeRelocation);
        }

        if (outShadeRelocation != null) {
            outputTransformer = new MavenShade(outShadeRelocation);
        }

        if (new File(filename).isDirectory() || filename.endsWith("/")) {
            // Existing local dir or dir URL

            if (inputTransformer != null || outputTransformer != null) {
                throw new IllegalArgumentException("loadMappings(" + filename + "): shade relocation not supported on directories"); // yet
            }

            loadMappingsDir(filename, reverse, false, numericSrgNames);
        } else {
            // File

            if (numericSrgNames) {
                throw new IllegalArgumentException("loadMappings(" + filename + "): numeric only supported on directories, not files");
            }

            try (BufferedReader reader = new BufferedReader(new FileReader(FileLocator.getFile(filename)))) {
                loadMappings(reader, inputTransformer, outputTransformer, reverse);
            }
        }
    }

    /**
     * Load a mapping given a .csrg file
     *
     * @param reader Mapping file reader
     * @param inputTransformer Transformation to apply on input
     * @param outputTransformer Transformation to apply on output
     * @param reverse Swap input and output mappings (after applying any
     * input/output transformations)
     * @throws IOException
     */
    public void loadMappings(BufferedReader reader, MappingTransformer inputTransformer, MappingTransformer outputTransformer, boolean reverse) throws IOException {
        if (inputTransformer == null) {
            inputTransformer = MavenShade.IDENTITY;
        }
        if (outputTransformer == null) {
            outputTransformer = MavenShade.IDENTITY;
        }

        List lines = new ArrayList<>();
        String line;
        while ((line = reader.readLine()) != null) {
            int commentIndex = line.indexOf('#');
            if (commentIndex != -1) {
                line = line.substring(0, commentIndex);
            }
            if (line.isEmpty()) {
                continue;
            }
            lines.add(line);
        }

        ProgressMeter meter = new ProgressMeter(lines.size() * 2, "Loading mappings... %2.0f%%");

        //Gather class mappings here so that we can support reversing csrg/tsrg.
        final Map clsMap = new HashMap<>();
        final Map prgMap = new HashMap<>();
        for (String l : lines) {
            if (l.endsWith(":")) {
                String[] parts = l.split(" -> ");
                String orig = parts[0].replace('.', '/');
                String obf = parts[1].substring(0, parts[1].length() - 1).replace('.', '/');

                clsMap.put(obf, orig);
                prgMap.put(orig, obf);
            } else if (l.contains(":")) {
                if (!l.startsWith("CL:")) {
                    continue;
                }
                String[] tokens = l.split(" ");
                clsMap.put(tokens[0], tokens[1]);
            } else {
                if (l.startsWith("\t")) {
                    continue;
                }
                String[] tokens = l.split(" ");
                if (tokens.length == 2) {
                    clsMap.put(tokens[0], tokens[1]);
                }
            }
            meter.makeProgress();
        }

        // We use a Remapper so that we don't have to duplicate the logic of remapping method descriptors.
        Remapper reverseMapper = new Remapper() {
            @Override
            public String map(String cls) {
                return clsMap.getOrDefault(cls, cls);
            }
        };

        for (String l : lines) {
            if (l.startsWith("tsrg2")) {
                continue;
            }
            if (l.contains(" -> ")) {
                parseProguardLine(l, inputTransformer, outputTransformer, reverse, reverseMapper, prgMap);
            } else if (l.contains(":")) {
                // standard srg
                parseSrgLine(l, inputTransformer, outputTransformer, reverse);
            } else {
                // better 'compact' srg format
                parseCsrgLine(l, inputTransformer, outputTransformer, reverse, reverseMapper);
            }
            meter.makeProgress();
        }

        currentClass = null;
    }

    private static final Pattern MEMBER_PATTERN = Pattern.compile("(?:\\d+:\\d+:)?(.*?) (.*?) \\-> (.*)");
    private void parseProguardLine(String line, MappingTransformer inputTransformer, MappingTransformer outputTransformer, boolean reverse, Remapper reverseMap, Map prgMap) throws IOException {
        //Tsrg format, identical to Csrg, except the field and method lines start with \t and should use the last class the was parsed.
        if (line.startsWith("    ")) {
            if (this.currentClass == null) {
                throw new IOException("Invalid proguard file, tsrg field/method line before class line: " + line);
            }
            line = line.trim();
        }

        if (line.endsWith(":")) {
            String[] parts = line.split(" -> ");
            String orig = parts[0].replace('.', '/');
            String obf = parts[1].substring(0, parts[1].length() - 1).replace('.', '/');

            String oldClassName = inputTransformer.transformClassName(obf);
            String newClassName = outputTransformer.transformClassName(orig);

            if (oldClassName.endsWith("/")) {
                // Special case: mapping an entire hierarchy of classes
                if (reverse) {
                    packages.put(newClassName, oldClassName.substring(0, oldClassName.length() - 1));
                } else {
                    packages.put(oldClassName.substring(0, oldClassName.length() - 1), newClassName);
                }
            } else {
                if (reverse) {
                    classes.put(newClassName, oldClassName);
                } else {
                    classes.put(oldClassName, newClassName);
                }
                currentClass = obf;
            }
        } else {
            Matcher matcher = MEMBER_PATTERN.matcher(line);
            matcher.find();

            String obfName = matcher.group(3);
            String nameDesc = matcher.group(2);
            if (nameDesc.contains("(")) {
                String desc = ProguardUtil.csrgDesc(prgMap, nameDesc.substring(nameDesc.indexOf('(')), matcher.group(1));
                String newName = nameDesc.substring(0, nameDesc.indexOf('('));

                // System.out.println( lastClass + " " + matcher.group( 3 ) + " " + sig + " " + mojName );
                String oldClassName = inputTransformer.transformClassName(currentClass);
                String oldMethodName = inputTransformer.transformMethodName(currentClass, obfName, desc);
                String oldMethodDescriptor = inputTransformer.transformMethodDescriptor(desc);
                String newMethodName = outputTransformer.transformMethodName(currentClass, newName, desc);

                if (reverse) {
                    String newClassName = reverseMap.map(oldClassName);
                    if (newClassName.equals(oldClassName)) {
                        // throw new IOException("Invalid csrg file line, could not be reversed: " + line);
                    }
                    oldClassName = newClassName;
                    oldMethodDescriptor = reverseMap.mapMethodDesc(oldMethodDescriptor);

                    String temp = newMethodName;
                    newMethodName = oldMethodName;
                    oldMethodName = temp;
                }

                methods.put(oldClassName + "/" + oldMethodName + " " + oldMethodDescriptor, newMethodName);
            } else {
                String desc = ProguardUtil.toJVMType(prgMap, matcher.group(1));

                String oldClassName = inputTransformer.transformClassName(currentClass);
                String oldFieldName = inputTransformer.transformFieldName(currentClass, obfName);
                String oldFieldDescriptor = inputTransformer.transformMethodDescriptor(desc);
                String newFieldName = outputTransformer.transformFieldName(currentClass, nameDesc);

                if (reverse) {
                    String newClassName = reverseMap.map(oldClassName);
                    if (newClassName.equals(oldClassName)) {
                        // throw new IOException("Invalid csrg file line, could not be reversed: " + line);
                    }
                    oldClassName = newClassName;
                    oldFieldDescriptor = reverseMap.mapDesc(oldFieldDescriptor);

                    String temp = newFieldName;
                    newFieldName = oldFieldName;
                    oldFieldName = temp;
                }

                fields.put(oldClassName + "/" + oldFieldName + "/" + oldFieldDescriptor, newFieldName);
            }
        }
    }

    /**
     * Parse a 'csrg' mapping format line and populate the data structures
     */
    private void parseCsrgLine(String line, MappingTransformer inputTransformer, MappingTransformer outputTransformer, boolean reverse, Remapper reverseMap) throws IOException {
        //Tsrg format, identical to Csrg, except the field and method lines start with \t and should use the last class the was parsed.
        if (line.startsWith("\t\t")) {
            // Two tabs means the format is Tsrgv2 with parameters and extra data that isn't needed.
            return;
        }
        if (line.startsWith("\t")) {
            if (this.currentClass == null) {
                throw new IOException("Invalid tsrg file, tsrg field/method line before class line: " + line);
            }
            line = currentClass + " " + line.substring(1);
        }

        String[] tokens = line.split(" ");

        if (tokens.length == 2) {
            String oldClassName = inputTransformer.transformClassName(tokens[0]);
            String newClassName = outputTransformer.transformClassName(tokens[1]);

            if (oldClassName.endsWith("/")) {

                // package names always either 1) suffixed with '/', or 2) equal to '.' to signify default package
                if (!newClassName.equals(".") && !newClassName.endsWith("/")) {
                    newClassName += "/";
                }

                if (!oldClassName.equals(".") && !oldClassName.endsWith("/")) {
                    oldClassName += "/";
                }

                // Special case: mapping an entire hierarchy of classes
                if (reverse) {
                    packages.put(newClassName, oldClassName);
                } else {
                    packages.put(oldClassName, newClassName);
                }
            } else {
                if (reverse) {
                    classes.put(newClassName, oldClassName);
                } else {
                    classes.put(oldClassName, newClassName);
                }
                currentClass = tokens[0];
            }
        } else if (tokens.length == 3) {
            String oldClassName = inputTransformer.transformClassName(tokens[0]);
            String oldFieldName = inputTransformer.transformFieldName(tokens[0], tokens[1]);
            String newFieldName = outputTransformer.transformFieldName(tokens[0], tokens[2]);

            if (reverse) {
                String newClassName = reverseMap.map(oldClassName);
                if (newClassName.equals(oldClassName)) {
                    // throw new IOException("Invalid csrg file line, could not be reversed: " + line);
                }
                oldClassName = newClassName;

                String temp = newFieldName;
                newFieldName = oldFieldName;
                oldFieldName = temp;
            }

            fields.put(oldClassName + "/" + oldFieldName, newFieldName);
        } else if (tokens.length == 4) {
            String oldClassName = inputTransformer.transformClassName(tokens[0]);
            String oldMethodName = inputTransformer.transformMethodName(tokens[0], tokens[1], tokens[2]);
            String oldMethodDescriptor = inputTransformer.transformMethodDescriptor(tokens[2]);
            String newMethodName = outputTransformer.transformMethodName(tokens[0], tokens[3], tokens[2]);

            if (reverse) {
                String newClassName = reverseMap.map(oldClassName);
                if (newClassName.equals(oldClassName)) {
                    // throw new IOException("Invalid csrg file line, could not be reversed: " + line);
                }
                oldClassName = newClassName;
                oldMethodDescriptor = reverseMap.mapMethodDesc(oldMethodDescriptor);

                String temp = newMethodName;
                newMethodName = oldMethodName;
                oldMethodName = temp;
            }

            methods.put(oldClassName + "/" + oldMethodName + " " + oldMethodDescriptor, newMethodName);
        } else {
            throw new IOException("Invalid csrg file line, token count " + tokens.length + " unexpected in " + line);
        }
    }

    /**
     * Parse a standard 'srg' mapping format line and populate the data
     * structures
     */
    private void parseSrgLine(String line, MappingTransformer inputTransformer, MappingTransformer outputTransformer, boolean reverse) throws IOException {
        String[] tokens = line.split(" ");
        String kind = tokens[0];

        if (kind.equals("CL:")) {
            String oldClassName = inputTransformer.transformClassName(tokens[1]);
            String newClassName = outputTransformer.transformClassName(tokens[2]);

            if (reverse) {
                String temp = newClassName;
                newClassName = oldClassName;
                oldClassName = temp;
            }

            if (isExcludedPackage(oldClassName)) {
                SpecialSource.log("Ignored CL: " + oldClassName + " " + newClassName);
                return;
            }

            if (classes.containsKey(oldClassName) && !newClassName.equals(classes.get(oldClassName))) {
                throw new IllegalArgumentException("Duplicate class mapping: " + oldClassName + " -> " + newClassName
                        + " but already mapped to " + classes.get(oldClassName) + " in line=" + line);
            }

            if (oldClassName.endsWith("/*") && newClassName.endsWith("/*")) {
                // extension for remapping class name prefixes
                oldClassName = oldClassName.substring(0, oldClassName.length() - 1);
                newClassName = newClassName.substring(0, newClassName.length() - 1);

                packages.put(oldClassName, newClassName);
            } else {
                classes.put(oldClassName, newClassName);
                currentClass = tokens[0];
            }
        } else if (kind.equals("PK:")) {
            String oldPackageName = inputTransformer.transformClassName(tokens[1]);
            String newPackageName = outputTransformer.transformClassName(tokens[2]);

            if (reverse) {
                String temp = newPackageName;
                newPackageName = oldPackageName;
                oldPackageName = temp;
            }

            if (isExcludedPackage(oldPackageName)) {
                SpecialSource.log("Ignored PK: " + oldPackageName + " -> " + newPackageName);
                return;
            }

            // package names always either 1) suffixed with '/', or 2) equal to '.' to signify default package
            if (!newPackageName.equals(".") && !newPackageName.endsWith("/")) {
                newPackageName += "/";
            }

            if (!oldPackageName.equals(".") && !oldPackageName.endsWith("/")) {
                oldPackageName += "/";
            }

            if (packages.containsKey(oldPackageName) && !newPackageName.equals(packages.get(oldPackageName))) {
                throw new IllegalArgumentException("Duplicate package mapping: " + oldPackageName + " ->" + newPackageName
                        + " but already mapped to " + packages.get(oldPackageName) + " in line=" + line);
            }

            packages.put(oldPackageName, newPackageName);
        } else if (kind.equals("FD:")) {
            String oldFull = tokens[1];
            String newFull = tokens[2];

            // Split the qualified field names into their classes and actual names
            int splitOld = oldFull.lastIndexOf('/');
            int splitNew = newFull.lastIndexOf('/');
            if (splitOld == -1 || splitNew == -1) {
                throw new IllegalArgumentException("Field name is invalid, not fully-qualified: " + oldFull
                        + " -> " + newFull + " in line=" + line);
            }

            String oldClassName = inputTransformer.transformClassName(oldFull.substring(0, splitOld));
            String oldFieldName = inputTransformer.transformFieldName(oldFull.substring(0, splitOld), oldFull.substring(splitOld + 1));
            String newClassName = outputTransformer.transformClassName(newFull.substring(0, splitNew)); // TODO: verify with existing class map? (only used for reverse)
            String newFieldName = outputTransformer.transformFieldName(oldFull.substring(0, splitOld), newFull.substring(splitNew + 1));

            if (reverse) {
                oldClassName = newClassName;

                String temp = newFieldName;
                newFieldName = oldFieldName;
                oldFieldName = temp;
            }

            if (isExcludedPackage(oldClassName)) {
                SpecialSource.log("Ignored FD: " + oldClassName + "/" + oldFieldName + " -> " + newFieldName);
                return;
            }

            String oldEntry = oldClassName + "/" + oldFieldName;
            if (fields.containsKey(oldEntry) && !newFieldName.equals(fields.get(oldEntry))) {
                throw new IllegalArgumentException("Duplicate field mapping: " + oldEntry + " ->" + newFieldName
                        + " but already mapped to " + fields.get(oldEntry) + " in line=" + line);
            }

            fields.put(oldEntry, newFieldName);
        } else if (kind.equals("MD:")) {
            String oldFull = tokens[1];
            String newFull = tokens[3];

            // Split the qualified field names into their classes and actual names TODO: refactor with below
            int splitOld = oldFull.lastIndexOf('/');
            int splitNew = newFull.lastIndexOf('/');
            if (splitOld == -1 || splitNew == -1) {
                throw new IllegalArgumentException("Field name is invalid, not fully-qualified: " + oldFull
                        + " -> " + newFull + " in line=" + line);
            }

            String oldClassName = inputTransformer.transformClassName(oldFull.substring(0, splitOld));
            String oldMethodName = inputTransformer.transformMethodName(oldFull.substring(0, splitOld), oldFull.substring(splitOld + 1), tokens[2]);
            String oldMethodDescriptor = inputTransformer.transformMethodDescriptor(tokens[2]);
            String newClassName = outputTransformer.transformClassName(newFull.substring(0, splitNew)); // TODO: verify with existing class map? (only used for reverse)
            String newMethodName = outputTransformer.transformMethodName(oldFull.substring(0, splitOld), newFull.substring(splitNew + 1), tokens[2]);
            String newMethodDescriptor = outputTransformer.transformMethodDescriptor(tokens[4]); // TODO: verify with existing class map? (only used for reverse)
            // TODO: support isClassIgnored() on reversed method descriptors

            if (reverse) {
                oldClassName = newClassName;
                oldMethodDescriptor = newMethodDescriptor;

                String temp = newMethodName;
                newMethodName = oldMethodName;
                oldMethodName = temp;
            }

            if (isExcludedPackage(oldClassName)) {
                SpecialSource.log("Ignored MD: " + oldClassName + "/" + oldMethodName + " -> " + newMethodName);
                return;
            }

            String oldEntry = oldClassName + "/" + oldMethodName + " " + oldMethodDescriptor;
            if (methods.containsKey(oldEntry) && !newMethodName.equals(methods.get(oldEntry))) {
                throw new IllegalArgumentException("Duplicate method mapping: " + oldEntry + " ->" + newMethodName
                        + " but already mapped to " + methods.get(oldEntry) + " in line=" + line);
            }

            methods.put(oldEntry, newMethodName);
        } else {
            throw new IllegalArgumentException("Unable to parse srg file, unrecognized mapping type in line=" + line);
        }
    }

    public JarMapping(JarComparer oldJar, JarComparer newJar, File logFile, boolean compact) throws IOException {
        this(oldJar, newJar, logFile, compact, false);
    }
    public JarComparer newJar;

    /**
     * Generate a mapping given an original jar and renamed jar
     *
     * @param oldJar Original jar
     * @param newJar Renamed jar
     * @param logfile Optional .srg file to output mappings to
     * @param compact If true, generate .csrg logfile instead of .srg
     * @param full if true, generate duplicates
     * @throws IOException
     */
    public JarMapping(JarComparer oldJar, JarComparer newJar, File logfile, boolean compact, boolean full) throws IOException {
        SpecialSource.validate(oldJar, newJar);

        this.newJar = newJar;

        MappingWriter srgWriter = (compact) ? new CompactSearge(oldJar.jar.getFilename(), newJar.jar.getFilename()) : new Searge(oldJar.jar.getFilename(), newJar.jar.getFilename());


        for (int i = 0; i < oldJar.classes.size(); i++) {
            String oldClass = oldJar.classes.get(i);
            String newClass = newJar.classes.get(i);
            if (full || !oldClass.equals(newClass)) {
                classes.put(oldClass, newClass);
                srgWriter.addClassMap(oldClass, newClass);
            }
        }
        for (int i = 0; i < oldJar.fields.size(); i++) {
            Ownable oldField = oldJar.fields.get(i);
            Ownable newField = newJar.fields.get(i);
            String key = oldField.owner + "/" + oldField.name;
            fields.put(key, newField.name);

            if (full || !oldField.name.equals(newField.name)) {
                srgWriter.addFieldMap(oldField, newField);
            }
        }
        for (int i = 0; i < oldJar.methods.size(); i++) {
            Ownable oldMethod = oldJar.methods.get(i);
            Ownable newMethod = newJar.methods.get(i);
            String key = oldMethod.owner + "/" + oldMethod.name + " " + oldMethod.descriptor;
            methods.put(key, newMethod.name);

            MethodDescriptor methodDescriptorTransformer = new MethodDescriptor(null, classes);
            String oldDescriptor = methodDescriptorTransformer.transform(oldMethod.descriptor);

            if (full || !(oldMethod.name + " " + oldDescriptor).equals(newMethod.name + " " + newMethod.descriptor)) {
                srgWriter.addMethodMap(oldMethod, newMethod);
            }
        }

        try (PrintWriter out = (logfile == null ? new PrintWriter(System.out) : new PrintWriter(logfile))) {
            srgWriter.write(out);
        }
    }

    private static class ProguardUtil {

        private static String csrgDesc(Map data, String args, String ret) {
            String[] parts = args.substring(1, args.length() - 1).split(",");
            StringBuilder desc = new StringBuilder("(");
            for (String part : parts) {
                if (part.isEmpty()) {
                    continue;
                }
                desc.append(toJVMType(data, part));
            }
            desc.append(")");
            desc.append(toJVMType(data, ret));
            return desc.toString();
        }

        private static String toJVMType(Map data, String type) {
            switch (type) {
                case "byte":
                    return "B";
                case "char":
                    return "C";
                case "double":
                    return "D";
                case "float":
                    return "F";
                case "int":
                    return "I";
                case "long":
                    return "J";
                case "short":
                    return "S";
                case "boolean":
                    return "Z";
                case "void":
                    return "V";
                default:
                    if (type.endsWith("[]")) {
                        return "[" + toJVMType(data, type.substring(0, type.length() - 2));
                    }
                    String clazzType = type.replace('.', '/');
                    String mappedType = data.get(clazzType);

                    return "L" + ((mappedType != null) ? mappedType : clazzType) + ";";
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy