Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop Invalid Mappings improvements #468

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import cuchaz.enigma.analysis.index.JarIndex;
import cuchaz.enigma.classprovider.ClasspathClassProvider;
import cuchaz.enigma.translation.mapping.EntryMapping;
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;
Expand Down Expand Up @@ -43,10 +42,9 @@ public void run(String... args) throws Exception {

System.out.println("Reading mappings...");

MappingFormat format = chooseEnigmaFormat(fileMappings);
MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();

EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.none(), saveParameters);
EntryTree<EntryMapping> mappings = readMappings(fileMappings, ProgressListener.none(), saveParameters);
project.setMappings(mappings);

JarIndex idx = project.getJarIndex();
Expand Down
55 changes: 48 additions & 7 deletions enigma-cli/src/main/java/cuchaz/enigma/command/Command.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import com.google.common.io.MoreFiles;

Expand Down Expand Up @@ -41,22 +43,61 @@ protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) th
System.out.println("Reading mappings...");

MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress, saveParameters);
EntryTree<EntryMapping> mappings = readMappings(fileMappings, progress, saveParameters);

project.setMappings(mappings);
}

return project;
}

protected static MappingFormat chooseEnigmaFormat(Path path) {
if (Files.isDirectory(path)) {
return MappingFormat.ENIGMA_DIRECTORY;
} else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) {
return MappingFormat.ENIGMA_ZIP;
protected static EntryTree<EntryMapping> readMappings(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws Exception {
List<Exception> suppressed = new ArrayList<>();

if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) {
return MappingFormat.ENIGMA_ZIP.read(path, progress, saveParameters);
} else {
for (MappingFormat format : MappingFormat.getReadableFormats()) {
try {
return format.read(path, progress, saveParameters);
} catch (Exception e) {
suppressed.add(e);
}
}
}

RuntimeException exception = new RuntimeException("Unable to parse mappings!");

for (Exception suppressedException : suppressed) {
exception.addSuppressed(suppressedException);
}

throw exception;
}

protected static void writeMappings(EntryTree<EntryMapping> mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws Exception {
List<Exception> suppressed = new ArrayList<>();

if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) {
MappingFormat.ENIGMA_ZIP.write(mappings, path, progress, saveParameters);
} else {
return MappingFormat.ENIGMA_FILE;
for (MappingFormat format : MappingFormat.getWritableFormats()) {
try {
format.write(mappings, path, progress, saveParameters);
return;
} catch (Exception e) {
suppressed.add(e);
}
}
}

RuntimeException exception = new RuntimeException("Unable to write mappings!");

for (Exception suppressedException : suppressed) {
exception.addSuppressed(suppressedException);
}

throw exception;
}

protected static File getWritableFile(String path) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package cuchaz.enigma.command;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

import cuchaz.enigma.Enigma;
import cuchaz.enigma.EnigmaProject;
import cuchaz.enigma.ProgressListener;
import cuchaz.enigma.classprovider.ClasspathClassProvider;
import cuchaz.enigma.translation.mapping.EntryMapping;
import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
import cuchaz.enigma.translation.mapping.tree.EntryTree;

public class DropInvalidMappingsCommand extends Command {
public DropInvalidMappingsCommand() {
super("drop-invalid-mappings");
}

@Override
public String getUsage() {
return "<in jar> <mappings in> [<mappings out>]";
}

@Override
public boolean isValidArgument(int length) {
return length == 3;
}

@Override
public void run(String... args) throws Exception {
Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath();
Path fileMappingsIn = getReadablePath(getArg(args, 1, "mappings in", true));

if (fileMappingsIn == null) {
System.out.println("No mappings input specified, skipping.");
return;
}

String mappingsOut = getArg(args, 2, "mappings out", false);
Path fileMappingsOut = mappingsOut != null && !mappingsOut.isEmpty() ? getReadablePath(mappingsOut) : fileMappingsIn;
Enigma enigma = Enigma.create();

System.out.println("Reading JAR...");
EnigmaProject project = enigma.openJar(fileJarIn, new ClasspathClassProvider(), ProgressListener.none());

System.out.println("Reading mappings...");
MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
EntryTree<EntryMapping> mappings = readMappings(fileMappingsIn, ProgressListener.none(), saveParameters);
project.setMappings(mappings);

System.out.println("Dropping invalid mappings...");
project.dropMappings(ProgressListener.none());

System.out.println("Writing mappings...");

if (fileMappingsOut == fileMappingsIn) {
System.out.println("Overwriting input mappings");
Files.walkFileTree(fileMappingsIn, new SimpleFileVisitor<>() {
@Override
public FileVisitResult postVisitDirectory(
Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(
Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
});

Files.deleteIfExists(fileMappingsIn);
}

writeMappings(project.getMapper().getObfToDeobf(), fileMappingsOut, ProgressListener.none(), saveParameters);
}
}
20 changes: 16 additions & 4 deletions enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -110,15 +111,26 @@ public void dropMappings(ProgressListener progress) {
private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
// drop mappings that don't match the jar
MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
MappingsChecker.Dropped droppedBroken = checker.dropBrokenMappings(progress);

Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
Map<Entry<?>, String> droppedBrokenMappings = droppedBroken.getDroppedMappings();

for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
for (Map.Entry<Entry<?>, String> mapping : droppedBrokenMappings.entrySet()) {
System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
}

return droppedMappings.keySet();
MappingsChecker.Dropped droppedEmpty = checker.dropEmptyMappings(progress);

Map<Entry<?>, String> droppedEmptyMappings = droppedEmpty.getDroppedMappings();

for (Map.Entry<Entry<?>, String> mapping : droppedEmptyMappings.entrySet()) {
System.out.println("WARNING: " + mapping.getKey() + " (" + mapping.getValue() + ") was empty. Mapping was dropped.");
}

Collection<Entry<?>> droppedMappings = new HashSet<>();
droppedMappings.addAll(droppedBrokenMappings.keySet());
droppedMappings.addAll(droppedEmptyMappings.keySet());
return droppedMappings;
}

public boolean isRenamable(Entry<?> obfEntry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrate
return Collections.singleton(entry);
}

if (access == null || !access.isPrivate()) {
if (access == null || (!access.isPrivate() && !access.isStatic())) {
Collection<Entry<ClassEntry>> resolvedChildren = resolveChildEntry(classChild, strategy);

if (!resolvedChildren.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import cuchaz.enigma.ProgressListener;
import cuchaz.enigma.analysis.index.JarIndex;
Expand All @@ -34,27 +37,35 @@ public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) {
this.mappings = mappings;
}

public Dropped dropBrokenMappings(ProgressListener progress) {
private Dropped dropMappings(ProgressListener progress, BiConsumer<Dropped, Entry<?>> dropper) {
Dropped dropped = new Dropped();

Collection<Entry<?>> obfEntries = mappings.getAllEntries().filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry).toList();
// HashEntryTree#getAllEntries filters out empty classes
Stream<Entry<?>> allEntries = StreamSupport.stream(mappings.spliterator(), false).map(EntryTreeNode::getEntry);
Collection<Entry<?>> obfEntries = allEntries
.filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry)
.toList();

progress.init(obfEntries.size(), "Checking for dropped mappings");

int steps = 0;

for (Entry<?> entry : obfEntries) {
progress.step(steps++, entry.toString());
tryDropEntry(dropped, entry);
dropper.accept(dropped, entry);
}

dropped.apply(mappings);

return dropped;
}

private void tryDropEntry(Dropped dropped, Entry<?> entry) {
if (shouldDropEntry(entry)) {
public Dropped dropBrokenMappings(ProgressListener progress) {
return dropMappings(progress, this::tryDropBrokenEntry);
}

private void tryDropBrokenEntry(Dropped dropped, Entry<?> entry) {
if (shouldDropBrokenEntry(entry)) {
EntryMapping mapping = mappings.get(entry);

if (mapping != null) {
Expand All @@ -63,16 +74,11 @@ private void tryDropEntry(Dropped dropped, Entry<?> entry) {
}
}

private boolean shouldDropEntry(Entry<?> entry) {
private boolean shouldDropBrokenEntry(Entry<?> entry) {
if (!index.getEntryIndex().hasEntry(entry)) {
return true;
}

if (entry instanceof LocalVariableEntry localVariableEntry) {
// Drop local variables only if the method entry is to be dropped
return shouldDropEntry(localVariableEntry.getParent());
}

Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);

if (resolvedEntries.isEmpty()) {
Expand All @@ -92,6 +98,34 @@ private boolean shouldDropEntry(Entry<?> entry) {
return true;
}

public Dropped dropEmptyMappings(ProgressListener progress) {
return dropMappings(progress, this::tryDropEmptyEntry);
}

private void tryDropEmptyEntry(Dropped dropped, Entry<?> entry) {
if (shouldDropEmptyMapping(entry)) {
EntryMapping mapping = mappings.get(entry);

if (mapping != null) {
dropped.drop(entry, mapping);
}
}
}

private boolean shouldDropEmptyMapping(Entry<?> entry) {
EntryMapping mapping = mappings.get(entry);

if (mapping != null) {
boolean isEmpty = mapping.targetName() == null && mapping.javadoc() == null && mapping.accessModifier() == AccessModifier.UNCHANGED;

if (isEmpty) {
return mappings.getChildren(entry).isEmpty();
}
}

return false;
}

public static class Dropped {
private final Map<Entry<?>, String> droppedMappings = new HashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -66,4 +68,18 @@ public MappingsWriter getWriter() {
public MappingsReader getReader() {
return reader;
}

public static List<MappingFormat> getWritableFormats() {
return Arrays.asList(values())
.stream()
.filter(format -> format.getWriter() != null)
.toList();
}

public static List<MappingFormat> getReadableFormats() {
return Arrays.asList(values())
.stream()
.filter(format -> format.getReader() != null)
.toList();
}
}