/* * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. * Copyright (C) 2013 Minecraft Forge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ package net.minecraftforge.gradle.user; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.gradle.api.DefaultTask; import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.ParallelizableTask; import org.gradle.api.tasks.TaskAction; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import groovy.lang.Closure; import net.md_5.specialsource.Jar; import net.md_5.specialsource.JarMapping; import net.md_5.specialsource.JarRemapper; import net.md_5.specialsource.provider.ClassLoaderProvider; import net.md_5.specialsource.provider.JarProvider; import net.md_5.specialsource.provider.JointProvider; import net.minecraftforge.gradle.common.Constants; import net.minecraftforge.gradle.util.GradleConfigurationException; import net.minecraftforge.gradle.util.mcp.ReobfExceptor; /** * Reobfuscates an arbitrary jar artifact. * * <p> * To reobfuscate other artifacts or to change settings, use this in your build * script. * * <pre> *reobf { * // the jar artifact to reobfuscate * jar { * * // Using non-default srg names * // reobf to notch * useNotchSrg() * // or for Searge names * useSrgSrg() * // or something else * mappings = file('srgs/minecraft.srg') * * // In case you need to modify the classpath * classpath += configurations.provided * * // Use this to add srg files or lines * // You can combine strings and files. * extra 'PK: org/ejml your/pkg/ejml', file('srgs/mappings.srg') * * // You can also use with '+=' and array * extra += ['CL: your/pkg/Original your/pkg/Renamed', file('srgs/mappings2.srg')] * * } * * // Some other artifact using default settings * // the brackets are needed to create it * otherJar {} *} * </pre> * */ @ParallelizableTask public class TaskSingleReobf extends DefaultTask { private Object jar; private FileCollection classpath; // because decomp stuff private Object fieldCsv; private Object methodCsv; private Object exceptorCfg; private Object deobfFile; private Object recompFile; private boolean isDecomp = false; private Object primarySrg; private List<Object> secondarySrgFiles = Lists.newArrayList(); private List<String> extraSrgLines = Lists.newArrayList(); private List<ReobfTransformer> preTransformers = Lists.newArrayList(); private List<ReobfTransformer> postTransformers = Lists.newArrayList(); public TaskSingleReobf() { super(); this.getOutputs().upToDateWhen(Constants.CALL_FALSE); // allways execute period } // Main Functionality // -------------------------------------------- @TaskAction public void doTask() throws IOException { // prepare Srgs File srg = File.createTempFile("reobf-default", ".srg", getTemporaryDir()); File srgLines = File.createTempFile("reobf-extraLines", ".srg", getTemporaryDir()); srg.deleteOnExit(); srgLines.deleteOnExit(); if (isDecomp()) { ReobfExceptor exc = new ReobfExceptor(); exc.deobfJar = getDeobfFile(); exc.toReobfJar = getRecompFile(); exc.excConfig = getExceptorCfg(); exc.fieldCSV = getFieldCsv(); exc.methodCSV = getMethodCsv(); exc.doFirstThings(); exc.buildSrg(getPrimarySrg(), srg); } else { Files.copy(getPrimarySrg(), srg); } // generate extraSrg { if (!srgLines.exists()) { srgLines.getParentFile().mkdirs(); srgLines.createNewFile(); } BufferedWriter writer = Files.newWriter(srgLines, Charsets.UTF_8); for (String line : getExtraSrgLines()) { writer.write(line); writer.newLine(); } writer.flush(); writer.close(); } // prepare jar for reobf File out = getJar(); // we will repalce the file on output File tempIn = File.createTempFile("input", ".jar", getTemporaryDir()); tempIn.deleteOnExit(); Constants.copyFile(out, tempIn); // copy the to-be-output jar to the temporary input location. because output == input // pre-transform List<ReobfTransformer> transformers = getPreTransformers(); if (!transformers.isEmpty()) { File transformed = File.createTempFile("preTransformed", ".jar", getTemporaryDir()); transformed.deleteOnExit(); applyExtraTransformers(tempIn, transformed, transformers); tempIn = transformed; // for later copying } // obfuscate File obfuscated = File.createTempFile("obfuscated", ".jar", getTemporaryDir()); obfuscated.deleteOnExit(); applySpecialSource(tempIn, obfuscated, srg, srgLines, getSecondarySrgFiles()); // post transform transformers = getPostTransformers(); if (!transformers.isEmpty()) { File transformed = File.createTempFile("postTransformed", ".jar", getTemporaryDir()); transformed.deleteOnExit(); applyExtraTransformers(obfuscated, transformed, transformers); obfuscated = transformed; // for later copying } // copy to output Constants.copyFile(obfuscated, out); } private void applySpecialSource(File input, File output, File srg, File extraSrg, FileCollection extraSrgFiles) throws IOException { // load mapping JarMapping mapping = new JarMapping(); mapping.loadMappings(srg); mapping.loadMappings(extraSrg); for (File f : extraSrgFiles) { mapping.loadMappings(f); } // make remapper JarRemapper remapper = new JarRemapper(null, mapping); // load jar Jar inputJar = Jar.init(input); // ensure that inheritance provider is used JointProvider inheritanceProviders = new JointProvider(); inheritanceProviders.add(new JarProvider(inputJar)); if (classpath != null) inheritanceProviders.add(new ClassLoaderProvider(new URLClassLoader(Constants.toUrls(classpath)))); mapping.setFallbackInheritanceProvider(inheritanceProviders); // remap jar remapper.remapJar(inputJar, output); } private void applyExtraTransformers(File inJar, File outJar, List<ReobfTransformer> transformers) throws IOException { ZipFile in = new ZipFile(inJar); final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outJar))); for (ZipEntry e : Collections.list(in.entries())) { if (e.isDirectory()) { out.putNextEntry(e); } else { ZipEntry n = new ZipEntry(e.getName()); n.setTime(e.getTime()); out.putNextEntry(n); byte[] data = ByteStreams.toByteArray(in.getInputStream(e)); // correct source name if (e.getName().endsWith(".class")) { for (ReobfTransformer trans : transformers) { data = trans.transform(data); } } out.write(data); } } out.flush(); out.close(); in.close(); } // Main Jar and classpath // -------------------------------------------- public File getJar() { return getProject().file(jar); } public void setJar(Object jar) { this.jar = jar; } public FileCollection getClasspath() { return classpath; } public void setClasspath(FileCollection classpath) { this.classpath = classpath; } // SRG STUFF // -------------------------------------------- public File getPrimarySrg() { if (primarySrg == null) throw new GradleConfigurationException("Primary reobfuscation for Task '" + getName() + "' isnt set!"); return getProject().file(primarySrg); } public void setPrimarySrg(Object srg) { this.primarySrg = srg; } public void addSecondarySrgFile(Object thing) { secondarySrgFiles.add(thing); } public FileCollection getSecondarySrgFiles() { List<File> files = new ArrayList<File>(secondarySrgFiles.size()); for (Object thing : getProject().files(secondarySrgFiles)) { File f = getProject().file(thing); if (f.isDirectory()) { for (File nested : getProject().fileTree(f)) { if ("srg".equals(Files.getFileExtension(nested.getName()).toLowerCase())) { files.add(nested.getAbsoluteFile()); } } } else if ("srg".equals(Files.getFileExtension(f.getName()).toLowerCase())) { files.add(f.getAbsoluteFile()); } } return getProject().files(files); } public List<String> getExtraSrgLines() { return extraSrgLines; } public void addExtraSrgLine(String srgLine) { this.extraSrgLines.add(srgLine); } public void addExtraSrgLines(String... srgLines) { this.extraSrgLines.addAll(Arrays.asList(srgLines)); } public void addExtraSrgLines(Collection<String> srgLines) { this.extraSrgLines.addAll(srgLines); } // GETTERS AND STUF FOR DECOMP SPECIFIC STUFF // -------------------------------------------- public File getFieldCsv() { return fieldCsv == null ? null : getProject().file(fieldCsv); } public void setFieldCsv(Object fieldCsv) { this.fieldCsv = fieldCsv; } public File getMethodCsv() { return methodCsv == null ? null : getProject().file(methodCsv); } public void setMethodCsv(Object methodCsv) { this.methodCsv = methodCsv; } public File getExceptorCfg() { return exceptorCfg == null ? null : getProject().file(exceptorCfg); } public void setExceptorCfg(Object file) { this.exceptorCfg = file; } public File getDeobfFile() { return deobfFile == null ? null : getProject().file(deobfFile); } public void setDeobfFile(Object deobfFile) { this.deobfFile = deobfFile; } public File getRecompFile() { return recompFile == null ? null : getProject().file(recompFile); } public void setRecompFile(Object recompFile) { this.recompFile = recompFile; } public boolean isDecomp() { return isDecomp; } public void setDecomp(boolean isDecomp) { this.isDecomp = isDecomp; } // EXTRA FANCY TRANSFORMERS // -------------------------------------------- public List<ReobfTransformer> getPostTransformers() { return postTransformers; // Autobots! ROLL OUT! } public void addPostTransformer(ReobfTransformer autobot) { postTransformers.add(autobot); } public void addPostTransformer(Closure<byte[]> decepticon) { postTransformers.add(new ClosureTransformer(decepticon)); } public List<ReobfTransformer> getPreTransformers() { return preTransformers; // Autobots! ROLL OUT! } public void addPreTransformer(ReobfTransformer autobot) { preTransformers.add(autobot); } public void addPreTransformer(Closure<byte[]> decepticon) { preTransformers.add(new ClosureTransformer(decepticon)); } public static class ClosureTransformer implements ReobfTransformer { private static final long serialVersionUID = 1L; private Closure<byte[]> closure; public ClosureTransformer(Closure<byte[]> closure) { super(); this.closure = closure; } @Override public byte[] transform(byte[] data) { return closure.call(data); } } }