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

net.minecraftforge.gradle.tasks.DecompileTask Maven / Gradle / Ivy

There is a newer version: 3.2.0
Show newest version
package net.minecraftforge.gradle.tasks;

import static net.minecraftforge.gradle.common.Constants.EXT_NAME_MC;
import groovy.lang.Closure;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import net.minecraftforge.gradle.common.BaseExtension;
import net.minecraftforge.gradle.common.Constants;
import net.minecraftforge.gradle.delayed.DelayedFile;
import net.minecraftforge.gradle.extrastuff.FFPatcher;
import net.minecraftforge.gradle.extrastuff.FmlCleanup;
import net.minecraftforge.gradle.extrastuff.GLConstantFixer;
import net.minecraftforge.gradle.extrastuff.McpCleanup;
import net.minecraftforge.gradle.patching.ContextualPatch;
import net.minecraftforge.gradle.patching.ContextualPatch.HunkReport;
import net.minecraftforge.gradle.patching.ContextualPatch.PatchReport;
import net.minecraftforge.gradle.patching.ContextualPatch.PatchStatus;
import net.minecraftforge.gradle.tasks.abstractutil.CachedTask;

import org.gradle.api.file.FileCollection;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.JavaExecSpec;

import argo.saj.InvalidSyntaxException;

import com.github.abrarsyed.jastyle.ASFormatter;
import com.github.abrarsyed.jastyle.FileWildcardFilter;
import com.github.abrarsyed.jastyle.OptParser;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;

public class DecompileTask extends CachedTask
{
    @InputFile
    private DelayedFile inJar;

    @InputFile
    private DelayedFile fernFlower;

    private DelayedFile patch;

    @InputFile
    private DelayedFile astyleConfig;

    @OutputFile
    @Cached
    private DelayedFile outJar;

    private HashMap sourceMap   = new HashMap();
    private HashMap resourceMap = new HashMap();
    
    private static final Pattern BEFORE = Pattern.compile("(?m)((case|default).+(?:\\r\\n|\\r|\\n))(?:\\r\\n|\\r|\\n)");
    private static final Pattern AFTER  = Pattern.compile("(?m)(?:\\r\\n|\\r|\\n)((?:\\r\\n|\\r|\\n)[ \\t]+(case|default))");

    /**
     * This method outputs to the cleanSrc
     */
    @TaskAction
    protected void doMCPStuff() throws Throwable
    {
        // define files.
        File temp = new File(getTemporaryDir(), getInJar().getName());

        getLogger().info("Decompiling Jar");
        decompile(getInJar(), getTemporaryDir(), getFernFlower());

        getLogger().info("Loading decompiled jar");
        readJarAndFix(temp);

        getLogger().info("Applying MCP patches");
        if (getPatch().isFile())
        {
            applySingleMcpPatch(getPatch());
        }
        else
        {
            applyPatchDirectory(getPatch());
        }

        getLogger().info("Cleaning source");
        applyMcpCleanup(getAstyleConfig());

        getLogger().info("Saving Jar");
        saveJar(getOutJar());
    }

    private void decompile(final File inJar, final File outJar, final File fernFlower)
    {
        getProject().javaexec(new Closure(this)
        {
            private static final long serialVersionUID = 4608694547855396167L;

            public JavaExecSpec call()
            {
                JavaExecSpec exec = (JavaExecSpec) getDelegate();

                exec.args(
                        fernFlower.getAbsolutePath(),
                        "-din=1",
                        "-rbr=0",
                        "-dgs=1",
                        "-asc=1",
                        "-log=ERROR",
                        inJar.getAbsolutePath(),
                        outJar.getAbsolutePath()
                );

                exec.setMain("-jar");
                exec.setWorkingDir(fernFlower.getParentFile());

                exec.classpath(Constants.getClassPath());
                exec.setStandardOutput(Constants.createLogger(getLogger(), LogLevel.DEBUG));

                exec.setMaxHeapSize("512M");

                return exec;
            }

            public JavaExecSpec call(Object obj)
            {
                return call();
            }
        });
    }

    private void readJarAndFix(final File jar) throws IOException
    {
        // begin reading jar
        final ZipInputStream zin = new ZipInputStream(new FileInputStream(jar));
        ZipEntry entry = null;
        String fileStr;

        BaseExtension exten = (BaseExtension)getProject().getExtensions().getByName(EXT_NAME_MC);
        boolean fixInterfaces = !exten.getVersion().equals("1.7.2");

        while ((entry = zin.getNextEntry()) != null)
        {
            // no META or dirs. wel take care of dirs later.
            if (entry.getName().contains("META-INF"))
            {
                continue;
            }

            // resources or directories.
            if (entry.isDirectory() || !entry.getName().endsWith(".java"))
            {
                resourceMap.put(entry.getName(), ByteStreams.toByteArray(zin));
            }
            else
            {
                // source!
                fileStr = new String(ByteStreams.toByteArray(zin), Charset.defaultCharset());

                // fix
                fileStr = FFPatcher.processFile(new File(entry.getName()).getName(), fileStr, fixInterfaces);

                sourceMap.put(entry.getName(), fileStr);
            }
        }

        zin.close();
    }

    private void applySingleMcpPatch(File patchFile) throws Throwable
    {
        ContextualPatch patch = ContextualPatch.create(Files.toString(patchFile, Charset.defaultCharset()), new ContextProvider(sourceMap));
        printPatchErrors(patch.patch(false));
    }

    private void printPatchErrors(List errors) throws Throwable
    {
        boolean fuzzed = false;
        for (PatchReport report : errors)
        {
            if (!report.getStatus().isSuccess())
            {
                getLogger().log(LogLevel.ERROR, "Patching failed: " + report.getTarget(), report.getFailure());

                for (HunkReport hunk : report.getHunks())
                {
                    if (!hunk.getStatus().isSuccess())
                    {
                        getLogger().error("Hunk " + hunk.getHunkID() + " failed!");
                    }
                }

                throw report.getFailure();
            }
            else if (report.getStatus() == PatchStatus.Fuzzed) // catch fuzzed patches
            {
                getLogger().log(LogLevel.INFO, "Patching fuzzed: " + report.getTarget(), report.getFailure());
                fuzzed = true;

                for (HunkReport hunk : report.getHunks())
                {
                    if (!hunk.getStatus().isSuccess())
                    {
                        getLogger().info("Hunk " + hunk.getHunkID() + " fuzzed " + hunk.getFuzz() + "!");
                    }
                }
            }
            else
            {
                getLogger().info("Patch succeeded: " + report.getTarget());
            }
        }
        if (fuzzed)
            getLogger().lifecycle("Patches Fuzzed!");
    }

    private void applyPatchDirectory(File patchDir) throws Throwable
    {
        Multimap patches = ArrayListMultimap.create();
        for (File f : patchDir.listFiles(new FileWildcardFilter("*.patch")))
        {
            String base = f.getName();
            patches.put(base, f);
            for(File e : patchDir.listFiles(new FileWildcardFilter(base + ".*")))
            {
                patches.put(base, e);
            }
        }

        for (String key : patches.keySet())
        {
            ContextualPatch patch = findPatch(patches.get(key));
            if (patch == null)
            {
                getLogger().lifecycle("Patch not found for set: " + key); //This should never happen, but whatever
            }
            else
            {
                printPatchErrors(patch.patch(false));
            }
        }
    }

    private ContextualPatch findPatch(Collection files) throws Throwable
    {
        ContextualPatch patch = null;
        for (File f : files)
        {
            patch = ContextualPatch.create(Files.toString(f, Charset.defaultCharset()), new ContextProvider(sourceMap));
            List errors = patch.patch(true);
            
            boolean success = true;
            for (PatchReport rep : errors)
            {
                if (!rep.getStatus().isSuccess()) success = false;
            }
            if (success) break;
        }
        return patch;
    }

    private void applyMcpCleanup(File conf) throws IOException, InvalidSyntaxException
    {
        ASFormatter formatter = new ASFormatter();
        OptParser parser = new OptParser(formatter);
        parser.parseOptionFile(conf);

        Reader reader;
        Writer writer;

        GLConstantFixer fixer = new GLConstantFixer();
        ArrayList files = new ArrayList(sourceMap.keySet());
        Collections.sort(files); // Just to make sure we have the same order.. shouldn't matter on anything but lets be careful.

        for (String file : files)
        {
            String text = sourceMap.get(file);

            getLogger().debug("Processing file: " + file);

            getLogger().debug("processing comments");
            text = McpCleanup.stripComments(text);

            getLogger().debug("fixing imports comments");
            text = McpCleanup.fixImports(text);

            getLogger().debug("various other cleanup");
            text = McpCleanup.cleanup(text);

            getLogger().debug("fixing OGL constants");
            text = fixer.fixOGL(text);

            getLogger().debug("formatting source");
            reader = new StringReader(text);
            writer = new StringWriter();
            formatter.format(reader, writer);
            reader.close();
            writer.flush();
            writer.close();
            text = writer.toString();
            
            getLogger().debug("applying FML transformations");
            text = BEFORE.matcher(text).replaceAll("$1");
            text = AFTER.matcher(text).replaceAll("$1");
            text = FmlCleanup.renameClass(text);

            sourceMap.put(file, text);
        }
    }

    private void saveJar(File output) throws IOException
    {
        ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output));

        // write in resources
        for (Map.Entry entry : resourceMap.entrySet())
        {
            zout.putNextEntry(new ZipEntry(entry.getKey()));
            zout.write(entry.getValue());
            zout.closeEntry();
        }

        // write in sources
        for (Map.Entry entry : sourceMap.entrySet())
        {
            zout.putNextEntry(new ZipEntry(entry.getKey()));
            zout.write(entry.getValue().getBytes());
            zout.closeEntry();
        }

        zout.close();
    }

    public HashMap getSourceMap()
    {
        return sourceMap;
    }

    public void setSourceMap(HashMap sourceMap)
    {
        this.sourceMap = sourceMap;
    }

    public File getAstyleConfig()
    {
        return astyleConfig.call();
    }

    public void setAstyleConfig(DelayedFile astyleConfig)
    {
        this.astyleConfig = astyleConfig;
    }

    public File getFernFlower()
    {
        return fernFlower.call();
    }

    public void setFernFlower(DelayedFile fernFlower)
    {
        this.fernFlower = fernFlower;
    }

    public File getInJar()
    {
        return inJar.call();
    }

    public void setInJar(DelayedFile inJar)
    {
        this.inJar = inJar;
    }

    public File getOutJar()
    {
        return outJar.call();
    }

    public void setOutJar(DelayedFile outJar)
    {
        this.outJar = outJar;
    }

    @InputFiles
    public FileCollection getPatches()
    {
         File patches = patch.call();
         if (patches.isDirectory())
             return getProject().fileTree(patches);
         else
             return getProject().files(patches);
    }
    
    public File getPatch()
    {
        return patch.call();
    }

    public void setPatch(DelayedFile patch)
    {
        this.patch = patch;
    }

    public HashMap getResourceMap()
    {
        return resourceMap;
    }

    public void setResourceMap(HashMap resourceMap)
    {
        this.resourceMap = resourceMap;
    }

    /**
     * A private inner class to be used with the MCPPatches only.
     */
    private class ContextProvider implements ContextualPatch.IContextProvider
    {
        private Map fileMap;

        private final int STRIP = 1;

        public ContextProvider(Map fileMap)
        {
            this.fileMap = fileMap;
        }

        private String strip(String target)
        {
            target = target.replace('\\', '/');
            int index = 0;
            for (int x = 0; x < STRIP; x++)
            {
                index = target.indexOf('/', index) + 1;
            }
            return target.substring(index);
        }

        @Override
        public List getData(String target)
        {
            target = strip(target);

            if (fileMap.containsKey(target))
            {
                String[] lines = fileMap.get(target).split("\r\n|\r|\n");
                List ret = new ArrayList();
                for (String line : lines)
                {
                    ret.add(line);
                }
                return ret;
            }

            return null;
        }

        @Override
        public void setData(String target, List data)
        {
            fileMap.put(strip(target), Joiner.on(Constants.NEWLINE).join(data));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy