diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/FillClassMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/FillClassMappingsCommand.java new file mode 100644 index 000000000..c893a3c5b --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/FillClassMappingsCommand.java @@ -0,0 +1,101 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.ClasspathClassProvider; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.ParentedEntry; +import cuchaz.enigma.utils.Utils; +import org.tinylog.Logger; + +import java.nio.file.Path; +import java.util.List; + +public class FillClassMappingsCommand extends Command { + public static final String NAME = "fill-class-mappings"; + + protected FillClassMappingsCommand() { + super(NAME); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 4; + } + + @Override + public void run(String... args) throws Exception { + Path inJar = getReadablePath(getArg(args, 0, "in-jar", true)); + Path source = getReadablePath(getArg(args, 1, "source", true)); + Path result = getWritablePath(getArg(args, 2, "result", true)); + String resultFormat = getArg(args, 3, "result-format", true); + + run(inJar, source, result, resultFormat); + } + + public static void run(Path jar, Path source, Path result, String resultFormat) throws Exception { + boolean debug = shouldDebug(NAME); + + Logger.info("Reading JAR..."); + Enigma enigma = Enigma.create(); + EnigmaProject project = enigma.openJar(jar, new ClasspathClassProvider(), ProgressListener.none()); + + Logger.info("Reading mappings..."); + MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + EntryTree mappings = readMappings(source, ProgressListener.none(), saveParameters); + project.setMappings(mappings); + + if (debug) { + mappings = new DeltaTrackingTree<>(mappings); + } + + Logger.info("Adding mappings..."); + JarIndex index = project.getJarIndex(); + List rootEntries = mappings.getRootNodes().map(EntryTreeNode::getEntry) + .filter(entry -> entry instanceof ClassEntry) + .map(entry -> (ClassEntry) entry) + .toList(); + for (ClassEntry rootEntry : rootEntries) { + // These entries already have a mapping tree node + recursiveAddMappings(mappings, index, rootEntry, false); + } + + Logger.info("Writing mappings..."); + Utils.delete(result); + MappingCommandsUtil.write(mappings, resultFormat, result, saveParameters); + + if (debug) { + writeDebugDelta((DeltaTrackingTree) mappings, result); + } + } + + private static void recursiveAddMappings(EntryTree mappings, JarIndex index, ClassEntry entry, boolean addMapping) { + EntryTreeNode node = mappings.findNode(entry); + boolean hasMapping = node != null && node.hasValue() && node.getValue().targetName() != null; + + Logger.debug("Entry {} {} a mapping", entry, hasMapping ? "has" : "doesn't have"); + if (!hasMapping && addMapping) { + Logger.debug("Adding mapping for {}", entry); + mappings.insert(entry, EntryMapping.DEFAULT); + } + + List> children = index.getChildrenByClass().get(entry); + for (ParentedEntry child : children) { + if (child instanceof ClassEntry classChild) { + recursiveAddMappings(mappings, index, classChild, addMapping || hasMapping); + } + } + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java b/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java index 02c7d0573..f0d7461d6 100644 --- a/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java @@ -95,6 +95,7 @@ private static void logEnigmaInfo() { register(new MapSpecializedMethodsCommand()); register(new InsertProposedMappingsCommand()); register(new DropInvalidMappingsCommand()); + register(new FillClassMappingsCommand()); } private static final class CommandHelpException extends IllegalArgumentException { diff --git a/enigma-cli/src/test/java/cuchaz/enigma/command/FillClassMappingsCommandTest.java b/enigma-cli/src/test/java/cuchaz/enigma/command/FillClassMappingsCommandTest.java new file mode 100644 index 000000000..4d367a081 --- /dev/null +++ b/enigma-cli/src/test/java/cuchaz/enigma/command/FillClassMappingsCommandTest.java @@ -0,0 +1,114 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.junit.jupiter.api.Test; + +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class FillClassMappingsCommandTest { + private static final Path JAR = Path.of("../enigma/build/test-obf/innerClasses.jar"); + private static final Path MAPPINGS; + + private static final ClassEntry A = new ClassEntry("a"); + private static final MethodEntry A_METHOD = MethodEntry.parse("a", "a", "()V"); + private static final ClassEntry A_ANONYMOUS = new ClassEntry("a$1"); + private static final ClassEntry B = new ClassEntry("b"); + private static final MethodEntry B_METHOD = MethodEntry.parse("b", "a", "(Ld;)V"); + private static final ClassEntry B_ANONYMOUS = new ClassEntry("b$1"); + private static final ClassEntry C = new ClassEntry("c"); + private static final FieldEntry C_FIELD = FieldEntry.parse("c", "a", "c$a"); + private static final ClassEntry C_INNER = new ClassEntry("c$a"); + private static final FieldEntry C_INNER_FIELD = FieldEntry.parse("c$a", "a", "I"); + private static final ClassEntry D = new ClassEntry("d"); + private static final ClassEntry D_INNER = new ClassEntry("d$a"); + private static final ClassEntry E = new ClassEntry("e"); + private static final MethodEntry E_METHOD_1 = MethodEntry.parse("e", "a", "()Ljava/lang/Object;"); + private static final MethodEntry E_METHOD_2 = MethodEntry.parse("e", "a", "()Ljava/lang/String;"); + private static final ClassEntry E_ANONYMOUS = new ClassEntry("e$1"); + private static final ClassEntry F = new ClassEntry("f"); + private static final ClassEntry F_LEVEL_1 = new ClassEntry("f$a"); + private static final FieldEntry F_LEVEL_1_FIELD = FieldEntry.parse("f$a", "a", "I"); + private static final ClassEntry F_LEVEL_2 = new ClassEntry("f$a$a"); + private static final FieldEntry F_LEVEL_2_FIELD = FieldEntry.parse("f$a$a", "a", "I"); + private static final ClassEntry F_LEVEL_3 = new ClassEntry("f$a$a$a"); + private static final FieldEntry F_LEVEL_3_FIELD = FieldEntry.parse("f$a$a$a", "a", "I"); + + @Test + public void test() throws Exception { + Path resultFile = Files.createTempFile("fillClassMappings", ".mappings"); + FillClassMappingsCommand.run(JAR, MAPPINGS, resultFile, MappingFormat.ENIGMA_FILE.name()); + + EntryTree result = MappingFormat.ENIGMA_FILE.read(resultFile, ProgressListener.none(), new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF)); + + assertEquals("A_Anonymous", getName(result, A)); + assertNotNull(result.findNode(A_ANONYMOUS)); + assertNull(getName(result, A_METHOD)); + assertNull(getName(result, A_ANONYMOUS)); + + assertNotNull(result.findNode(B)); + assertNull(getName(result, B)); + assertEquals("foo", getName(result, B_METHOD)); + assertNull(result.findNode(B_ANONYMOUS)); + + assertNotNull(result.findNode(C)); + assertNull(getName(result, C)); + assertNull(getName(result, C_FIELD)); + assertNotNull(result.findNode(C_INNER)); + assertNull(getName(result, C_INNER)); + assertEquals("a", getName(result, C_INNER_FIELD)); + + assertNotNull(result.findNode(D)); + assertNull(getName(result, D)); + assertEquals("Inner", getName(result, D_INNER)); + + assertNotNull(result.findNode(E)); + assertNull(getName(result, E)); + assertNull(getName(result, E_METHOD_1)); + assertEquals("outerMethod", getName(result, E_METHOD_2)); + assertNull(result.findNode(E_ANONYMOUS)); + + assertNotNull(result.findNode(F)); + assertNull(getName(result, F)); + assertNotNull(result.findNode(F_LEVEL_1)); + assertNull(getName(result, F_LEVEL_1)); + assertNull(getName(result, F_LEVEL_1_FIELD)); + assertNotNull(result.findNode(F_LEVEL_2)); + assertEquals("Level2", getName(result, F_LEVEL_2)); + assertNull(getName(result, F_LEVEL_2_FIELD)); + assertNotNull(result.findNode(F_LEVEL_3)); + assertNull(getName(result, F_LEVEL_3)); + assertNull(getName(result, F_LEVEL_3_FIELD)); + } + + private static String getName(EntryTree mappings, Entry entry) { + if (!mappings.contains(entry)) { + return null; + } + + EntryMapping mapping = mappings.get(entry); + return mapping != null ? mapping.targetName() : null; + } + + static { + try { + MAPPINGS = Path.of(FillClassMappingsCommandTest.class.getResource("/fillClassMappings/").toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/enigma-cli/src/test/resources/fillClassMappings/A_Anonymous.mapping b/enigma-cli/src/test/resources/fillClassMappings/A_Anonymous.mapping new file mode 100644 index 000000000..80e6e4339 --- /dev/null +++ b/enigma-cli/src/test/resources/fillClassMappings/A_Anonymous.mapping @@ -0,0 +1 @@ +CLASS a A_Anonymous diff --git a/enigma-cli/src/test/resources/fillClassMappings/b.mapping b/enigma-cli/src/test/resources/fillClassMappings/b.mapping new file mode 100644 index 000000000..548769290 --- /dev/null +++ b/enigma-cli/src/test/resources/fillClassMappings/b.mapping @@ -0,0 +1,2 @@ +CLASS b + METHOD a foo (Ld;)V diff --git a/enigma-cli/src/test/resources/fillClassMappings/c.mapping b/enigma-cli/src/test/resources/fillClassMappings/c.mapping new file mode 100644 index 000000000..c94f75f69 --- /dev/null +++ b/enigma-cli/src/test/resources/fillClassMappings/c.mapping @@ -0,0 +1,3 @@ +CLASS c + CLASS a + FIELD a a I diff --git a/enigma-cli/src/test/resources/fillClassMappings/d.mapping b/enigma-cli/src/test/resources/fillClassMappings/d.mapping new file mode 100644 index 000000000..2ea5cb238 --- /dev/null +++ b/enigma-cli/src/test/resources/fillClassMappings/d.mapping @@ -0,0 +1,2 @@ +CLASS d + CLASS a Inner diff --git a/enigma-cli/src/test/resources/fillClassMappings/e.mapping b/enigma-cli/src/test/resources/fillClassMappings/e.mapping new file mode 100644 index 000000000..dbc528489 --- /dev/null +++ b/enigma-cli/src/test/resources/fillClassMappings/e.mapping @@ -0,0 +1,2 @@ +CLASS e + METHOD a outerMethod ()Ljava/lang/String; diff --git a/enigma-cli/src/test/resources/fillClassMappings/f.mapping b/enigma-cli/src/test/resources/fillClassMappings/f.mapping new file mode 100644 index 000000000..b4a5731ba --- /dev/null +++ b/enigma-cli/src/test/resources/fillClassMappings/f.mapping @@ -0,0 +1,3 @@ +CLASS f + CLASS a + CLASS a Level2 diff --git a/enigma-cli/src/test/resources/tinylog-test.properties b/enigma-cli/src/test/resources/tinylog-test.properties new file mode 100644 index 000000000..26acc077e --- /dev/null +++ b/enigma-cli/src/test/resources/tinylog-test.properties @@ -0,0 +1,5 @@ +writingthread = true + +writerConsole = console +writerConsole.format = {date: HH:mm:ss.SSS} [{level}]: {message} +writerConsole.level = debug diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java index c74fc2aa5..1205875fd 100644 --- a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java @@ -11,10 +11,14 @@ package cuchaz.enigma.inputs.innerClasses; +@SuppressWarnings("Convert2Lambda") public class A_Anonymous { public void foo() { - Runnable runnable = () -> { - // don't care + Runnable runnable = new Runnable() { + @Override + public void run() { + // don't care + } }; runnable.run(); }