SpecialSource/src/main/java/net/md_5/specialsource/SpecialSource.java

315 lines
13 KiB
Java
Raw Normal View History

2012-11-27 12:02:36 +00:00
/**
2013-06-11 00:49:15 +00:00
* Copyright (c) 2012, md_5. All rights reserved.
2012-11-27 12:02:36 +00:00
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
2012-12-08 23:15:58 +00:00
* 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.
2012-11-27 12:02:36 +00:00
*/
package net.md_5.specialsource;
import net.md_5.specialsource.util.Pair;
import net.md_5.specialsource.util.FileLocator;
import net.md_5.specialsource.provider.JointProvider;
import net.md_5.specialsource.provider.JarProvider;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
2012-11-27 12:02:36 +00:00
import org.objectweb.asm.ClassReader;
import static java.util.Arrays.asList;
import net.md_5.specialsource.provider.ClassLoaderProvider;
2012-11-27 12:02:36 +00:00
public class SpecialSource {
private static OptionSet options;
private static boolean verbose;
public static boolean kill_source = false;
public static boolean kill_lvt = false;
public static boolean kill_generics = false;
public static String identifier = null;
2012-11-27 12:02:36 +00:00
public static void main(String[] args) throws Exception {
OptionParser parser = new OptionParser() {
{
acceptsAll(asList("?", "help"), "Show the help");
2013-01-24 03:47:15 +00:00
acceptsAll(asList("a", "first-jar"), "First jar with original names, for generating mapping")
.withRequiredArg()
.ofType(String.class);
2013-01-24 03:47:15 +00:00
acceptsAll(asList("b", "second-jar"), "Second jar with renamed names, for generating mapping")
.withRequiredArg()
.ofType(String.class);
acceptsAll(asList("s", "srg-out"), "Mapping file output")
.withRequiredArg()
.ofType(File.class);
2013-01-24 03:47:15 +00:00
acceptsAll(asList("c", "compact"), "Output mapping file in compact format");
acceptsAll(asList("f", "generate-dupes"), "Include unrenamed symbols in mapping file output");
2013-01-24 03:47:15 +00:00
2013-01-22 08:17:48 +00:00
acceptsAll(asList("m", "srg-in"), "Mapping file input")
.withRequiredArg()
.ofType(String.class);
2013-01-22 08:17:48 +00:00
acceptsAll(asList("n", "numeric-srg"), "Use numeric .srg mappings with srg-in dir (num->mcp vs obf->mcp)");
acceptsAll(asList("R", "in-shade-relocation", "shade-relocation"), "Simulate maven-shade-plugin relocation patterns on srg-in input names")
.withRequiredArg();
acceptsAll(asList("out-shade-relocation"), "Simulate maven-shade-plugin relocation patterns on srg-in output names")
.withRequiredArg();
acceptsAll(asList("r", "reverse"), "Reverse input/output names on srg-in");
acceptsAll(asList("i", "in-jar"), "Input jar(s) to remap")
.withRequiredArg()
.ofType(String.class);
acceptsAll(asList("o", "out-jar"), "Output jar to write")
.withRequiredArg()
.ofType(File.class);
acceptsAll(asList("force-redownload"), "Force redownloading remote resources (invalid cache)");
acceptsAll(asList("l", "live"), "Enable runtime inheritance lookup");
Add remapped runtime inheritance provider RemappedRuntimeInheritanceProvider is similar to RuntimeInheritanceProvider, providing access to inheritance information at runtime, but class names are remapped before looking up, and the inheritance is reverse-remapped before returning. This allows the inheritance to be provided from an obfuscated jar, via the cb2obf mappings. For example: java -cp target/SpecialSource-1.2-SNAPSHOT-shaded.jar:mcpc-plus-1.4.7-R0.2-SNAPSHOT-023.jar net.md_5.specialsource.SpecialSource --shade-relocation net.minecraft.server=net.minecraft.server.v1_4_R1 --shade-relocation org.bouncycastle=net.minecraft.v1_4_R1.org.bouncycastle --srg-in ../jars/1.4.7/cb2obf.csrg --in-jar ../IncompatiblePlugin/IncompatiblePlugin-01.jar --out-jar /tmp/sp/out.jar -L -L enables remapped runtime inheritance, in order to use the obfuscated MCPC+ jar classes to lookup the inherited classes for remapping. The same mappings are used as for remapping the plugin (--srg-in and --shade-relocation take effect). The original option, -l, in contrast does not remap on class lookup, and can be used to lookup inheritance from a jar with CraftBukkit mappings, for example: java -cp target/SpecialSource-1.2-SNAPSHOT-shaded.jar:craftbukkit-1.4.7-R0.1.jar net.md_5.specialsource.SpecialSource --shade-relocation net.minecraft.server=net.minecraft.server.v1_4_R1 --shade-relocation org.bouncycastle=net.minecraft.v1_4_R1.org.bouncycastle --srg-in ../jars/1.4.7/cb2obf.csrg --in-jar ../IncompatiblePlugin/IncompatiblePlugin-01.jar --out-jar /tmp/sp/out.jar -l
2013-01-24 04:41:43 +00:00
acceptsAll(asList("L", "live-remapped"), "Enable runtime inheritance lookup through a mapping");
acceptsAll(asList("H", "write-inheritance"), "Write inheritance map to file")
.withRequiredArg()
.ofType(File.class);
2013-01-30 04:31:47 +00:00
acceptsAll(asList("h", "read-inheritance"), "Read inheritance map from file")
.withRequiredArg()
.ofType(String.class);
//acceptsAll(asList("G", "remap-reflect-field"), "Remap reflection calls to getDeclaredField()"); // TODO
acceptsAll(asList("q", "quiet"), "Quiet mode");
acceptsAll(asList("v", "version"), "Displays version information");
acceptsAll(asList("kill-source"), "Removes the \"SourceFile\" attribute");
acceptsAll(asList("kill-lvt"), "Removes the \"LocalVariableTable\" attribute");
acceptsAll(asList("kill-generics"), "Removes the \"LocalVariableTypeTable\" and \"Signature\" attributes");
acceptsAll(asList("d", "identifier"), "Identifier to place on each class that is transformed, by default, none")
.withRequiredArg()
.ofType(String.class);
2013-06-11 00:47:37 +00:00
acceptsAll(asList("e", "excluded-packages"), "A comma seperated list of packages that should not be transformed, even if the srg specifies they should")
.withRequiredArg()
.ofType(String.class);
}
};
try {
options = parser.parse(args);
} catch (OptionException ex) {
System.out.println(ex.getLocalizedMessage());
2013-03-11 01:20:19 +00:00
System.exit(-1);
return;
}
2013-01-22 08:17:48 +00:00
if (options == null || options.has("?")) {
try {
2013-01-22 08:17:48 +00:00
parser.printHelpOn(System.err);
} catch (IOException ex) {
System.out.println(ex.getLocalizedMessage());
}
2013-03-11 01:20:19 +00:00
System.exit(-1);
return;
}
if (options.has("version"))
{
System.out.println("SpecialSource v{something}");
return;
}
2013-01-22 08:17:48 +00:00
JarMapping jarMapping;
verbose = !options.has("quiet");
kill_source = options.has("kill-source");
kill_lvt = options.has("kill-lvt");
kill_generics = options.has("kill-generics");
if (options.has("identifier"))
{
identifier = (String)options.valueOf("identifier");
}
2013-06-11 00:47:37 +00:00
String[] excluded = new String[0];
if (options.has("excluded-packages"))
{
2013-06-11 00:47:37 +00:00
excluded = ((String)options.valueOf("excluded-packages")).split(",");
}
FileLocator.useCache = !options.has("force-redownload");
2013-01-22 08:17:48 +00:00
if (options.has("first-jar") && options.has("second-jar")) {
// Generate mappings from two otherwise-identical jars
log("Reading jars");
Jar jar1 = Jar.init(FileLocator.getFile((String) options.valueOf("first-jar")));
Jar jar2 = Jar.init(FileLocator.getFile((String) options.valueOf("second-jar")));
2013-01-22 08:17:48 +00:00
log("Creating jar compare");
JarComparer visitor1 = new JarComparer(jar1);
JarComparer visitor2 = new JarComparer(jar2);
visit(new Pair<Jar>(jar1, jar2), new Pair<JarComparer>(visitor1, visitor2), new Pair<String>(jar1.getMain(), jar2.getMain()));
2013-01-22 08:17:48 +00:00
jarMapping = new JarMapping(visitor1, visitor2, (File) options.valueOf("srg-out"), options.has("compact"), options.has("generate-dupes"));
2013-06-11 00:47:37 +00:00
for (String pkg : excluded)
{
2013-06-11 00:47:37 +00:00
jarMapping.addExcludedPackage(pkg);
}
2013-01-22 08:17:48 +00:00
} else if (options.has("srg-in")) {
log("Loading mappings");
jarMapping = new JarMapping();
2013-06-11 00:47:37 +00:00
for (String pkg : excluded)
{
2013-06-11 00:47:37 +00:00
jarMapping.addExcludedPackage(pkg);
}
// Loading options
boolean reverse = options.has("reverse");
boolean numeric = options.has("numeric-srg");
String inShadeRelocation = (String) options.valueOf("in-shade-relocation");
String outShadeRelocation = (String) options.valueOf("out-shade-relocation");
// Load each mapping
@SuppressWarnings("unchecked")
List<String> filenames = (List<String>) options.valuesOf("srg-in");
for (String filename : filenames) {
jarMapping.loadMappings(filename, reverse, numeric, inShadeRelocation, outShadeRelocation);
}
2013-01-22 08:17:48 +00:00
} else {
System.err.println("No mappings given, first-jar/second-jar or srg-in required");
parser.printHelpOn(System.err);
2013-03-11 01:20:19 +00:00
System.exit(-1);
2013-01-22 08:17:48 +00:00
return;
}
log(jarMapping.packages.size() + " packages, " + jarMapping.classes.size() + " classes, " + jarMapping.fields.size() + " fields, " + jarMapping.methods.size() + " methods");
JointProvider inheritanceProviders = new JointProvider();
jarMapping.setFallbackInheritanceProvider(inheritanceProviders);
if (options.has("live")) {
inheritanceProviders.add(new ClassLoaderProvider(ClassLoader.getSystemClassLoader()));
}
2013-01-30 04:31:47 +00:00
if (options.has("read-inheritance")) {
InheritanceMap inheritanceMap = new InheritanceMap();
BufferedReader reader = new BufferedReader(new FileReader(FileLocator.getFile((String) options.valueOf("read-inheritance"))));
BiMap<String, String> inverseClassMap = HashBiMap.create(jarMapping.classes).inverse();
inheritanceMap.load(reader, inverseClassMap);
log("Loaded inheritance map for " + inheritanceMap.size() + " classes");
2013-01-30 04:31:47 +00:00
inheritanceProviders.add(inheritanceMap);
}
if (options.has("in-jar")) {
2013-01-22 08:17:48 +00:00
if (!options.has("out-jar")) {
2013-01-24 03:47:15 +00:00
System.err.println("No output jar given, in-jar requires out-jar");
2013-01-22 08:17:48 +00:00
parser.printHelpOn(System.err);
2013-03-11 01:20:19 +00:00
System.exit(-1);
2013-01-22 08:17:48 +00:00
return;
}
@SuppressWarnings("unchecked")
List<String> filenames = (List<String>) options.valuesOf("in-jar");
List<File> files = new ArrayList<File>();
for (String filename : filenames) {
files.add(FileLocator.getFile(filename));
}
Jar jar3 = Jar.init(files);
inheritanceProviders.add(new JarProvider(jar3));
Add remapped runtime inheritance provider RemappedRuntimeInheritanceProvider is similar to RuntimeInheritanceProvider, providing access to inheritance information at runtime, but class names are remapped before looking up, and the inheritance is reverse-remapped before returning. This allows the inheritance to be provided from an obfuscated jar, via the cb2obf mappings. For example: java -cp target/SpecialSource-1.2-SNAPSHOT-shaded.jar:mcpc-plus-1.4.7-R0.2-SNAPSHOT-023.jar net.md_5.specialsource.SpecialSource --shade-relocation net.minecraft.server=net.minecraft.server.v1_4_R1 --shade-relocation org.bouncycastle=net.minecraft.v1_4_R1.org.bouncycastle --srg-in ../jars/1.4.7/cb2obf.csrg --in-jar ../IncompatiblePlugin/IncompatiblePlugin-01.jar --out-jar /tmp/sp/out.jar -L -L enables remapped runtime inheritance, in order to use the obfuscated MCPC+ jar classes to lookup the inherited classes for remapping. The same mappings are used as for remapping the plugin (--srg-in and --shade-relocation take effect). The original option, -l, in contrast does not remap on class lookup, and can be used to lookup inheritance from a jar with CraftBukkit mappings, for example: java -cp target/SpecialSource-1.2-SNAPSHOT-shaded.jar:craftbukkit-1.4.7-R0.1.jar net.md_5.specialsource.SpecialSource --shade-relocation net.minecraft.server=net.minecraft.server.v1_4_R1 --shade-relocation org.bouncycastle=net.minecraft.v1_4_R1.org.bouncycastle --srg-in ../jars/1.4.7/cb2obf.csrg --in-jar ../IncompatiblePlugin/IncompatiblePlugin-01.jar --out-jar /tmp/sp/out.jar -l
2013-01-24 04:41:43 +00:00
log("Remapping final jar");
JarRemapper jarRemapper = new JarRemapper(jarMapping);
jarRemapper.remapJar(jar3, (File) options.valueOf("out-jar"));
}
if (options.has("write-inheritance")) {
InheritanceMap inheritanceMap = new InheritanceMap();
inheritanceMap.generate(inheritanceProviders, jarMapping.classes.values());
PrintWriter printWriter = new PrintWriter((File) options.valueOf("write-inheritance"));
inheritanceMap.save(printWriter);
printWriter.close();
}
2012-11-27 12:02:36 +00:00
}
public static void log(String message) {
if (options != null && !options.has("quiet")) {
System.out.println(message);
}
}
2012-12-08 23:29:04 +00:00
private static void visit(Pair<Jar> jars, Pair<JarComparer> visitors, Pair<String> classes) throws IOException {
2012-11-27 12:02:36 +00:00
JarComparer visitor1 = visitors.first;
JarComparer visitor2 = visitors.second;
ClassReader clazz1 = new ClassReader(jars.first.getClass(classes.first));
ClassReader clazz2 = new ClassReader(jars.second.getClass(classes.second));
clazz1.accept(visitor1, 0);
clazz2.accept(visitor2, 0);
validate(visitor1, visitor2);
while (visitor1.iterDepth < visitor1.classes.size()) {
String className1 = visitor1.classes.get(visitor1.iterDepth);
String className2 = visitor2.classes.get(visitor1.iterDepth);
2012-12-08 23:29:04 +00:00
Pair<String> pair = new Pair<String>(className1, className2);
2012-11-27 12:02:36 +00:00
visitor1.iterDepth++;
visit(jars, visitors, pair);
}
}
public static void validate(JarComparer visitor1, JarComparer visitor2) {
if (visitor1.classes.size() != visitor2.classes.size()) {
throw new IllegalStateException("classes " + visitor1.classes.size() + " != " + visitor2.classes.size());
2012-11-27 12:02:36 +00:00
}
if (visitor1.fields.size() != visitor2.fields.size()) {
throw new IllegalStateException("fields " + visitor1.fields.size() + " != " + visitor2.fields.size());
2012-11-27 12:02:36 +00:00
}
if (visitor1.methods.size() != visitor2.methods.size()) {
throw new IllegalStateException("methods " + visitor1.methods.size() + " != " + visitor2.methods.size());
2012-11-27 12:02:36 +00:00
}
}
public static boolean verbose() {
return verbose;
}
2012-11-27 12:02:36 +00:00
}