diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma')
231 files changed, 0 insertions, 21193 deletions
diff --git a/src/main/java/cuchaz/enigma/ClassProvider.java b/src/main/java/cuchaz/enigma/ClassProvider.java deleted file mode 100644 index 2b91379..0000000 --- a/src/main/java/cuchaz/enigma/ClassProvider.java +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import org.objectweb.asm.tree.ClassNode; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | public interface ClassProvider { | ||
| 8 | @Nullable | ||
| 9 | ClassNode getClassNode(String name); | ||
| 10 | } | ||
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java deleted file mode 100644 index 93b013d..0000000 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ /dev/null | |||
| @@ -1,105 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import cuchaz.enigma.command.*; | ||
| 15 | |||
| 16 | import java.util.LinkedHashMap; | ||
| 17 | import java.util.Locale; | ||
| 18 | import java.util.Map; | ||
| 19 | |||
| 20 | public class CommandMain { | ||
| 21 | |||
| 22 | private static final Map<String, Command> COMMANDS = new LinkedHashMap<>(); | ||
| 23 | |||
| 24 | public static void main(String... args) throws Exception { | ||
| 25 | try { | ||
| 26 | // process the command | ||
| 27 | if (args.length < 1) | ||
| 28 | throw new IllegalArgumentException("Requires a command"); | ||
| 29 | String command = args[0].toLowerCase(Locale.ROOT); | ||
| 30 | |||
| 31 | Command cmd = COMMANDS.get(command); | ||
| 32 | if (cmd == null) | ||
| 33 | throw new IllegalArgumentException("Command not recognized: " + command); | ||
| 34 | |||
| 35 | if (!cmd.isValidArgument(args.length - 1)) { | ||
| 36 | throw new CommandHelpException(cmd); | ||
| 37 | } | ||
| 38 | |||
| 39 | String[] cmdArgs = new String[args.length - 1]; | ||
| 40 | System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); | ||
| 41 | |||
| 42 | try { | ||
| 43 | cmd.run(cmdArgs); | ||
| 44 | } catch (Exception ex) { | ||
| 45 | throw new CommandHelpException(cmd, ex); | ||
| 46 | } | ||
| 47 | } catch (CommandHelpException ex) { | ||
| 48 | System.err.println(ex.getMessage()); | ||
| 49 | System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); | ||
| 50 | System.out.println("Command " + ex.command.name + " has encountered an error! Usage:"); | ||
| 51 | printHelp(ex.command); | ||
| 52 | System.exit(1); | ||
| 53 | } catch (IllegalArgumentException ex) { | ||
| 54 | System.err.println(ex.getMessage()); | ||
| 55 | printHelp(); | ||
| 56 | System.exit(1); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | private static void printHelp() { | ||
| 61 | System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); | ||
| 62 | System.out.println("Usage:"); | ||
| 63 | System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); | ||
| 64 | System.out.println("\twhere <command> is one of:"); | ||
| 65 | |||
| 66 | for (Command command : COMMANDS.values()) { | ||
| 67 | printHelp(command); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | private static void printHelp(Command command) { | ||
| 72 | System.out.println("\t\t" + command.name + " " + command.getUsage()); | ||
| 73 | } | ||
| 74 | |||
| 75 | private static void register(Command command) { | ||
| 76 | Command old = COMMANDS.put(command.name, command); | ||
| 77 | if (old != null) { | ||
| 78 | System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | static { | ||
| 83 | register(new DeobfuscateCommand()); | ||
| 84 | register(new DecompileCommand()); | ||
| 85 | register(new ConvertMappingsCommand()); | ||
| 86 | register(new ComposeMappingsCommand()); | ||
| 87 | register(new InvertMappingsCommand()); | ||
| 88 | register(new CheckMappingsCommand()); | ||
| 89 | register(new MapSpecializedMethodsCommand()); | ||
| 90 | } | ||
| 91 | |||
| 92 | private static final class CommandHelpException extends IllegalArgumentException { | ||
| 93 | |||
| 94 | final Command command; | ||
| 95 | |||
| 96 | CommandHelpException(Command command) { | ||
| 97 | this.command = command; | ||
| 98 | } | ||
| 99 | |||
| 100 | CommandHelpException(Command command, Throwable cause) { | ||
| 101 | super(cause); | ||
| 102 | this.command = command; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/main/java/cuchaz/enigma/Constants.java b/src/main/java/cuchaz/enigma/Constants.java deleted file mode 100644 index 577315f..0000000 --- a/src/main/java/cuchaz/enigma/Constants.java +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | public class Constants { | ||
| 15 | public static final String NAME = "Enigma"; | ||
| 16 | public static final String VERSION = "@VERSION@/Fabric"; | ||
| 17 | public static final String URL = "https://fabricmc.net"; | ||
| 18 | public static final int MiB = 1024 * 1024; // 1 mebibyte | ||
| 19 | public static final int KiB = 1024; // 1 kebibyte | ||
| 20 | } | ||
diff --git a/src/main/java/cuchaz/enigma/Enigma.java b/src/main/java/cuchaz/enigma/Enigma.java deleted file mode 100644 index f5f0649..0000000 --- a/src/main/java/cuchaz/enigma/Enigma.java +++ /dev/null | |||
| @@ -1,121 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import com.google.common.collect.ImmutableListMultimap; | ||
| 16 | import cuchaz.enigma.analysis.ClassCache; | ||
| 17 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 18 | import cuchaz.enigma.api.EnigmaPlugin; | ||
| 19 | import cuchaz.enigma.api.EnigmaPluginContext; | ||
| 20 | import cuchaz.enigma.api.service.EnigmaService; | ||
| 21 | import cuchaz.enigma.api.service.EnigmaServiceFactory; | ||
| 22 | import cuchaz.enigma.api.service.EnigmaServiceType; | ||
| 23 | import cuchaz.enigma.api.service.JarIndexerService; | ||
| 24 | import cuchaz.enigma.utils.Utils; | ||
| 25 | |||
| 26 | import java.io.IOException; | ||
| 27 | import java.nio.file.Path; | ||
| 28 | import java.util.List; | ||
| 29 | import java.util.ServiceLoader; | ||
| 30 | |||
| 31 | public class Enigma { | ||
| 32 | private final EnigmaProfile profile; | ||
| 33 | private final EnigmaServices services; | ||
| 34 | |||
| 35 | private Enigma(EnigmaProfile profile, EnigmaServices services) { | ||
| 36 | this.profile = profile; | ||
| 37 | this.services = services; | ||
| 38 | } | ||
| 39 | |||
| 40 | public static Enigma create() { | ||
| 41 | return new Builder().build(); | ||
| 42 | } | ||
| 43 | |||
| 44 | public static Builder builder() { | ||
| 45 | return new Builder(); | ||
| 46 | } | ||
| 47 | |||
| 48 | public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException { | ||
| 49 | ClassCache classCache = ClassCache.of(path); | ||
| 50 | JarIndex jarIndex = classCache.index(progress); | ||
| 51 | |||
| 52 | services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex)); | ||
| 53 | |||
| 54 | return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path)); | ||
| 55 | } | ||
| 56 | |||
| 57 | public EnigmaProfile getProfile() { | ||
| 58 | return profile; | ||
| 59 | } | ||
| 60 | |||
| 61 | public EnigmaServices getServices() { | ||
| 62 | return services; | ||
| 63 | } | ||
| 64 | |||
| 65 | public static class Builder { | ||
| 66 | private EnigmaProfile profile = EnigmaProfile.EMPTY; | ||
| 67 | private Iterable<EnigmaPlugin> plugins = ServiceLoader.load(EnigmaPlugin.class); | ||
| 68 | |||
| 69 | private Builder() { | ||
| 70 | } | ||
| 71 | |||
| 72 | public Builder setProfile(EnigmaProfile profile) { | ||
| 73 | Preconditions.checkNotNull(profile, "profile cannot be null"); | ||
| 74 | this.profile = profile; | ||
| 75 | return this; | ||
| 76 | } | ||
| 77 | |||
| 78 | public Builder setPlugins(Iterable<EnigmaPlugin> plugins) { | ||
| 79 | Preconditions.checkNotNull(plugins, "plugins cannot be null"); | ||
| 80 | this.plugins = plugins; | ||
| 81 | return this; | ||
| 82 | } | ||
| 83 | |||
| 84 | public Enigma build() { | ||
| 85 | PluginContext pluginContext = new PluginContext(profile); | ||
| 86 | for (EnigmaPlugin plugin : plugins) { | ||
| 87 | plugin.init(pluginContext); | ||
| 88 | } | ||
| 89 | |||
| 90 | EnigmaServices services = pluginContext.buildServices(); | ||
| 91 | return new Enigma(profile, services); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | private static class PluginContext implements EnigmaPluginContext { | ||
| 96 | private final EnigmaProfile profile; | ||
| 97 | |||
| 98 | private final ImmutableListMultimap.Builder<EnigmaServiceType<?>, EnigmaService> services = ImmutableListMultimap.builder(); | ||
| 99 | |||
| 100 | PluginContext(EnigmaProfile profile) { | ||
| 101 | this.profile = profile; | ||
| 102 | } | ||
| 103 | |||
| 104 | @Override | ||
| 105 | public <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory) { | ||
| 106 | List<EnigmaProfile.Service> serviceProfiles = profile.getServiceProfiles(serviceType); | ||
| 107 | |||
| 108 | for (EnigmaProfile.Service serviceProfile : serviceProfiles) { | ||
| 109 | if (serviceProfile.matches(id)) { | ||
| 110 | T service = factory.create(serviceProfile::getArgument); | ||
| 111 | services.put(serviceType, service); | ||
| 112 | break; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | EnigmaServices buildServices() { | ||
| 118 | return new EnigmaServices(services.build()); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | } | ||
diff --git a/src/main/java/cuchaz/enigma/EnigmaProfile.java b/src/main/java/cuchaz/enigma/EnigmaProfile.java deleted file mode 100644 index 09b90f5..0000000 --- a/src/main/java/cuchaz/enigma/EnigmaProfile.java +++ /dev/null | |||
| @@ -1,131 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import com.google.common.collect.ImmutableMap; | ||
| 4 | import com.google.gson.Gson; | ||
| 5 | import com.google.gson.GsonBuilder; | ||
| 6 | import com.google.gson.JsonDeserializationContext; | ||
| 7 | import com.google.gson.JsonDeserializer; | ||
| 8 | import com.google.gson.JsonElement; | ||
| 9 | import com.google.gson.JsonObject; | ||
| 10 | import com.google.gson.JsonParseException; | ||
| 11 | import com.google.gson.annotations.SerializedName; | ||
| 12 | import com.google.gson.reflect.TypeToken; | ||
| 13 | import cuchaz.enigma.api.service.EnigmaServiceType; | ||
| 14 | import cuchaz.enigma.translation.mapping.MappingFileNameFormat; | ||
| 15 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 16 | |||
| 17 | import javax.annotation.Nullable; | ||
| 18 | import java.io.BufferedReader; | ||
| 19 | import java.io.IOException; | ||
| 20 | import java.io.InputStreamReader; | ||
| 21 | import java.io.Reader; | ||
| 22 | import java.lang.reflect.Type; | ||
| 23 | import java.nio.charset.StandardCharsets; | ||
| 24 | import java.nio.file.Files; | ||
| 25 | import java.nio.file.Path; | ||
| 26 | import java.util.Collections; | ||
| 27 | import java.util.List; | ||
| 28 | import java.util.Map; | ||
| 29 | import java.util.Optional; | ||
| 30 | |||
| 31 | public final class EnigmaProfile { | ||
| 32 | public static final EnigmaProfile EMPTY = new EnigmaProfile(new ServiceContainer(ImmutableMap.of())); | ||
| 33 | |||
| 34 | private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); | ||
| 35 | private static final Gson GSON = new GsonBuilder() | ||
| 36 | .registerTypeAdapter(ServiceContainer.class, (JsonDeserializer<ServiceContainer>) EnigmaProfile::loadServiceContainer) | ||
| 37 | .create(); | ||
| 38 | private static final Type SERVICE_LIST_TYPE = new TypeToken<List<Service>>() { | ||
| 39 | }.getType(); | ||
| 40 | |||
| 41 | @SerializedName("services") | ||
| 42 | private final ServiceContainer serviceProfiles; | ||
| 43 | |||
| 44 | @SerializedName("mapping_save_parameters") | ||
| 45 | private final MappingSaveParameters mappingSaveParameters = null; | ||
| 46 | |||
| 47 | private EnigmaProfile(ServiceContainer serviceProfiles) { | ||
| 48 | this.serviceProfiles = serviceProfiles; | ||
| 49 | } | ||
| 50 | |||
| 51 | public static EnigmaProfile read(@Nullable Path file) throws IOException { | ||
| 52 | if (file != null) { | ||
| 53 | try (BufferedReader reader = Files.newBufferedReader(file)) { | ||
| 54 | return EnigmaProfile.parse(reader); | ||
| 55 | } | ||
| 56 | } else { | ||
| 57 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream("/profile.json"), StandardCharsets.UTF_8))) { | ||
| 58 | return EnigmaProfile.parse(reader); | ||
| 59 | } catch (IOException ex) { | ||
| 60 | System.err.println("Failed to load default profile, will use empty profile: " + ex.getMessage()); | ||
| 61 | return EnigmaProfile.EMPTY; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | public static EnigmaProfile parse(Reader reader) { | ||
| 67 | return GSON.fromJson(reader, EnigmaProfile.class); | ||
| 68 | } | ||
| 69 | |||
| 70 | private static ServiceContainer loadServiceContainer(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | ||
| 71 | if (!json.isJsonObject()) { | ||
| 72 | throw new JsonParseException("services must be an Object!"); | ||
| 73 | } | ||
| 74 | |||
| 75 | JsonObject object = json.getAsJsonObject(); | ||
| 76 | |||
| 77 | ImmutableMap.Builder<String, List<Service>> builder = ImmutableMap.builder(); | ||
| 78 | |||
| 79 | for (Map.Entry<String, JsonElement> entry : object.entrySet()) { | ||
| 80 | JsonElement value = entry.getValue(); | ||
| 81 | if (value.isJsonObject()) { | ||
| 82 | builder.put(entry.getKey(), Collections.singletonList(GSON.fromJson(value, Service.class))); | ||
| 83 | } else if (value.isJsonArray()) { | ||
| 84 | builder.put(entry.getKey(), GSON.fromJson(value, SERVICE_LIST_TYPE)); | ||
| 85 | } else { | ||
| 86 | throw new JsonParseException(String.format("Don't know how to convert %s to a list of service!", value)); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | return new ServiceContainer(builder.build()); | ||
| 91 | } | ||
| 92 | |||
| 93 | public List<Service> getServiceProfiles(EnigmaServiceType<?> serviceType) { | ||
| 94 | return serviceProfiles.get(serviceType.key); | ||
| 95 | } | ||
| 96 | |||
| 97 | public MappingSaveParameters getMappingSaveParameters() { | ||
| 98 | //noinspection ConstantConditions | ||
| 99 | return mappingSaveParameters == null ? EnigmaProfile.DEFAULT_MAPPING_SAVE_PARAMETERS : mappingSaveParameters; | ||
| 100 | } | ||
| 101 | |||
| 102 | public static class Service { | ||
| 103 | private final String id; | ||
| 104 | private final Map<String, String> args; | ||
| 105 | |||
| 106 | Service(String id, Map<String, String> args) { | ||
| 107 | this.id = id; | ||
| 108 | this.args = args; | ||
| 109 | } | ||
| 110 | |||
| 111 | public boolean matches(String id) { | ||
| 112 | return this.id.equals(id); | ||
| 113 | } | ||
| 114 | |||
| 115 | public Optional<String> getArgument(String key) { | ||
| 116 | return args != null ? Optional.ofNullable(args.get(key)) : Optional.empty(); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | static final class ServiceContainer { | ||
| 121 | private final Map<String, List<Service>> services; | ||
| 122 | |||
| 123 | ServiceContainer(Map<String, List<Service>> services) { | ||
| 124 | this.services = services; | ||
| 125 | } | ||
| 126 | |||
| 127 | List<Service> get(String key) { | ||
| 128 | return services.getOrDefault(key, Collections.emptyList()); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java deleted file mode 100644 index b345fb3..0000000 --- a/src/main/java/cuchaz/enigma/EnigmaProject.java +++ /dev/null | |||
| @@ -1,276 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import com.google.common.base.Functions; | ||
| 4 | import com.google.common.base.Preconditions; | ||
| 5 | import cuchaz.enigma.analysis.ClassCache; | ||
| 6 | import cuchaz.enigma.analysis.EntryReference; | ||
| 7 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 8 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 9 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | ||
| 10 | import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; | ||
| 11 | import cuchaz.enigma.network.EnigmaServer; | ||
| 12 | import cuchaz.enigma.source.*; | ||
| 13 | import cuchaz.enigma.translation.Translator; | ||
| 14 | import cuchaz.enigma.translation.mapping.*; | ||
| 15 | import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; | ||
| 16 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 21 | import cuchaz.enigma.utils.I18n; | ||
| 22 | |||
| 23 | import cuchaz.enigma.utils.Utils; | ||
| 24 | import org.objectweb.asm.ClassWriter; | ||
| 25 | import org.objectweb.asm.Opcodes; | ||
| 26 | import org.objectweb.asm.tree.ClassNode; | ||
| 27 | |||
| 28 | import java.io.*; | ||
| 29 | import java.nio.file.Files; | ||
| 30 | import java.nio.file.Path; | ||
| 31 | import java.util.Collection; | ||
| 32 | import java.util.Map; | ||
| 33 | import java.util.Objects; | ||
| 34 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 35 | import java.util.jar.JarEntry; | ||
| 36 | import java.util.jar.JarOutputStream; | ||
| 37 | import java.util.stream.Collectors; | ||
| 38 | |||
| 39 | public class EnigmaProject { | ||
| 40 | private final Enigma enigma; | ||
| 41 | |||
| 42 | private final ClassCache classCache; | ||
| 43 | private final JarIndex jarIndex; | ||
| 44 | private final byte[] jarChecksum; | ||
| 45 | |||
| 46 | private EntryRemapper mapper; | ||
| 47 | |||
| 48 | public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) { | ||
| 49 | Preconditions.checkArgument(jarChecksum.length == EnigmaServer.CHECKSUM_SIZE); | ||
| 50 | this.enigma = enigma; | ||
| 51 | this.classCache = classCache; | ||
| 52 | this.jarIndex = jarIndex; | ||
| 53 | this.jarChecksum = jarChecksum; | ||
| 54 | |||
| 55 | this.mapper = EntryRemapper.empty(jarIndex); | ||
| 56 | } | ||
| 57 | |||
| 58 | public void setMappings(EntryTree<EntryMapping> mappings) { | ||
| 59 | if (mappings != null) { | ||
| 60 | mapper = EntryRemapper.mapped(jarIndex, mappings); | ||
| 61 | } else { | ||
| 62 | mapper = EntryRemapper.empty(jarIndex); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | public Enigma getEnigma() { | ||
| 67 | return enigma; | ||
| 68 | } | ||
| 69 | |||
| 70 | public ClassCache getClassCache() { | ||
| 71 | return classCache; | ||
| 72 | } | ||
| 73 | |||
| 74 | public JarIndex getJarIndex() { | ||
| 75 | return jarIndex; | ||
| 76 | } | ||
| 77 | |||
| 78 | public byte[] getJarChecksum() { | ||
| 79 | return jarChecksum; | ||
| 80 | } | ||
| 81 | |||
| 82 | public EntryRemapper getMapper() { | ||
| 83 | return mapper; | ||
| 84 | } | ||
| 85 | |||
| 86 | public void dropMappings(ProgressListener progress) { | ||
| 87 | DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf(); | ||
| 88 | |||
| 89 | Collection<Entry<?>> dropped = dropMappings(mappings, progress); | ||
| 90 | for (Entry<?> entry : dropped) { | ||
| 91 | mappings.trackChange(entry); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) { | ||
| 96 | // drop mappings that don't match the jar | ||
| 97 | MappingsChecker checker = new MappingsChecker(jarIndex, mappings); | ||
| 98 | MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress); | ||
| 99 | |||
| 100 | Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings(); | ||
| 101 | for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) { | ||
| 102 | System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); | ||
| 103 | } | ||
| 104 | |||
| 105 | return droppedMappings.keySet(); | ||
| 106 | } | ||
| 107 | |||
| 108 | public boolean isRenamable(Entry<?> obfEntry) { | ||
| 109 | if (obfEntry instanceof MethodEntry) { | ||
| 110 | // HACKHACK: Object methods are not obfuscated identifiers | ||
| 111 | MethodEntry obfMethodEntry = (MethodEntry) obfEntry; | ||
| 112 | String name = obfMethodEntry.getName(); | ||
| 113 | String sig = obfMethodEntry.getDesc().toString(); | ||
| 114 | if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { | ||
| 115 | return false; | ||
| 116 | } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { | ||
| 117 | return false; | ||
| 118 | } else if (name.equals("finalize") && sig.equals("()V")) { | ||
| 119 | return false; | ||
| 120 | } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { | ||
| 121 | return false; | ||
| 122 | } else if (name.equals("hashCode") && sig.equals("()I")) { | ||
| 123 | return false; | ||
| 124 | } else if (name.equals("notify") && sig.equals("()V")) { | ||
| 125 | return false; | ||
| 126 | } else if (name.equals("notifyAll") && sig.equals("()V")) { | ||
| 127 | return false; | ||
| 128 | } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { | ||
| 129 | return false; | ||
| 130 | } else if (name.equals("wait") && sig.equals("()V")) { | ||
| 131 | return false; | ||
| 132 | } else if (name.equals("wait") && sig.equals("(J)V")) { | ||
| 133 | return false; | ||
| 134 | } else if (name.equals("wait") && sig.equals("(JI)V")) { | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) { | ||
| 138 | return false; | ||
| 139 | } | ||
| 140 | |||
| 141 | return this.jarIndex.getEntryIndex().hasEntry(obfEntry); | ||
| 142 | } | ||
| 143 | |||
| 144 | public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) { | ||
| 145 | return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); | ||
| 146 | } | ||
| 147 | |||
| 148 | public JarExport exportRemappedJar(ProgressListener progress) { | ||
| 149 | Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses(); | ||
| 150 | |||
| 151 | NameProposalService[] nameProposalServices = getEnigma().getServices().get(NameProposalService.TYPE).toArray(new NameProposalService[0]); | ||
| 152 | Translator deobfuscator = nameProposalServices.length == 0 ? mapper.getDeobfuscator() : new ProposingTranslator(mapper, nameProposalServices); | ||
| 153 | |||
| 154 | AtomicInteger count = new AtomicInteger(); | ||
| 155 | progress.init(classEntries.size(), I18n.translate("progress.classes.deobfuscating")); | ||
| 156 | |||
| 157 | Map<String, ClassNode> compiled = classEntries.parallelStream() | ||
| 158 | .map(entry -> { | ||
| 159 | ClassEntry translatedEntry = deobfuscator.translate(entry); | ||
| 160 | progress.step(count.getAndIncrement(), translatedEntry.toString()); | ||
| 161 | |||
| 162 | ClassNode node = classCache.getClassNode(entry.getFullName()); | ||
| 163 | if (node != null) { | ||
| 164 | ClassNode translatedNode = new ClassNode(); | ||
| 165 | node.accept(new TranslationClassVisitor(deobfuscator, Utils.ASM_VERSION, new SourceFixVisitor(Utils.ASM_VERSION, translatedNode, jarIndex))); | ||
| 166 | return translatedNode; | ||
| 167 | } | ||
| 168 | |||
| 169 | return null; | ||
| 170 | }) | ||
| 171 | .filter(Objects::nonNull) | ||
| 172 | .collect(Collectors.toMap(n -> n.name, Functions.identity())); | ||
| 173 | |||
| 174 | return new JarExport(jarIndex, compiled); | ||
| 175 | } | ||
| 176 | |||
| 177 | public static final class JarExport { | ||
| 178 | private final JarIndex jarIndex; | ||
| 179 | private final Map<String, ClassNode> compiled; | ||
| 180 | |||
| 181 | JarExport(JarIndex jarIndex, Map<String, ClassNode> compiled) { | ||
| 182 | this.jarIndex = jarIndex; | ||
| 183 | this.compiled = compiled; | ||
| 184 | } | ||
| 185 | |||
| 186 | public void write(Path path, ProgressListener progress) throws IOException { | ||
| 187 | progress.init(this.compiled.size(), I18n.translate("progress.jar.writing")); | ||
| 188 | |||
| 189 | try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) { | ||
| 190 | AtomicInteger count = new AtomicInteger(); | ||
| 191 | |||
| 192 | for (ClassNode node : this.compiled.values()) { | ||
| 193 | progress.step(count.getAndIncrement(), node.name); | ||
| 194 | |||
| 195 | String entryName = node.name.replace('.', '/') + ".class"; | ||
| 196 | |||
| 197 | ClassWriter writer = new ClassWriter(0); | ||
| 198 | node.accept(writer); | ||
| 199 | |||
| 200 | out.putNextEntry(new JarEntry(entryName)); | ||
| 201 | out.write(writer.toByteArray()); | ||
| 202 | out.closeEntry(); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | public SourceExport decompile(ProgressListener progress, DecompilerService decompilerService) { | ||
| 208 | Collection<ClassNode> classes = this.compiled.values().stream() | ||
| 209 | .filter(classNode -> classNode.name.indexOf('$') == -1) | ||
| 210 | .collect(Collectors.toList()); | ||
| 211 | |||
| 212 | progress.init(classes.size(), I18n.translate("progress.classes.decompiling")); | ||
| 213 | |||
| 214 | //create a common instance outside the loop as mappings shouldn't be changing while this is happening | ||
| 215 | Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false)); | ||
| 216 | |||
| 217 | AtomicInteger count = new AtomicInteger(); | ||
| 218 | |||
| 219 | Collection<ClassSource> decompiled = classes.parallelStream() | ||
| 220 | .map(translatedNode -> { | ||
| 221 | progress.step(count.getAndIncrement(), translatedNode.name); | ||
| 222 | |||
| 223 | String source = decompileClass(translatedNode, decompiler); | ||
| 224 | return new ClassSource(translatedNode.name, source); | ||
| 225 | }) | ||
| 226 | .collect(Collectors.toList()); | ||
| 227 | |||
| 228 | return new SourceExport(decompiled); | ||
| 229 | } | ||
| 230 | |||
| 231 | private String decompileClass(ClassNode translatedNode, Decompiler decompiler) { | ||
| 232 | return decompiler.getSource(translatedNode.name).asString(); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | public static final class SourceExport { | ||
| 237 | private final Collection<ClassSource> decompiled; | ||
| 238 | |||
| 239 | SourceExport(Collection<ClassSource> decompiled) { | ||
| 240 | this.decompiled = decompiled; | ||
| 241 | } | ||
| 242 | |||
| 243 | public void write(Path path, ProgressListener progress) throws IOException { | ||
| 244 | progress.init(decompiled.size(), I18n.translate("progress.sources.writing")); | ||
| 245 | |||
| 246 | int count = 0; | ||
| 247 | for (ClassSource source : decompiled) { | ||
| 248 | progress.step(count++, source.name); | ||
| 249 | |||
| 250 | Path sourcePath = source.resolvePath(path); | ||
| 251 | source.writeTo(sourcePath); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | private static class ClassSource { | ||
| 257 | private final String name; | ||
| 258 | private final String source; | ||
| 259 | |||
| 260 | ClassSource(String name, String source) { | ||
| 261 | this.name = name; | ||
| 262 | this.source = source; | ||
| 263 | } | ||
| 264 | |||
| 265 | void writeTo(Path path) throws IOException { | ||
| 266 | Files.createDirectories(path.getParent()); | ||
| 267 | try (BufferedWriter writer = Files.newBufferedWriter(path)) { | ||
| 268 | writer.write(source); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | Path resolvePath(Path root) { | ||
| 273 | return root.resolve(name.replace('.', '/') + ".java"); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | } | ||
diff --git a/src/main/java/cuchaz/enigma/EnigmaServices.java b/src/main/java/cuchaz/enigma/EnigmaServices.java deleted file mode 100644 index df3b7bb..0000000 --- a/src/main/java/cuchaz/enigma/EnigmaServices.java +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import com.google.common.collect.ImmutableListMultimap; | ||
| 4 | import cuchaz.enigma.api.service.EnigmaService; | ||
| 5 | import cuchaz.enigma.api.service.EnigmaServiceType; | ||
| 6 | |||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | public final class EnigmaServices { | ||
| 10 | private final ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services; | ||
| 11 | |||
| 12 | EnigmaServices(ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services) { | ||
| 13 | this.services = services; | ||
| 14 | } | ||
| 15 | |||
| 16 | @SuppressWarnings("unchecked") | ||
| 17 | public <T extends EnigmaService> List<T> get(EnigmaServiceType<T> type) { | ||
| 18 | return (List<T>) services.get(type); | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java deleted file mode 100644 index 84331cc..0000000 --- a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java +++ /dev/null | |||
| @@ -1,35 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | public class ExceptionIgnorer { | ||
| 15 | |||
| 16 | public static boolean shouldIgnore(Throwable t) { | ||
| 17 | |||
| 18 | // is this that pesky concurrent access bug in the highlight painter system? | ||
| 19 | // (ancient ui code is ancient) | ||
| 20 | if (t instanceof ArrayIndexOutOfBoundsException) { | ||
| 21 | StackTraceElement[] stackTrace = t.getStackTrace(); | ||
| 22 | if (stackTrace.length > 1) { | ||
| 23 | |||
| 24 | // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ? | ||
| 25 | StackTraceElement frame = stackTrace[1]; | ||
| 26 | if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) { | ||
| 27 | return true; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | return false; | ||
| 33 | } | ||
| 34 | |||
| 35 | } | ||
diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java deleted file mode 100644 index 7c87669..0000000 --- a/src/main/java/cuchaz/enigma/Main.java +++ /dev/null | |||
| @@ -1,116 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import cuchaz.enigma.gui.Gui; | ||
| 15 | import cuchaz.enigma.gui.GuiController; | ||
| 16 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 17 | |||
| 18 | import joptsimple.*; | ||
| 19 | |||
| 20 | import java.io.IOException; | ||
| 21 | import java.nio.file.Files; | ||
| 22 | import java.nio.file.Path; | ||
| 23 | import java.nio.file.Paths; | ||
| 24 | |||
| 25 | import com.google.common.io.MoreFiles; | ||
| 26 | |||
| 27 | public class Main { | ||
| 28 | |||
| 29 | public static void main(String[] args) throws IOException { | ||
| 30 | OptionParser parser = new OptionParser(); | ||
| 31 | |||
| 32 | OptionSpec<Path> jar = parser.accepts("jar", "Jar file to open at startup") | ||
| 33 | .withRequiredArg() | ||
| 34 | .withValuesConvertedBy(PathConverter.INSTANCE); | ||
| 35 | |||
| 36 | OptionSpec<Path> mappings = parser.accepts("mappings", "Mappings file to open at startup") | ||
| 37 | .withRequiredArg() | ||
| 38 | .withValuesConvertedBy(PathConverter.INSTANCE); | ||
| 39 | |||
| 40 | OptionSpec<Path> profile = parser.accepts("profile", "Profile json to apply at startup") | ||
| 41 | .withRequiredArg() | ||
| 42 | .withValuesConvertedBy(PathConverter.INSTANCE); | ||
| 43 | |||
| 44 | parser.accepts("help", "Displays help information"); | ||
| 45 | |||
| 46 | try { | ||
| 47 | OptionSet options = parser.parse(args); | ||
| 48 | |||
| 49 | if (options.has("help")) { | ||
| 50 | parser.printHelpOn(System.out); | ||
| 51 | return; | ||
| 52 | } | ||
| 53 | |||
| 54 | EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile)); | ||
| 55 | |||
| 56 | Gui gui = new Gui(parsedProfile); | ||
| 57 | GuiController controller = gui.getController(); | ||
| 58 | |||
| 59 | if (options.has(jar)) { | ||
| 60 | Path jarPath = options.valueOf(jar); | ||
| 61 | controller.openJar(jarPath) | ||
| 62 | .whenComplete((v, t) -> { | ||
| 63 | if (options.has(mappings)) { | ||
| 64 | Path mappingsPath = options.valueOf(mappings); | ||
| 65 | if (Files.isDirectory(mappingsPath)) { | ||
| 66 | controller.openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsPath); | ||
| 67 | } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsPath))) { | ||
| 68 | controller.openMappings(MappingFormat.ENIGMA_ZIP, mappingsPath); | ||
| 69 | } else { | ||
| 70 | controller.openMappings(MappingFormat.ENIGMA_FILE, mappingsPath); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | }); | ||
| 74 | } | ||
| 75 | } catch (OptionException e) { | ||
| 76 | System.out.println("Invalid arguments: " + e.getMessage()); | ||
| 77 | System.out.println(); | ||
| 78 | parser.printHelpOn(System.out); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | public static class PathConverter implements ValueConverter<Path> { | ||
| 83 | public static final ValueConverter<Path> INSTANCE = new PathConverter(); | ||
| 84 | |||
| 85 | PathConverter() { | ||
| 86 | } | ||
| 87 | |||
| 88 | @Override | ||
| 89 | public Path convert(String path) { | ||
| 90 | // expand ~ to the home dir | ||
| 91 | if (path.startsWith("~")) { | ||
| 92 | // get the home dir | ||
| 93 | Path dirHome = Paths.get(System.getProperty("user.home")); | ||
| 94 | |||
| 95 | // is the path just ~/ or is it ~user/ ? | ||
| 96 | if (path.startsWith("~/")) { | ||
| 97 | return dirHome.resolve(path.substring(2)); | ||
| 98 | } else { | ||
| 99 | return dirHome.getParent().resolve(path.substring(1)); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | return Paths.get(path); | ||
| 104 | } | ||
| 105 | |||
| 106 | @Override | ||
| 107 | public Class<? extends Path> valueType() { | ||
| 108 | return Path.class; | ||
| 109 | } | ||
| 110 | |||
| 111 | @Override | ||
| 112 | public String valuePattern() { | ||
| 113 | return "path"; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/src/main/java/cuchaz/enigma/ProgressListener.java b/src/main/java/cuchaz/enigma/ProgressListener.java deleted file mode 100644 index 6da3b81..0000000 --- a/src/main/java/cuchaz/enigma/ProgressListener.java +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | public interface ProgressListener { | ||
| 4 | static ProgressListener none() { | ||
| 5 | return new ProgressListener() { | ||
| 6 | @Override | ||
| 7 | public void init(int totalWork, String title) { | ||
| 8 | } | ||
| 9 | |||
| 10 | @Override | ||
| 11 | public void step(int numDone, String message) { | ||
| 12 | } | ||
| 13 | }; | ||
| 14 | } | ||
| 15 | |||
| 16 | void init(int totalWork, String title); | ||
| 17 | |||
| 18 | void step(int numDone, String message); | ||
| 19 | } | ||
diff --git a/src/main/java/cuchaz/enigma/ProposingTranslator.java b/src/main/java/cuchaz/enigma/ProposingTranslator.java deleted file mode 100644 index 018fbfd..0000000 --- a/src/main/java/cuchaz/enigma/ProposingTranslator.java +++ /dev/null | |||
| @@ -1,53 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 4 | import cuchaz.enigma.translation.Translatable; | ||
| 5 | import cuchaz.enigma.translation.Translator; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 7 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 9 | |||
| 10 | import java.util.Arrays; | ||
| 11 | import java.util.Optional; | ||
| 12 | |||
| 13 | public class ProposingTranslator implements Translator { | ||
| 14 | private final EntryRemapper mapper; | ||
| 15 | private final NameProposalService[] nameProposalServices; | ||
| 16 | |||
| 17 | public ProposingTranslator(EntryRemapper mapper, NameProposalService[] nameProposalServices) { | ||
| 18 | this.mapper = mapper; | ||
| 19 | this.nameProposalServices = nameProposalServices; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | @SuppressWarnings("unchecked") | ||
| 24 | public <T extends Translatable> T translate(T translatable) { | ||
| 25 | if (translatable == null) { | ||
| 26 | return null; | ||
| 27 | } | ||
| 28 | |||
| 29 | T deobfuscated = mapper.deobfuscate(translatable); | ||
| 30 | |||
| 31 | if (translatable instanceof Entry && ((Entry) deobfuscated).getName().equals(((Entry<?>) translatable).getName())) { | ||
| 32 | return mapper.getObfResolver() | ||
| 33 | .resolveEntry((Entry<?>) translatable, ResolutionStrategy.RESOLVE_ROOT) | ||
| 34 | .stream() | ||
| 35 | .map(this::proposeName) | ||
| 36 | .filter(Optional::isPresent) | ||
| 37 | .map(Optional::get) | ||
| 38 | .findFirst() | ||
| 39 | .map(newName -> (T) ((Entry) deobfuscated).withName(newName)) | ||
| 40 | .orElse(deobfuscated); | ||
| 41 | } | ||
| 42 | |||
| 43 | return deobfuscated; | ||
| 44 | } | ||
| 45 | |||
| 46 | private Optional<String> proposeName(Entry<?> entry) { | ||
| 47 | return Arrays.stream(nameProposalServices) | ||
| 48 | .map(service -> service.proposeName(entry, mapper)) | ||
| 49 | .filter(Optional::isPresent) | ||
| 50 | .map(Optional::get) | ||
| 51 | .findFirst(); | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java deleted file mode 100644 index 82ca669..0000000 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ /dev/null | |||
| @@ -1,43 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 15 | |||
| 16 | import java.lang.reflect.Modifier; | ||
| 17 | |||
| 18 | public enum Access { | ||
| 19 | |||
| 20 | PUBLIC, PROTECTED, PACKAGE, PRIVATE; | ||
| 21 | |||
| 22 | public static Access get(AccessFlags flags) { | ||
| 23 | return get(flags.getFlags()); | ||
| 24 | } | ||
| 25 | |||
| 26 | public static Access get(int modifiers) { | ||
| 27 | boolean isPublic = Modifier.isPublic(modifiers); | ||
| 28 | boolean isProtected = Modifier.isProtected(modifiers); | ||
| 29 | boolean isPrivate = Modifier.isPrivate(modifiers); | ||
| 30 | |||
| 31 | if (isPublic && !isProtected && !isPrivate) { | ||
| 32 | return PUBLIC; | ||
| 33 | } else if (!isPublic && isProtected && !isPrivate) { | ||
| 34 | return PROTECTED; | ||
| 35 | } else if (!isPublic && !isProtected && isPrivate) { | ||
| 36 | return PRIVATE; | ||
| 37 | } else if (!isPublic && !isProtected && !isPrivate) { | ||
| 38 | return PACKAGE; | ||
| 39 | } | ||
| 40 | // assume public by default | ||
| 41 | return PUBLIC; | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java deleted file mode 100644 index dc3f553..0000000 --- a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java +++ /dev/null | |||
| @@ -1,157 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import cuchaz.enigma.api.EnigmaPlugin; | ||
| 4 | import cuchaz.enigma.api.EnigmaPluginContext; | ||
| 5 | import cuchaz.enigma.api.service.JarIndexerService; | ||
| 6 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 7 | import cuchaz.enigma.source.DecompilerService; | ||
| 8 | import cuchaz.enigma.source.Decompilers; | ||
| 9 | import cuchaz.enigma.source.procyon.ProcyonDecompiler; | ||
| 10 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 14 | import cuchaz.enigma.utils.Pair; | ||
| 15 | import cuchaz.enigma.utils.Utils; | ||
| 16 | import org.objectweb.asm.ClassReader; | ||
| 17 | import org.objectweb.asm.ClassVisitor; | ||
| 18 | import org.objectweb.asm.FieldVisitor; | ||
| 19 | import org.objectweb.asm.MethodVisitor; | ||
| 20 | import org.objectweb.asm.Opcodes; | ||
| 21 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 22 | import org.objectweb.asm.tree.FieldInsnNode; | ||
| 23 | import org.objectweb.asm.tree.InsnList; | ||
| 24 | import org.objectweb.asm.tree.LdcInsnNode; | ||
| 25 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 26 | import org.objectweb.asm.tree.MethodNode; | ||
| 27 | import org.objectweb.asm.tree.analysis.Analyzer; | ||
| 28 | import org.objectweb.asm.tree.analysis.Frame; | ||
| 29 | import org.objectweb.asm.tree.analysis.SourceInterpreter; | ||
| 30 | import org.objectweb.asm.tree.analysis.SourceValue; | ||
| 31 | |||
| 32 | import java.util.ArrayList; | ||
| 33 | import java.util.HashMap; | ||
| 34 | import java.util.HashSet; | ||
| 35 | import java.util.List; | ||
| 36 | import java.util.Map; | ||
| 37 | import java.util.Optional; | ||
| 38 | import java.util.Set; | ||
| 39 | |||
| 40 | public final class BuiltinPlugin implements EnigmaPlugin { | ||
| 41 | |||
| 42 | public BuiltinPlugin() { | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public void init(EnigmaPluginContext ctx) { | ||
| 47 | registerEnumNamingService(ctx); | ||
| 48 | registerDecompilerServices(ctx); | ||
| 49 | } | ||
| 50 | |||
| 51 | private void registerEnumNamingService(EnigmaPluginContext ctx) { | ||
| 52 | final Map<Entry<?>, String> names = new HashMap<>(); | ||
| 53 | final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); | ||
| 54 | |||
| 55 | ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES)); | ||
| 56 | ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); | ||
| 57 | } | ||
| 58 | |||
| 59 | private void registerDecompilerServices(EnigmaPluginContext ctx) { | ||
| 60 | ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); | ||
| 61 | ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR); | ||
| 62 | } | ||
| 63 | |||
| 64 | private static final class EnumFieldNameFindingVisitor extends ClassVisitor { | ||
| 65 | |||
| 66 | private ClassEntry clazz; | ||
| 67 | private String className; | ||
| 68 | private final Map<Entry<?>, String> mappings; | ||
| 69 | private final Set<Pair<String, String>> enumFields = new HashSet<>(); | ||
| 70 | private final List<MethodNode> classInits = new ArrayList<>(); | ||
| 71 | |||
| 72 | EnumFieldNameFindingVisitor(Map<Entry<?>, String> mappings) { | ||
| 73 | super(Utils.ASM_VERSION); | ||
| 74 | this.mappings = mappings; | ||
| 75 | } | ||
| 76 | |||
| 77 | @Override | ||
| 78 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 79 | super.visit(version, access, name, signature, superName, interfaces); | ||
| 80 | this.className = name; | ||
| 81 | this.clazz = new ClassEntry(name); | ||
| 82 | this.enumFields.clear(); | ||
| 83 | this.classInits.clear(); | ||
| 84 | } | ||
| 85 | |||
| 86 | @Override | ||
| 87 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { | ||
| 88 | if ((access & Opcodes.ACC_ENUM) != 0) { | ||
| 89 | if (!enumFields.add(new Pair<>(name, descriptor))) { | ||
| 90 | throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | return super.visitField(access, name, descriptor, signature, value); | ||
| 94 | } | ||
| 95 | |||
| 96 | @Override | ||
| 97 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { | ||
| 98 | if ("<clinit>".equals(name)) { | ||
| 99 | MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions); | ||
| 100 | classInits.add(node); | ||
| 101 | return node; | ||
| 102 | } | ||
| 103 | return super.visitMethod(access, name, descriptor, signature, exceptions); | ||
| 104 | } | ||
| 105 | |||
| 106 | @Override | ||
| 107 | public void visitEnd() { | ||
| 108 | super.visitEnd(); | ||
| 109 | try { | ||
| 110 | collectResults(); | ||
| 111 | } catch (Exception ex) { | ||
| 112 | throw new RuntimeException(ex); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | private void collectResults() throws Exception { | ||
| 117 | String owner = className; | ||
| 118 | Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter()); | ||
| 119 | |||
| 120 | for (MethodNode mn : classInits) { | ||
| 121 | Frame<SourceValue>[] frames = analyzer.analyze(className, mn); | ||
| 122 | |||
| 123 | InsnList instrs = mn.instructions; | ||
| 124 | for (int i = 1; i < instrs.size(); i++) { | ||
| 125 | AbstractInsnNode instr1 = instrs.get(i - 1); | ||
| 126 | AbstractInsnNode instr2 = instrs.get(i); | ||
| 127 | String s = null; | ||
| 128 | |||
| 129 | if (instr2.getOpcode() == Opcodes.PUTSTATIC | ||
| 130 | && ((FieldInsnNode) instr2).owner.equals(owner) | ||
| 131 | && enumFields.contains(new Pair<>(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) | ||
| 132 | && instr1.getOpcode() == Opcodes.INVOKESPECIAL | ||
| 133 | && "<init>".equals(((MethodInsnNode) instr1).name)) { | ||
| 134 | |||
| 135 | for (int j = 0; j < frames[i - 1].getStackSize(); j++) { | ||
| 136 | SourceValue sv = frames[i - 1].getStack(j); | ||
| 137 | for (AbstractInsnNode ci : sv.insns) { | ||
| 138 | if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) { | ||
| 139 | //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) { | ||
| 140 | if (s == null) { | ||
| 141 | s = (String) (((LdcInsnNode) ci).cst); | ||
| 142 | // stringsFound++; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | if (s != null) { | ||
| 150 | mappings.put(new FieldEntry(clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s); | ||
| 151 | } | ||
| 152 | // report otherwise? | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/src/main/java/cuchaz/enigma/analysis/ClassCache.java deleted file mode 100644 index f694bf3..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassCache.java +++ /dev/null | |||
| @@ -1,127 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import com.google.common.cache.Cache; | ||
| 4 | import com.google.common.cache.CacheBuilder; | ||
| 5 | import com.google.common.collect.ImmutableSet; | ||
| 6 | import cuchaz.enigma.ClassProvider; | ||
| 7 | import cuchaz.enigma.ProgressListener; | ||
| 8 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 9 | import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; | ||
| 10 | import cuchaz.enigma.utils.Utils; | ||
| 11 | import org.objectweb.asm.ClassReader; | ||
| 12 | import org.objectweb.asm.ClassVisitor; | ||
| 13 | import org.objectweb.asm.Opcodes; | ||
| 14 | import org.objectweb.asm.tree.ClassNode; | ||
| 15 | |||
| 16 | import javax.annotation.Nullable; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.nio.file.FileSystem; | ||
| 19 | import java.nio.file.FileSystems; | ||
| 20 | import java.nio.file.Files; | ||
| 21 | import java.nio.file.Path; | ||
| 22 | import java.util.concurrent.ExecutionException; | ||
| 23 | import java.util.concurrent.TimeUnit; | ||
| 24 | import java.util.function.Supplier; | ||
| 25 | |||
| 26 | public final class ClassCache implements AutoCloseable, ClassProvider { | ||
| 27 | private final FileSystem fileSystem; | ||
| 28 | private final ImmutableSet<String> classNames; | ||
| 29 | |||
| 30 | private final Cache<String, ClassNode> nodeCache = CacheBuilder.newBuilder() | ||
| 31 | .maximumSize(128) | ||
| 32 | .expireAfterAccess(1, TimeUnit.MINUTES) | ||
| 33 | .build(); | ||
| 34 | |||
| 35 | private ClassCache(FileSystem fileSystem, ImmutableSet<String> classNames) { | ||
| 36 | this.fileSystem = fileSystem; | ||
| 37 | this.classNames = classNames; | ||
| 38 | } | ||
| 39 | |||
| 40 | public static ClassCache of(Path jarPath) throws IOException { | ||
| 41 | FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); | ||
| 42 | ImmutableSet<String> classNames = collectClassNames(fileSystem); | ||
| 43 | |||
| 44 | return new ClassCache(fileSystem, classNames); | ||
| 45 | } | ||
| 46 | |||
| 47 | private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException { | ||
| 48 | ImmutableSet.Builder<String> classNames = ImmutableSet.builder(); | ||
| 49 | for (Path root : fileSystem.getRootDirectories()) { | ||
| 50 | Files.walk(root).map(Path::toString) | ||
| 51 | .forEach(path -> { | ||
| 52 | if (path.endsWith(".class")) { | ||
| 53 | String name = path.substring(1, path.length() - ".class".length()); | ||
| 54 | classNames.add(name); | ||
| 55 | } | ||
| 56 | }); | ||
| 57 | } | ||
| 58 | |||
| 59 | return classNames.build(); | ||
| 60 | } | ||
| 61 | |||
| 62 | @Nullable | ||
| 63 | @Override | ||
| 64 | public ClassNode getClassNode(String name) { | ||
| 65 | if (!classNames.contains(name)) { | ||
| 66 | return null; | ||
| 67 | } | ||
| 68 | |||
| 69 | try { | ||
| 70 | return nodeCache.get(name, () -> parseNode(name)); | ||
| 71 | } catch (ExecutionException e) { | ||
| 72 | throw new RuntimeException(e); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | private ClassNode parseNode(String name) throws IOException { | ||
| 77 | ClassReader reader = getReader(name); | ||
| 78 | |||
| 79 | ClassNode node = new ClassNode(); | ||
| 80 | |||
| 81 | LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Utils.ASM_VERSION, node); | ||
| 82 | reader.accept(visitor, 0); | ||
| 83 | |||
| 84 | return node; | ||
| 85 | } | ||
| 86 | |||
| 87 | private ClassReader getReader(String name) throws IOException { | ||
| 88 | Path path = fileSystem.getPath(name + ".class"); | ||
| 89 | byte[] bytes = Files.readAllBytes(path); | ||
| 90 | return new ClassReader(bytes); | ||
| 91 | } | ||
| 92 | |||
| 93 | public int getClassCount() { | ||
| 94 | return classNames.size(); | ||
| 95 | } | ||
| 96 | |||
| 97 | public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) { | ||
| 98 | for (String className : classNames) { | ||
| 99 | ClassVisitor visitor = visitorSupplier.get(); | ||
| 100 | |||
| 101 | ClassNode cached = nodeCache.getIfPresent(className); | ||
| 102 | if (cached != null) { | ||
| 103 | cached.accept(visitor); | ||
| 104 | continue; | ||
| 105 | } | ||
| 106 | |||
| 107 | try { | ||
| 108 | ClassReader reader = getReader(className); | ||
| 109 | reader.accept(visitor, readFlags); | ||
| 110 | } catch (IOException e) { | ||
| 111 | System.out.println("Failed to visit class " + className); | ||
| 112 | e.printStackTrace(); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | @Override | ||
| 118 | public void close() throws IOException { | ||
| 119 | this.fileSystem.close(); | ||
| 120 | } | ||
| 121 | |||
| 122 | public JarIndex index(ProgressListener progress) { | ||
| 123 | JarIndex index = JarIndex.empty(); | ||
| 124 | index.indexJar(this, progress); | ||
| 125 | return index; | ||
| 126 | } | ||
| 127 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java deleted file mode 100644 index 0fc44ca..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ /dev/null | |||
| @@ -1,72 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 16 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 17 | import cuchaz.enigma.translation.Translator; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 20 | |||
| 21 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 22 | import java.util.Collection; | ||
| 23 | import java.util.List; | ||
| 24 | |||
| 25 | public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { | ||
| 26 | private final Translator translator; | ||
| 27 | private final ClassEntry entry; | ||
| 28 | |||
| 29 | public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) { | ||
| 30 | this.translator = translator; | ||
| 31 | this.entry = entry; | ||
| 32 | } | ||
| 33 | |||
| 34 | public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { | ||
| 35 | // is this the node? | ||
| 36 | if (node.entry.equals(entry.getParent())) { | ||
| 37 | return node; | ||
| 38 | } | ||
| 39 | |||
| 40 | // recurse | ||
| 41 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 42 | ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry); | ||
| 43 | if (foundNode != null) { | ||
| 44 | return foundNode; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | return null; | ||
| 48 | } | ||
| 49 | |||
| 50 | public ClassEntry getClassEntry() { | ||
| 51 | return this.entry; | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public String toString() { | ||
| 56 | return translator.translate(entry).toString(); | ||
| 57 | } | ||
| 58 | |||
| 59 | public void load(JarIndex index) { | ||
| 60 | // get all method implementations | ||
| 61 | List<ClassImplementationsTreeNode> nodes = Lists.newArrayList(); | ||
| 62 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | ||
| 63 | |||
| 64 | Collection<ClassEntry> inheritors = inheritanceIndex.getChildren(entry); | ||
| 65 | for (ClassEntry inheritor : inheritors) { | ||
| 66 | nodes.add(new ClassImplementationsTreeNode(translator, inheritor)); | ||
| 67 | } | ||
| 68 | |||
| 69 | // add them to this node | ||
| 70 | nodes.forEach(this::add); | ||
| 71 | } | ||
| 72 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java deleted file mode 100644 index 7904c5f..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ /dev/null | |||
| @@ -1,72 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 16 | import cuchaz.enigma.translation.Translator; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 18 | |||
| 19 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 20 | import java.util.List; | ||
| 21 | |||
| 22 | public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { | ||
| 23 | private final Translator translator; | ||
| 24 | private final ClassEntry obfClassEntry; | ||
| 25 | |||
| 26 | public ClassInheritanceTreeNode(Translator translator, String obfClassName) { | ||
| 27 | this.translator = translator; | ||
| 28 | this.obfClassEntry = new ClassEntry(obfClassName); | ||
| 29 | } | ||
| 30 | |||
| 31 | public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { | ||
| 32 | // is this the node? | ||
| 33 | if (node.getObfClassName().equals(entry.getFullName())) { | ||
| 34 | return node; | ||
| 35 | } | ||
| 36 | |||
| 37 | // recurse | ||
| 38 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 39 | ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry); | ||
| 40 | if (foundNode != null) { | ||
| 41 | return foundNode; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | return null; | ||
| 45 | } | ||
| 46 | |||
| 47 | public String getObfClassName() { | ||
| 48 | return this.obfClassEntry.getFullName(); | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public String toString() { | ||
| 53 | return translator.translate(obfClassEntry).getFullName(); | ||
| 54 | } | ||
| 55 | |||
| 56 | public void load(InheritanceIndex ancestries, boolean recurse) { | ||
| 57 | // get all the child nodes | ||
| 58 | List<ClassInheritanceTreeNode> nodes = Lists.newArrayList(); | ||
| 59 | for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) { | ||
| 60 | nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName())); | ||
| 61 | } | ||
| 62 | |||
| 63 | // add them to this node | ||
| 64 | nodes.forEach(this::add); | ||
| 65 | |||
| 66 | if (recurse) { | ||
| 67 | for (ClassInheritanceTreeNode node : nodes) { | ||
| 68 | node.load(ancestries, true); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java deleted file mode 100644 index 90d8a6c..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java +++ /dev/null | |||
| @@ -1,94 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import com.google.common.collect.Sets; | ||
| 15 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 16 | import cuchaz.enigma.analysis.index.ReferenceIndex; | ||
| 17 | import cuchaz.enigma.translation.Translator; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 21 | |||
| 22 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 23 | import javax.swing.tree.TreeNode; | ||
| 24 | import java.util.Set; | ||
| 25 | |||
| 26 | public class ClassReferenceTreeNode extends DefaultMutableTreeNode | ||
| 27 | implements ReferenceTreeNode<ClassEntry, MethodDefEntry> { | ||
| 28 | |||
| 29 | private Translator deobfuscatingTranslator; | ||
| 30 | private ClassEntry entry; | ||
| 31 | private EntryReference<ClassEntry, MethodDefEntry> reference; | ||
| 32 | |||
| 33 | public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { | ||
| 34 | this.deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 35 | this.entry = entry; | ||
| 36 | this.reference = null; | ||
| 37 | } | ||
| 38 | |||
| 39 | public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<ClassEntry, MethodDefEntry> reference) { | ||
| 40 | this.deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 41 | this.entry = reference.entry; | ||
| 42 | this.reference = reference; | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public ClassEntry getEntry() { | ||
| 47 | return this.entry; | ||
| 48 | } | ||
| 49 | |||
| 50 | @Override | ||
| 51 | public EntryReference<ClassEntry, MethodDefEntry> getReference() { | ||
| 52 | return this.reference; | ||
| 53 | } | ||
| 54 | |||
| 55 | @Override | ||
| 56 | public String toString() { | ||
| 57 | if (this.reference != null) { | ||
| 58 | return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context)); | ||
| 59 | } | ||
| 60 | return this.deobfuscatingTranslator.translate(this.entry).getFullName(); | ||
| 61 | } | ||
| 62 | |||
| 63 | public void load(JarIndex index, boolean recurse) { | ||
| 64 | ReferenceIndex referenceIndex = index.getReferenceIndex(); | ||
| 65 | |||
| 66 | // get all the child nodes | ||
| 67 | for (EntryReference<ClassEntry, MethodDefEntry> reference : referenceIndex.getReferencesToClass(this.entry)) { | ||
| 68 | add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference)); | ||
| 69 | } | ||
| 70 | |||
| 71 | if (recurse && this.children != null) { | ||
| 72 | for (Object child : this.children) { | ||
| 73 | if (child instanceof ClassReferenceTreeNode) { | ||
| 74 | ClassReferenceTreeNode node = (ClassReferenceTreeNode) child; | ||
| 75 | |||
| 76 | // don't recurse into ancestor | ||
| 77 | Set<Entry<?>> ancestors = Sets.newHashSet(); | ||
| 78 | TreeNode n = node; | ||
| 79 | while (n.getParent() != null) { | ||
| 80 | n = n.getParent(); | ||
| 81 | if (n instanceof ClassReferenceTreeNode) { | ||
| 82 | ancestors.add(((ClassReferenceTreeNode) n).getEntry()); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | if (ancestors.contains(node.getEntry())) { | ||
| 86 | continue; | ||
| 87 | } | ||
| 88 | |||
| 89 | node.load(index, true); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java deleted file mode 100644 index 2e738c0..0000000 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ /dev/null | |||
| @@ -1,140 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import cuchaz.enigma.translation.Translatable; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 18 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 22 | import cuchaz.enigma.utils.Utils; | ||
| 23 | |||
| 24 | import java.util.Arrays; | ||
| 25 | import java.util.List; | ||
| 26 | |||
| 27 | public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements Translatable { | ||
| 28 | |||
| 29 | private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); | ||
| 30 | public E entry; | ||
| 31 | public C context; | ||
| 32 | public ReferenceTargetType targetType; | ||
| 33 | |||
| 34 | private boolean sourceName; | ||
| 35 | |||
| 36 | public EntryReference(E entry, String sourceName) { | ||
| 37 | this(entry, sourceName, null); | ||
| 38 | } | ||
| 39 | |||
| 40 | public EntryReference(E entry, String sourceName, C context) { | ||
| 41 | this(entry, sourceName, context, ReferenceTargetType.none()); | ||
| 42 | } | ||
| 43 | |||
| 44 | public EntryReference(E entry, String sourceName, C context, ReferenceTargetType targetType) { | ||
| 45 | if (entry == null) { | ||
| 46 | throw new IllegalArgumentException("Entry cannot be null!"); | ||
| 47 | } | ||
| 48 | |||
| 49 | this.entry = entry; | ||
| 50 | this.context = context; | ||
| 51 | this.targetType = targetType; | ||
| 52 | |||
| 53 | this.sourceName = sourceName != null && !sourceName.isEmpty(); | ||
| 54 | if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) { | ||
| 55 | this.sourceName = false; | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public EntryReference(E entry, C context, EntryReference<E, C> other) { | ||
| 60 | this.entry = entry; | ||
| 61 | this.context = context; | ||
| 62 | this.sourceName = other.sourceName; | ||
| 63 | this.targetType = other.targetType; | ||
| 64 | } | ||
| 65 | |||
| 66 | public ClassEntry getLocationClassEntry() { | ||
| 67 | if (context != null) { | ||
| 68 | return context.getContainingClass(); | ||
| 69 | } | ||
| 70 | return entry.getContainingClass(); | ||
| 71 | } | ||
| 72 | |||
| 73 | public boolean isNamed() { | ||
| 74 | return this.sourceName; | ||
| 75 | } | ||
| 76 | |||
| 77 | public Entry<?> getNameableEntry() { | ||
| 78 | if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { | ||
| 79 | // renaming a constructor really means renaming the class | ||
| 80 | return entry.getContainingClass(); | ||
| 81 | } | ||
| 82 | return entry; | ||
| 83 | } | ||
| 84 | |||
| 85 | public String getNameableName() { | ||
| 86 | return getNameableEntry().getName(); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public int hashCode() { | ||
| 91 | if (context != null) { | ||
| 92 | return Utils.combineHashesOrdered(entry.hashCode(), context.hashCode()); | ||
| 93 | } | ||
| 94 | return entry.hashCode(); | ||
| 95 | } | ||
| 96 | |||
| 97 | @Override | ||
| 98 | public boolean equals(Object other) { | ||
| 99 | return other instanceof EntryReference && equals((EntryReference<?, ?>) other); | ||
| 100 | } | ||
| 101 | |||
| 102 | public boolean equals(EntryReference<?, ?> other) { | ||
| 103 | // check entry first | ||
| 104 | boolean isEntrySame = entry.equals(other.entry); | ||
| 105 | if (!isEntrySame) { | ||
| 106 | return false; | ||
| 107 | } | ||
| 108 | |||
| 109 | // check caller | ||
| 110 | if (context == null && other.context == null) { | ||
| 111 | return true; | ||
| 112 | } else if (context != null && other.context != null) { | ||
| 113 | return context.equals(other.context); | ||
| 114 | } | ||
| 115 | return false; | ||
| 116 | } | ||
| 117 | |||
| 118 | @Override | ||
| 119 | public String toString() { | ||
| 120 | StringBuilder buf = new StringBuilder(); | ||
| 121 | buf.append(entry); | ||
| 122 | |||
| 123 | if (context != null) { | ||
| 124 | buf.append(" called from "); | ||
| 125 | buf.append(context); | ||
| 126 | } | ||
| 127 | |||
| 128 | if (targetType != null && targetType.getKind() != ReferenceTargetType.Kind.NONE) { | ||
| 129 | buf.append(" on target of type "); | ||
| 130 | buf.append(targetType); | ||
| 131 | } | ||
| 132 | |||
| 133 | return buf.toString(); | ||
| 134 | } | ||
| 135 | |||
| 136 | @Override | ||
| 137 | public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 138 | return new EntryReference<>(translator.translate(entry), translator.translate(context), this); | ||
| 139 | } | ||
| 140 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java deleted file mode 100644 index 4beab7f..0000000 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ /dev/null | |||
| @@ -1,83 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 15 | import cuchaz.enigma.analysis.index.ReferenceIndex; | ||
| 16 | import cuchaz.enigma.translation.Translator; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 20 | |||
| 21 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 22 | |||
| 23 | public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> { | ||
| 24 | |||
| 25 | private final Translator translator; | ||
| 26 | private FieldEntry entry; | ||
| 27 | private EntryReference<FieldEntry, MethodDefEntry> reference; | ||
| 28 | |||
| 29 | public FieldReferenceTreeNode(Translator translator, FieldEntry entry) { | ||
| 30 | this.translator = translator; | ||
| 31 | this.entry = entry; | ||
| 32 | this.reference = null; | ||
| 33 | } | ||
| 34 | |||
| 35 | private FieldReferenceTreeNode(Translator translator, EntryReference<FieldEntry, MethodDefEntry> reference) { | ||
| 36 | this.translator = translator; | ||
| 37 | this.entry = reference.entry; | ||
| 38 | this.reference = reference; | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public FieldEntry getEntry() { | ||
| 43 | return this.entry; | ||
| 44 | } | ||
| 45 | |||
| 46 | @Override | ||
| 47 | public EntryReference<FieldEntry, MethodDefEntry> getReference() { | ||
| 48 | return this.reference; | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public String toString() { | ||
| 53 | if (this.reference != null) { | ||
| 54 | return String.format("%s", translator.translate(this.reference.context)); | ||
| 55 | } | ||
| 56 | return translator.translate(entry).toString(); | ||
| 57 | } | ||
| 58 | |||
| 59 | public void load(JarIndex index, boolean recurse) { | ||
| 60 | ReferenceIndex referenceIndex = index.getReferenceIndex(); | ||
| 61 | |||
| 62 | // get all the child nodes | ||
| 63 | if (this.reference == null) { | ||
| 64 | for (EntryReference<FieldEntry, MethodDefEntry> reference : referenceIndex.getReferencesToField(this.entry)) { | ||
| 65 | add(new FieldReferenceTreeNode(translator, reference)); | ||
| 66 | } | ||
| 67 | } else { | ||
| 68 | for (EntryReference<MethodEntry, MethodDefEntry> reference : referenceIndex.getReferencesToMethod(this.reference.context)) { | ||
| 69 | add(new MethodReferenceTreeNode(translator, reference)); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | if (recurse && children != null) { | ||
| 74 | for (Object node : children) { | ||
| 75 | if (node instanceof MethodReferenceTreeNode) { | ||
| 76 | ((MethodReferenceTreeNode) node).load(index, true, false); | ||
| 77 | } else if (node instanceof FieldReferenceTreeNode) { | ||
| 78 | ((FieldReferenceTreeNode) node).load(index, true); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java b/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java deleted file mode 100644 index 80a7154..0000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java +++ /dev/null | |||
| @@ -1,154 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 4 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 5 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | import org.objectweb.asm.Type; | ||
| 10 | import org.objectweb.asm.tree.analysis.BasicValue; | ||
| 11 | import org.objectweb.asm.tree.analysis.SimpleVerifier; | ||
| 12 | |||
| 13 | import java.util.Set; | ||
| 14 | |||
| 15 | public class IndexSimpleVerifier extends SimpleVerifier { | ||
| 16 | private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;"); | ||
| 17 | private final EntryIndex entryIndex; | ||
| 18 | private final InheritanceIndex inheritanceIndex; | ||
| 19 | |||
| 20 | public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { | ||
| 21 | super(Utils.ASM_VERSION, null, null, null, false); | ||
| 22 | this.entryIndex = entryIndex; | ||
| 23 | this.inheritanceIndex = inheritanceIndex; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | protected boolean isSubTypeOf(BasicValue value, BasicValue expected) { | ||
| 28 | Type expectedType = expected.getType(); | ||
| 29 | Type type = value.getType(); | ||
| 30 | switch (expectedType.getSort()) { | ||
| 31 | case Type.INT: | ||
| 32 | case Type.FLOAT: | ||
| 33 | case Type.LONG: | ||
| 34 | case Type.DOUBLE: | ||
| 35 | return type.equals(expectedType); | ||
| 36 | case Type.ARRAY: | ||
| 37 | case Type.OBJECT: | ||
| 38 | if (type.equals(NULL_TYPE)) { | ||
| 39 | return true; | ||
| 40 | } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { | ||
| 41 | if (isAssignableFrom(expectedType, type)) { | ||
| 42 | return true; | ||
| 43 | } else if (isInterface(expectedType)) { | ||
| 44 | return isAssignableFrom(OBJECT_TYPE, type); | ||
| 45 | } else { | ||
| 46 | return false; | ||
| 47 | } | ||
| 48 | } else { | ||
| 49 | return false; | ||
| 50 | } | ||
| 51 | default: | ||
| 52 | throw new AssertionError(); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | protected boolean isInterface(Type type) { | ||
| 58 | AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName())); | ||
| 59 | if (classAccess != null) { | ||
| 60 | return classAccess.isInterface(); | ||
| 61 | } | ||
| 62 | |||
| 63 | Class<?> clazz = getClass(type); | ||
| 64 | if (clazz != null) { | ||
| 65 | return clazz.isInterface(); | ||
| 66 | } | ||
| 67 | |||
| 68 | return false; | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | protected Type getSuperClass(Type type) { | ||
| 73 | ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName())); | ||
| 74 | if (definition != null) { | ||
| 75 | return Type.getType('L' + definition.getSuperClass().getFullName() + ';'); | ||
| 76 | } | ||
| 77 | |||
| 78 | Class<?> clazz = getClass(type); | ||
| 79 | if (clazz != null) { | ||
| 80 | return Type.getType(clazz.getSuperclass()); | ||
| 81 | } | ||
| 82 | |||
| 83 | return OBJECT_TYPE; | ||
| 84 | } | ||
| 85 | |||
| 86 | @Override | ||
| 87 | protected boolean isAssignableFrom(Type type1, Type type2) { | ||
| 88 | if (type1.equals(type2)) { | ||
| 89 | return true; | ||
| 90 | } | ||
| 91 | |||
| 92 | if (type2.equals(NULL_TYPE)) { | ||
| 93 | return true; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (type1.getSort() == Type.ARRAY) { | ||
| 97 | return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1))); | ||
| 98 | } | ||
| 99 | |||
| 100 | if (type2.getSort() == Type.ARRAY) { | ||
| 101 | return type1.equals(OBJECT_TYPE); | ||
| 102 | } | ||
| 103 | |||
| 104 | if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) { | ||
| 105 | if (type1.equals(OBJECT_TYPE)) { | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | |||
| 109 | ClassEntry class1 = new ClassEntry(type1.getInternalName()); | ||
| 110 | ClassEntry class2 = new ClassEntry(type2.getInternalName()); | ||
| 111 | |||
| 112 | if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) { | ||
| 113 | return inheritanceIndex.getAncestors(class2).contains(class1); | ||
| 114 | } | ||
| 115 | |||
| 116 | Class<?> class1Class = getClass(Type.getType('L' + class1.getFullName() + ';')); | ||
| 117 | Class<?> class2Class = getClass(Type.getType('L' + class2.getFullName() + ';')); | ||
| 118 | |||
| 119 | if (class1Class == null) { | ||
| 120 | return true; // missing classes to find out | ||
| 121 | } | ||
| 122 | |||
| 123 | if (class2Class != null) { | ||
| 124 | return class1Class.isAssignableFrom(class2Class); | ||
| 125 | } | ||
| 126 | |||
| 127 | if (entryIndex.hasClass(class2)) { | ||
| 128 | Set<ClassEntry> ancestors = inheritanceIndex.getAncestors(class2); | ||
| 129 | |||
| 130 | for (ClassEntry ancestorEntry : ancestors) { | ||
| 131 | Class<?> ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';')); | ||
| 132 | if (ancestor == null || class1Class.isAssignableFrom(ancestor)) { | ||
| 133 | return true; // assignable, or missing classes to find out | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | return false; | ||
| 138 | } | ||
| 139 | |||
| 140 | return true; // missing classes to find out | ||
| 141 | } | ||
| 142 | |||
| 143 | return false; | ||
| 144 | } | ||
| 145 | |||
| 146 | @Override | ||
| 147 | protected final Class<?> getClass(Type type) { | ||
| 148 | try { | ||
| 149 | return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null); | ||
| 150 | } catch (ClassNotFoundException e) { | ||
| 151 | return null; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java b/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java deleted file mode 100644 index 0c2dfd7..0000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java +++ /dev/null | |||
| @@ -1,74 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import com.google.common.collect.Lists; | ||
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 5 | import cuchaz.enigma.translation.Translator; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 7 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 10 | |||
| 11 | import java.util.Collection; | ||
| 12 | import java.util.List; | ||
| 13 | |||
| 14 | public class IndexTreeBuilder { | ||
| 15 | private final JarIndex index; | ||
| 16 | |||
| 17 | public IndexTreeBuilder(JarIndex index) { | ||
| 18 | this.index = index; | ||
| 19 | } | ||
| 20 | |||
| 21 | public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) { | ||
| 22 | // get the root node | ||
| 23 | List<String> ancestry = Lists.newArrayList(); | ||
| 24 | ancestry.add(obfClassEntry.getFullName()); | ||
| 25 | for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) { | ||
| 26 | ancestry.add(classEntry.getFullName()); | ||
| 27 | } | ||
| 28 | |||
| 29 | ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1)); | ||
| 30 | |||
| 31 | // expand all children recursively | ||
| 32 | rootNode.load(index.getInheritanceIndex(), true); | ||
| 33 | |||
| 34 | return rootNode; | ||
| 35 | } | ||
| 36 | |||
| 37 | public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) { | ||
| 38 | if (index.getInheritanceIndex().isParent(obfClassEntry)) { | ||
| 39 | ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry); | ||
| 40 | node.load(index); | ||
| 41 | return node; | ||
| 42 | } | ||
| 43 | return null; | ||
| 44 | } | ||
| 45 | |||
| 46 | public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) { | ||
| 47 | MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 48 | |||
| 49 | // make a root node at the base | ||
| 50 | MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( | ||
| 51 | translator, resolvedEntry, | ||
| 52 | index.getEntryIndex().hasMethod(resolvedEntry) | ||
| 53 | ); | ||
| 54 | |||
| 55 | // expand the full tree | ||
| 56 | rootNode.load(index); | ||
| 57 | |||
| 58 | return rootNode; | ||
| 59 | } | ||
| 60 | |||
| 61 | public List<MethodImplementationsTreeNode> buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) { | ||
| 62 | EntryResolver resolver = index.getEntryResolver(); | ||
| 63 | Collection<MethodEntry> resolvedEntries = resolver.resolveEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 64 | |||
| 65 | List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); | ||
| 66 | for (MethodEntry resolvedEntry : resolvedEntries) { | ||
| 67 | MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, resolvedEntry); | ||
| 68 | node.load(index); | ||
| 69 | nodes.add(node); | ||
| 70 | } | ||
| 71 | |||
| 72 | return nodes; | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java b/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java deleted file mode 100644 index 8a1c238..0000000 --- a/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java +++ /dev/null | |||
| @@ -1,131 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import cuchaz.enigma.utils.Utils; | ||
| 4 | import org.objectweb.asm.Opcodes; | ||
| 5 | import org.objectweb.asm.Type; | ||
| 6 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 7 | import org.objectweb.asm.tree.analysis.AnalyzerException; | ||
| 8 | import org.objectweb.asm.tree.analysis.Interpreter; | ||
| 9 | import org.objectweb.asm.tree.analysis.Value; | ||
| 10 | |||
| 11 | import java.util.List; | ||
| 12 | import java.util.Objects; | ||
| 13 | import java.util.stream.Collectors; | ||
| 14 | |||
| 15 | public class InterpreterPair<V extends Value, W extends Value> extends Interpreter<InterpreterPair.PairValue<V, W>> { | ||
| 16 | private final Interpreter<V> left; | ||
| 17 | private final Interpreter<W> right; | ||
| 18 | |||
| 19 | public InterpreterPair(Interpreter<V> left, Interpreter<W> right) { | ||
| 20 | super(Utils.ASM_VERSION); | ||
| 21 | this.left = left; | ||
| 22 | this.right = right; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public PairValue<V, W> newValue(Type type) { | ||
| 27 | return pair( | ||
| 28 | left.newValue(type), | ||
| 29 | right.newValue(type) | ||
| 30 | ); | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public PairValue<V, W> newOperation(AbstractInsnNode insn) throws AnalyzerException { | ||
| 35 | return pair( | ||
| 36 | left.newOperation(insn), | ||
| 37 | right.newOperation(insn) | ||
| 38 | ); | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public PairValue<V, W> copyOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException { | ||
| 43 | return pair( | ||
| 44 | left.copyOperation(insn, value.left), | ||
| 45 | right.copyOperation(insn, value.right) | ||
| 46 | ); | ||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | public PairValue<V, W> unaryOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException { | ||
| 51 | return pair( | ||
| 52 | left.unaryOperation(insn, value.left), | ||
| 53 | right.unaryOperation(insn, value.right) | ||
| 54 | ); | ||
| 55 | } | ||
| 56 | |||
| 57 | @Override | ||
| 58 | public PairValue<V, W> binaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2) throws AnalyzerException { | ||
| 59 | return pair( | ||
| 60 | left.binaryOperation(insn, value1.left, value2.left), | ||
| 61 | right.binaryOperation(insn, value1.right, value2.right) | ||
| 62 | ); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Override | ||
| 66 | public PairValue<V, W> ternaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2, PairValue<V, W> value3) throws AnalyzerException { | ||
| 67 | return pair( | ||
| 68 | left.ternaryOperation(insn, value1.left, value2.left, value3.left), | ||
| 69 | right.ternaryOperation(insn, value1.right, value2.right, value3.right) | ||
| 70 | ); | ||
| 71 | } | ||
| 72 | |||
| 73 | @Override | ||
| 74 | public PairValue<V, W> naryOperation(AbstractInsnNode insn, List<? extends PairValue<V, W>> values) throws AnalyzerException { | ||
| 75 | return pair( | ||
| 76 | left.naryOperation(insn, values.stream().map(v -> v.left).collect(Collectors.toList())), | ||
| 77 | right.naryOperation(insn, values.stream().map(v -> v.right).collect(Collectors.toList())) | ||
| 78 | ); | ||
| 79 | } | ||
| 80 | |||
| 81 | @Override | ||
| 82 | public void returnOperation(AbstractInsnNode insn, PairValue<V, W> value, PairValue<V, W> expected) throws AnalyzerException { | ||
| 83 | left.returnOperation(insn, value.left, expected.left); | ||
| 84 | right.returnOperation(insn, value.right, expected.right); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public PairValue<V, W> merge(PairValue<V, W> value1, PairValue<V, W> value2) { | ||
| 89 | return pair( | ||
| 90 | left.merge(value1.left, value2.left), | ||
| 91 | right.merge(value1.right, value2.right) | ||
| 92 | ); | ||
| 93 | } | ||
| 94 | |||
| 95 | private PairValue<V, W> pair(V left, W right) { | ||
| 96 | if (left == null && right == null) { | ||
| 97 | return null; | ||
| 98 | } | ||
| 99 | |||
| 100 | return new PairValue<>(left, right); | ||
| 101 | } | ||
| 102 | |||
| 103 | public static final class PairValue<V extends Value, W extends Value> implements Value { | ||
| 104 | public final V left; | ||
| 105 | public final W right; | ||
| 106 | |||
| 107 | public PairValue(V left, W right) { | ||
| 108 | if (left == null && right == null) { | ||
| 109 | throw new IllegalArgumentException("should use null rather than pair of nulls"); | ||
| 110 | } | ||
| 111 | |||
| 112 | this.left = left; | ||
| 113 | this.right = right; | ||
| 114 | } | ||
| 115 | |||
| 116 | @Override | ||
| 117 | public boolean equals(Object o) { | ||
| 118 | return o instanceof InterpreterPair.PairValue && Objects.equals(left, ((PairValue) o).left) && Objects.equals(right, ((PairValue) o).right); | ||
| 119 | } | ||
| 120 | |||
| 121 | @Override | ||
| 122 | public int hashCode() { | ||
| 123 | return left.hashCode() * 31 + right.hashCode(); | ||
| 124 | } | ||
| 125 | |||
| 126 | @Override | ||
| 127 | public int getSize() { | ||
| 128 | return (left == null ? right : left).getSize(); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java deleted file mode 100644 index b09f7ac..0000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ /dev/null | |||
| @@ -1,85 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 16 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 17 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 18 | import cuchaz.enigma.translation.Translator; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 21 | |||
| 22 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 23 | import java.util.Collection; | ||
| 24 | import java.util.List; | ||
| 25 | |||
| 26 | public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { | ||
| 27 | |||
| 28 | private final Translator translator; | ||
| 29 | private MethodEntry entry; | ||
| 30 | |||
| 31 | public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) { | ||
| 32 | this.translator = translator; | ||
| 33 | if (entry == null) { | ||
| 34 | throw new IllegalArgumentException("Entry cannot be null!"); | ||
| 35 | } | ||
| 36 | |||
| 37 | this.entry = entry; | ||
| 38 | } | ||
| 39 | |||
| 40 | public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { | ||
| 41 | // is this the node? | ||
| 42 | if (node.getMethodEntry().equals(entry)) { | ||
| 43 | return node; | ||
| 44 | } | ||
| 45 | |||
| 46 | // recurse | ||
| 47 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 48 | MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry); | ||
| 49 | if (foundNode != null) { | ||
| 50 | return foundNode; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | return null; | ||
| 54 | } | ||
| 55 | |||
| 56 | public MethodEntry getMethodEntry() { | ||
| 57 | return this.entry; | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public String toString() { | ||
| 62 | MethodEntry translatedEntry = translator.translate(entry); | ||
| 63 | String className = translatedEntry.getParent().getFullName(); | ||
| 64 | String methodName = translatedEntry.getName(); | ||
| 65 | return className + "." + methodName + "()"; | ||
| 66 | } | ||
| 67 | |||
| 68 | public void load(JarIndex index) { | ||
| 69 | // get all method implementations | ||
| 70 | List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); | ||
| 71 | EntryIndex entryIndex = index.getEntryIndex(); | ||
| 72 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | ||
| 73 | |||
| 74 | Collection<ClassEntry> descendants = inheritanceIndex.getDescendants(entry.getParent()); | ||
| 75 | for (ClassEntry inheritor : descendants) { | ||
| 76 | MethodEntry methodEntry = entry.withParent(inheritor); | ||
| 77 | if (entryIndex.hasMethod(methodEntry)) { | ||
| 78 | nodes.add(new MethodImplementationsTreeNode(translator, methodEntry)); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | // add them to this node | ||
| 83 | nodes.forEach(this::add); | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java deleted file mode 100644 index e77b5cc..0000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ /dev/null | |||
| @@ -1,95 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 15 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 16 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 17 | import cuchaz.enigma.translation.Translator; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 20 | |||
| 21 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 22 | |||
| 23 | public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { | ||
| 24 | |||
| 25 | private final Translator translator; | ||
| 26 | private MethodEntry entry; | ||
| 27 | private boolean implemented; | ||
| 28 | |||
| 29 | public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean implemented) { | ||
| 30 | this.translator = translator; | ||
| 31 | this.entry = entry; | ||
| 32 | this.implemented = implemented; | ||
| 33 | } | ||
| 34 | |||
| 35 | public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { | ||
| 36 | // is this the node? | ||
| 37 | if (node.getMethodEntry().equals(entry)) { | ||
| 38 | return node; | ||
| 39 | } | ||
| 40 | |||
| 41 | // recurse | ||
| 42 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 43 | MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry); | ||
| 44 | if (foundNode != null) { | ||
| 45 | return foundNode; | ||
| 46 | } | ||
| 47 | } | ||
| 48 | return null; | ||
| 49 | } | ||
| 50 | |||
| 51 | public MethodEntry getMethodEntry() { | ||
| 52 | return this.entry; | ||
| 53 | } | ||
| 54 | |||
| 55 | public boolean isImplemented() { | ||
| 56 | return this.implemented; | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public String toString() { | ||
| 61 | MethodEntry translatedEntry = translator.translate(entry); | ||
| 62 | String className = translatedEntry.getContainingClass().getFullName(); | ||
| 63 | |||
| 64 | if (!this.implemented) { | ||
| 65 | return className; | ||
| 66 | } else { | ||
| 67 | String methodName = translatedEntry.getName(); | ||
| 68 | return className + "." + methodName + "()"; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Returns true if there is sub-node worthy to display. | ||
| 74 | */ | ||
| 75 | public boolean load(JarIndex index) { | ||
| 76 | // get all the child nodes | ||
| 77 | EntryIndex entryIndex = index.getEntryIndex(); | ||
| 78 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | ||
| 79 | |||
| 80 | boolean ret = false; | ||
| 81 | for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) { | ||
| 82 | MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc()); | ||
| 83 | |||
| 84 | MethodInheritanceTreeNode node = new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry)); | ||
| 85 | boolean childOverride = node.load(index); | ||
| 86 | |||
| 87 | if (childOverride || node.implemented) { | ||
| 88 | this.add(node); | ||
| 89 | ret = true; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | return ret; | ||
| 94 | } | ||
| 95 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java b/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java deleted file mode 100644 index 8117103..0000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import org.objectweb.asm.tree.MethodNode; | ||
| 4 | |||
| 5 | import java.util.function.Consumer; | ||
| 6 | |||
| 7 | public class MethodNodeWithAction extends MethodNode { | ||
| 8 | private final Consumer<MethodNode> action; | ||
| 9 | |||
| 10 | public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer<MethodNode> action) { | ||
| 11 | super(api, access, name, descriptor, signature, exceptions); | ||
| 12 | this.action = action; | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public void visitEnd() { | ||
| 17 | action.accept(this); | ||
| 18 | } | ||
| 19 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java deleted file mode 100644 index 8995eb5..0000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java +++ /dev/null | |||
| @@ -1,113 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import com.google.common.collect.Sets; | ||
| 15 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 16 | import cuchaz.enigma.analysis.index.ReferenceIndex; | ||
| 17 | import cuchaz.enigma.translation.Translator; | ||
| 18 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 22 | |||
| 23 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 24 | import javax.swing.tree.TreeNode; | ||
| 25 | import java.util.ArrayList; | ||
| 26 | import java.util.Collection; | ||
| 27 | import java.util.Set; | ||
| 28 | |||
| 29 | public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<MethodEntry, MethodDefEntry> { | ||
| 30 | |||
| 31 | private final Translator translator; | ||
| 32 | private MethodEntry entry; | ||
| 33 | private EntryReference<MethodEntry, MethodDefEntry> reference; | ||
| 34 | |||
| 35 | public MethodReferenceTreeNode(Translator translator, MethodEntry entry) { | ||
| 36 | this.translator = translator; | ||
| 37 | this.entry = entry; | ||
| 38 | this.reference = null; | ||
| 39 | } | ||
| 40 | |||
| 41 | public MethodReferenceTreeNode(Translator translator, EntryReference<MethodEntry, MethodDefEntry> reference) { | ||
| 42 | this.translator = translator; | ||
| 43 | this.entry = reference.entry; | ||
| 44 | this.reference = reference; | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public MethodEntry getEntry() { | ||
| 49 | return this.entry; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public EntryReference<MethodEntry, MethodDefEntry> getReference() { | ||
| 54 | return this.reference; | ||
| 55 | } | ||
| 56 | |||
| 57 | @Override | ||
| 58 | public String toString() { | ||
| 59 | if (this.reference != null) { | ||
| 60 | return String.format("%s", translator.translate(this.reference.context)); | ||
| 61 | } | ||
| 62 | return translator.translate(this.entry).getName(); | ||
| 63 | } | ||
| 64 | |||
| 65 | public void load(JarIndex index, boolean recurse, boolean recurseMethod) { | ||
| 66 | // get all the child nodes | ||
| 67 | Collection<EntryReference<MethodEntry, MethodDefEntry>> references = getReferences(index, recurseMethod); | ||
| 68 | |||
| 69 | for (EntryReference<MethodEntry, MethodDefEntry> reference : references) { | ||
| 70 | add(new MethodReferenceTreeNode(translator, reference)); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (recurse && this.children != null) { | ||
| 74 | for (Object child : this.children) { | ||
| 75 | if (child instanceof MethodReferenceTreeNode) { | ||
| 76 | MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; | ||
| 77 | |||
| 78 | // don't recurse into ancestor | ||
| 79 | Set<Entry<?>> ancestors = Sets.newHashSet(); | ||
| 80 | TreeNode n = node; | ||
| 81 | while (n.getParent() != null) { | ||
| 82 | n = n.getParent(); | ||
| 83 | if (n instanceof MethodReferenceTreeNode) { | ||
| 84 | ancestors.add(((MethodReferenceTreeNode) n).getEntry()); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | if (ancestors.contains(node.getEntry())) { | ||
| 88 | continue; | ||
| 89 | } | ||
| 90 | |||
| 91 | node.load(index, true, false); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | private Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferences(JarIndex index, boolean recurseMethod) { | ||
| 98 | ReferenceIndex referenceIndex = index.getReferenceIndex(); | ||
| 99 | |||
| 100 | if (recurseMethod) { | ||
| 101 | Collection<EntryReference<MethodEntry, MethodDefEntry>> references = new ArrayList<>(); | ||
| 102 | |||
| 103 | EntryResolver entryResolver = index.getEntryResolver(); | ||
| 104 | for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) { | ||
| 105 | references.addAll(referenceIndex.getReferencesToMethod(methodEntry)); | ||
| 106 | } | ||
| 107 | |||
| 108 | return references; | ||
| 109 | } else { | ||
| 110 | return referenceIndex.getReferencesToMethod(entry); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java deleted file mode 100644 index 5b19d18..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java +++ /dev/null | |||
| @@ -1,74 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 4 | |||
| 5 | public abstract class ReferenceTargetType { | ||
| 6 | private static final None NONE = new None(); | ||
| 7 | private static final Uninitialized UNINITIALIZED = new Uninitialized(); | ||
| 8 | |||
| 9 | public abstract Kind getKind(); | ||
| 10 | |||
| 11 | public static None none() { | ||
| 12 | return NONE; | ||
| 13 | } | ||
| 14 | |||
| 15 | public static Uninitialized uninitialized() { | ||
| 16 | return UNINITIALIZED; | ||
| 17 | } | ||
| 18 | |||
| 19 | public static ClassType classType(ClassEntry name) { | ||
| 20 | return new ClassType(name); | ||
| 21 | } | ||
| 22 | |||
| 23 | public enum Kind { | ||
| 24 | NONE, | ||
| 25 | UNINITIALIZED, | ||
| 26 | CLASS_TYPE | ||
| 27 | } | ||
| 28 | |||
| 29 | public static class None extends ReferenceTargetType { | ||
| 30 | @Override | ||
| 31 | public Kind getKind() { | ||
| 32 | return Kind.NONE; | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public String toString() { | ||
| 37 | return "(none)"; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | public static class Uninitialized extends ReferenceTargetType { | ||
| 42 | @Override | ||
| 43 | public Kind getKind() { | ||
| 44 | return Kind.UNINITIALIZED; | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public String toString() { | ||
| 49 | return "(uninitialized)"; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | public static class ClassType extends ReferenceTargetType { | ||
| 54 | private final ClassEntry entry; | ||
| 55 | |||
| 56 | private ClassType(ClassEntry entry) { | ||
| 57 | this.entry = entry; | ||
| 58 | } | ||
| 59 | |||
| 60 | public ClassEntry getEntry() { | ||
| 61 | return entry; | ||
| 62 | } | ||
| 63 | |||
| 64 | @Override | ||
| 65 | public Kind getKind() { | ||
| 66 | return Kind.CLASS_TYPE; | ||
| 67 | } | ||
| 68 | |||
| 69 | @Override | ||
| 70 | public String toString() { | ||
| 71 | return entry.toString(); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java deleted file mode 100644 index c0a3a75..0000000 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 15 | |||
| 16 | public interface ReferenceTreeNode<E extends Entry<?>, C extends Entry<?>> { | ||
| 17 | E getEntry(); | ||
| 18 | |||
| 19 | EntryReference<E, C> getReference(); | ||
| 20 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java deleted file mode 100644 index f0155e5..0000000 --- a/src/main/java/cuchaz/enigma/analysis/Token.java +++ /dev/null | |||
| @@ -1,72 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis; | ||
| 13 | |||
| 14 | public class Token implements Comparable<Token> { | ||
| 15 | |||
| 16 | public int start; | ||
| 17 | public int end; | ||
| 18 | public String text; | ||
| 19 | |||
| 20 | public Token(int start, int end, String text) { | ||
| 21 | this.start = start; | ||
| 22 | this.end = end; | ||
| 23 | this.text = text; | ||
| 24 | } | ||
| 25 | |||
| 26 | public int getRenameOffset(String to) { | ||
| 27 | int length = this.end - this.start; | ||
| 28 | return to.length() - length; | ||
| 29 | } | ||
| 30 | |||
| 31 | public void rename(StringBuffer source, String to) { | ||
| 32 | int oldEnd = this.end; | ||
| 33 | this.text = to; | ||
| 34 | this.end = this.start + to.length(); | ||
| 35 | |||
| 36 | source.replace(start, oldEnd, to); | ||
| 37 | } | ||
| 38 | |||
| 39 | public Token move(int offset) { | ||
| 40 | Token token = new Token(this.start + offset, this.end + offset, null); | ||
| 41 | token.text = text; | ||
| 42 | return token; | ||
| 43 | } | ||
| 44 | |||
| 45 | public boolean contains(int pos) { | ||
| 46 | return pos >= start && pos <= end; | ||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | public int compareTo(Token other) { | ||
| 51 | return start - other.start; | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public boolean equals(Object other) { | ||
| 56 | return other instanceof Token && equals((Token) other); | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public int hashCode() { | ||
| 61 | return start * 37 + end; | ||
| 62 | } | ||
| 63 | |||
| 64 | public boolean equals(Token other) { | ||
| 65 | return start == other.start && end == other.end && text.equals(other.text); | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public String toString() { | ||
| 70 | return String.format("[%d,%d]", start, end); | ||
| 71 | } | ||
| 72 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java deleted file mode 100644 index a4b1aac..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java +++ /dev/null | |||
| @@ -1,156 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import com.google.common.collect.Maps; | ||
| 4 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 5 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 6 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 10 | |||
| 11 | import javax.annotation.Nullable; | ||
| 12 | import java.util.*; | ||
| 13 | |||
| 14 | public class BridgeMethodIndex implements JarIndexer { | ||
| 15 | private final EntryIndex entryIndex; | ||
| 16 | private final InheritanceIndex inheritanceIndex; | ||
| 17 | private final ReferenceIndex referenceIndex; | ||
| 18 | |||
| 19 | private final Map<MethodEntry, MethodEntry> bridgeToSpecialized = Maps.newHashMap(); | ||
| 20 | private final Map<MethodEntry, MethodEntry> specializedToBridge = Maps.newHashMap(); | ||
| 21 | |||
| 22 | public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { | ||
| 23 | this.entryIndex = entryIndex; | ||
| 24 | this.inheritanceIndex = inheritanceIndex; | ||
| 25 | this.referenceIndex = referenceIndex; | ||
| 26 | } | ||
| 27 | |||
| 28 | public void findBridgeMethods() { | ||
| 29 | // look for access and bridged methods | ||
| 30 | for (MethodEntry methodEntry : entryIndex.getMethods()) { | ||
| 31 | MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry; | ||
| 32 | |||
| 33 | AccessFlags access = methodDefEntry.getAccess(); | ||
| 34 | if (access == null || !access.isSynthetic()) { | ||
| 35 | continue; | ||
| 36 | } | ||
| 37 | |||
| 38 | indexSyntheticMethod(methodDefEntry, access); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public void processIndex(JarIndex index) { | ||
| 44 | Map<MethodEntry, MethodEntry> copiedAccessToBridge = new HashMap<>(specializedToBridge); | ||
| 45 | |||
| 46 | for (Map.Entry<MethodEntry, MethodEntry> entry : copiedAccessToBridge.entrySet()) { | ||
| 47 | MethodEntry specializedEntry = entry.getKey(); | ||
| 48 | MethodEntry bridgeEntry = entry.getValue(); | ||
| 49 | if (bridgeEntry.getName().equals(specializedEntry.getName())) { | ||
| 50 | continue; | ||
| 51 | } | ||
| 52 | |||
| 53 | MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); | ||
| 54 | specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry)); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { | ||
| 59 | MethodEntry specializedMethod = findSpecializedMethod(syntheticMethod); | ||
| 60 | if (specializedMethod == null) { | ||
| 61 | return; | ||
| 62 | } | ||
| 63 | |||
| 64 | if (access.isBridge() || isPotentialBridge(syntheticMethod, specializedMethod)) { | ||
| 65 | bridgeToSpecialized.put(syntheticMethod, specializedMethod); | ||
| 66 | specializedToBridge.put(specializedMethod, syntheticMethod); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | private MethodEntry findSpecializedMethod(MethodEntry method) { | ||
| 71 | // we want to find all compiler-added methods that directly call another with no processing | ||
| 72 | |||
| 73 | // get all the methods that we call | ||
| 74 | final Collection<MethodEntry> referencedMethods = referenceIndex.getMethodsReferencedBy(method); | ||
| 75 | |||
| 76 | // is there just one? | ||
| 77 | if (referencedMethods.size() != 1) { | ||
| 78 | return null; | ||
| 79 | } | ||
| 80 | |||
| 81 | return referencedMethods.stream().findFirst().orElse(null); | ||
| 82 | } | ||
| 83 | |||
| 84 | private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) { | ||
| 85 | // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited | ||
| 86 | AccessFlags bridgeAccess = bridgeMethod.getAccess(); | ||
| 87 | if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) { | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | MethodDescriptor bridgeDesc = bridgeMethod.getDesc(); | ||
| 92 | MethodDescriptor specializedDesc = specializedMethod.getDesc(); | ||
| 93 | List<TypeDescriptor> bridgeArguments = bridgeDesc.getArgumentDescs(); | ||
| 94 | List<TypeDescriptor> specializedArguments = specializedDesc.getArgumentDescs(); | ||
| 95 | |||
| 96 | // A bridge method will always have the same number of arguments | ||
| 97 | if (bridgeArguments.size() != specializedArguments.size()) { | ||
| 98 | return false; | ||
| 99 | } | ||
| 100 | |||
| 101 | // Check that all argument types are bridge-compatible | ||
| 102 | for (int i = 0; i < bridgeArguments.size(); i++) { | ||
| 103 | if (!areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) { | ||
| 104 | return false; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | // Check that the return type is bridge-compatible | ||
| 109 | return areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc()); | ||
| 110 | } | ||
| 111 | |||
| 112 | private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) { | ||
| 113 | if (bridgeDesc.equals(specializedDesc)) { | ||
| 114 | return true; | ||
| 115 | } | ||
| 116 | |||
| 117 | // Either the descs will be equal, or they are both types and different through a generic | ||
| 118 | if (bridgeDesc.isType() && specializedDesc.isType()) { | ||
| 119 | ClassEntry bridgeType = bridgeDesc.getTypeEntry(); | ||
| 120 | ClassEntry accessedType = specializedDesc.getTypeEntry(); | ||
| 121 | |||
| 122 | // If the given types are completely unrelated to each other, this can't be bridge compatible | ||
| 123 | InheritanceIndex.Relation relation = inheritanceIndex.computeClassRelation(accessedType, bridgeType); | ||
| 124 | return relation != InheritanceIndex.Relation.UNRELATED; | ||
| 125 | } | ||
| 126 | |||
| 127 | return false; | ||
| 128 | } | ||
| 129 | |||
| 130 | public boolean isBridgeMethod(MethodEntry entry) { | ||
| 131 | return bridgeToSpecialized.containsKey(entry); | ||
| 132 | } | ||
| 133 | |||
| 134 | public boolean isSpecializedMethod(MethodEntry entry) { | ||
| 135 | return specializedToBridge.containsKey(entry); | ||
| 136 | } | ||
| 137 | |||
| 138 | @Nullable | ||
| 139 | public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) { | ||
| 140 | return specializedToBridge.get(specialized); | ||
| 141 | } | ||
| 142 | |||
| 143 | public MethodEntry getSpecializedFromBridge(MethodEntry bridge) { | ||
| 144 | return bridgeToSpecialized.get(bridge); | ||
| 145 | } | ||
| 146 | |||
| 147 | /** Includes "renamed specialized -> bridge" entries. */ | ||
| 148 | public Map<MethodEntry, MethodEntry> getSpecializedToBridge() { | ||
| 149 | return Collections.unmodifiableMap(specializedToBridge); | ||
| 150 | } | ||
| 151 | |||
| 152 | /** Only "bridge -> original name" entries. **/ | ||
| 153 | public Map<MethodEntry, MethodEntry> getBridgeToSpecialized() { | ||
| 154 | return Collections.unmodifiableMap(bridgeToSpecialized); | ||
| 155 | } | ||
| 156 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java deleted file mode 100644 index 9a2726e..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java +++ /dev/null | |||
| @@ -1,102 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 4 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 5 | |||
| 6 | import javax.annotation.Nullable; | ||
| 7 | import java.util.Collection; | ||
| 8 | import java.util.HashMap; | ||
| 9 | import java.util.Map; | ||
| 10 | |||
| 11 | public class EntryIndex implements JarIndexer { | ||
| 12 | private Map<ClassEntry, AccessFlags> classes = new HashMap<>(); | ||
| 13 | private Map<FieldEntry, AccessFlags> fields = new HashMap<>(); | ||
| 14 | private Map<MethodEntry, AccessFlags> methods = new HashMap<>(); | ||
| 15 | private Map<ClassEntry, ClassDefEntry> definitions = new HashMap<>(); | ||
| 16 | |||
| 17 | @Override | ||
| 18 | public void indexClass(ClassDefEntry classEntry) { | ||
| 19 | definitions.put(classEntry, classEntry); | ||
| 20 | classes.put(classEntry, classEntry.getAccess()); | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public void indexMethod(MethodDefEntry methodEntry) { | ||
| 25 | methods.put(methodEntry, methodEntry.getAccess()); | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void indexField(FieldDefEntry fieldEntry) { | ||
| 30 | fields.put(fieldEntry, fieldEntry.getAccess()); | ||
| 31 | } | ||
| 32 | |||
| 33 | public boolean hasClass(ClassEntry entry) { | ||
| 34 | return classes.containsKey(entry); | ||
| 35 | } | ||
| 36 | |||
| 37 | public boolean hasMethod(MethodEntry entry) { | ||
| 38 | return methods.containsKey(entry); | ||
| 39 | } | ||
| 40 | |||
| 41 | public boolean hasField(FieldEntry entry) { | ||
| 42 | return fields.containsKey(entry); | ||
| 43 | } | ||
| 44 | |||
| 45 | public boolean hasEntry(Entry<?> entry) { | ||
| 46 | if (entry instanceof ClassEntry) { | ||
| 47 | return hasClass((ClassEntry) entry); | ||
| 48 | } else if (entry instanceof MethodEntry) { | ||
| 49 | return hasMethod((MethodEntry) entry); | ||
| 50 | } else if (entry instanceof FieldEntry) { | ||
| 51 | return hasField((FieldEntry) entry); | ||
| 52 | } else if (entry instanceof LocalVariableEntry) { | ||
| 53 | return hasMethod(((LocalVariableEntry) entry).getParent()); | ||
| 54 | } | ||
| 55 | |||
| 56 | return false; | ||
| 57 | } | ||
| 58 | |||
| 59 | @Nullable | ||
| 60 | public AccessFlags getMethodAccess(MethodEntry entry) { | ||
| 61 | return methods.get(entry); | ||
| 62 | } | ||
| 63 | |||
| 64 | @Nullable | ||
| 65 | public AccessFlags getFieldAccess(FieldEntry entry) { | ||
| 66 | return fields.get(entry); | ||
| 67 | } | ||
| 68 | |||
| 69 | @Nullable | ||
| 70 | public AccessFlags getClassAccess(ClassEntry entry) { | ||
| 71 | return classes.get(entry); | ||
| 72 | } | ||
| 73 | |||
| 74 | @Nullable | ||
| 75 | public AccessFlags getEntryAccess(Entry<?> entry) { | ||
| 76 | if (entry instanceof MethodEntry) { | ||
| 77 | return getMethodAccess((MethodEntry) entry); | ||
| 78 | } else if (entry instanceof FieldEntry) { | ||
| 79 | return getFieldAccess((FieldEntry) entry); | ||
| 80 | } else if (entry instanceof LocalVariableEntry) { | ||
| 81 | return getMethodAccess(((LocalVariableEntry) entry).getParent()); | ||
| 82 | } | ||
| 83 | |||
| 84 | return null; | ||
| 85 | } | ||
| 86 | |||
| 87 | public ClassDefEntry getDefinition(ClassEntry entry) { | ||
| 88 | return definitions.get(entry); | ||
| 89 | } | ||
| 90 | |||
| 91 | public Collection<ClassEntry> getClasses() { | ||
| 92 | return classes.keySet(); | ||
| 93 | } | ||
| 94 | |||
| 95 | public Collection<MethodEntry> getMethods() { | ||
| 96 | return methods.keySet(); | ||
| 97 | } | ||
| 98 | |||
| 99 | public Collection<FieldEntry> getFields() { | ||
| 100 | return fields.keySet(); | ||
| 101 | } | ||
| 102 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java deleted file mode 100644 index f9cb23c..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 4 | import cuchaz.enigma.translation.representation.entry.FieldDefEntry; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 6 | import org.objectweb.asm.ClassVisitor; | ||
| 7 | import org.objectweb.asm.FieldVisitor; | ||
| 8 | import org.objectweb.asm.MethodVisitor; | ||
| 9 | |||
| 10 | public class IndexClassVisitor extends ClassVisitor { | ||
| 11 | private final JarIndexer indexer; | ||
| 12 | private ClassDefEntry classEntry; | ||
| 13 | |||
| 14 | public IndexClassVisitor(JarIndex indexer, int api) { | ||
| 15 | super(api); | ||
| 16 | this.indexer = indexer; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 21 | classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); | ||
| 22 | indexer.indexClass(classEntry); | ||
| 23 | |||
| 24 | super.visit(version, access, name, signature, superName, interfaces); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { | ||
| 29 | indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature)); | ||
| 30 | |||
| 31 | return super.visitField(access, name, desc, signature, value); | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { | ||
| 36 | indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature)); | ||
| 37 | |||
| 38 | return super.visitMethod(access, name, desc, signature, exceptions); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java deleted file mode 100644 index f3d419e..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java +++ /dev/null | |||
| @@ -1,180 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.IndexSimpleVerifier; | ||
| 4 | import cuchaz.enigma.analysis.InterpreterPair; | ||
| 5 | import cuchaz.enigma.analysis.MethodNodeWithAction; | ||
| 6 | import cuchaz.enigma.analysis.ReferenceTargetType; | ||
| 7 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 8 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 9 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 10 | import cuchaz.enigma.translation.representation.Signature; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 12 | import org.objectweb.asm.*; | ||
| 13 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 14 | import org.objectweb.asm.tree.FieldInsnNode; | ||
| 15 | import org.objectweb.asm.tree.InvokeDynamicInsnNode; | ||
| 16 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 17 | import org.objectweb.asm.tree.analysis.*; | ||
| 18 | |||
| 19 | import java.util.List; | ||
| 20 | import java.util.stream.Collectors; | ||
| 21 | |||
| 22 | public class IndexReferenceVisitor extends ClassVisitor { | ||
| 23 | private final JarIndexer indexer; | ||
| 24 | private final EntryIndex entryIndex; | ||
| 25 | private final InheritanceIndex inheritanceIndex; | ||
| 26 | private ClassEntry classEntry; | ||
| 27 | private String className; | ||
| 28 | |||
| 29 | public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) { | ||
| 30 | super(api); | ||
| 31 | this.indexer = indexer; | ||
| 32 | this.entryIndex = entryIndex; | ||
| 33 | this.inheritanceIndex = inheritanceIndex; | ||
| 34 | } | ||
| 35 | |||
| 36 | @Override | ||
| 37 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 38 | classEntry = new ClassEntry(name); | ||
| 39 | className = name; | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { | ||
| 44 | MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); | ||
| 45 | return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> { | ||
| 46 | try { | ||
| 47 | new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode); | ||
| 48 | } catch (AnalyzerException e) { | ||
| 49 | throw new RuntimeException(e); | ||
| 50 | } | ||
| 51 | }); | ||
| 52 | } | ||
| 53 | |||
| 54 | private static class MethodInterpreter extends InterpreterPair<BasicValue, SourceValue> { | ||
| 55 | private final MethodDefEntry callerEntry; | ||
| 56 | private JarIndexer indexer; | ||
| 57 | |||
| 58 | public MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { | ||
| 59 | super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter()); | ||
| 60 | this.callerEntry = callerEntry; | ||
| 61 | this.indexer = indexer; | ||
| 62 | } | ||
| 63 | |||
| 64 | @Override | ||
| 65 | public PairValue<BasicValue, SourceValue> newOperation(AbstractInsnNode insn) throws AnalyzerException { | ||
| 66 | if (insn.getOpcode() == Opcodes.GETSTATIC) { | ||
| 67 | FieldInsnNode field = (FieldInsnNode) insn; | ||
| 68 | indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); | ||
| 69 | } | ||
| 70 | |||
| 71 | return super.newOperation(insn); | ||
| 72 | } | ||
| 73 | |||
| 74 | @Override | ||
| 75 | public PairValue<BasicValue, SourceValue> unaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value) throws AnalyzerException { | ||
| 76 | if (insn.getOpcode() == Opcodes.PUTSTATIC) { | ||
| 77 | FieldInsnNode field = (FieldInsnNode) insn; | ||
| 78 | indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); | ||
| 79 | } | ||
| 80 | |||
| 81 | if (insn.getOpcode() == Opcodes.GETFIELD) { | ||
| 82 | FieldInsnNode field = (FieldInsnNode) insn; | ||
| 83 | indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn)); | ||
| 84 | } | ||
| 85 | |||
| 86 | return super.unaryOperation(insn, value); | ||
| 87 | } | ||
| 88 | |||
| 89 | |||
| 90 | @Override | ||
| 91 | public PairValue<BasicValue, SourceValue> binaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value1, PairValue<BasicValue, SourceValue> value2) throws AnalyzerException { | ||
| 92 | if (insn.getOpcode() == Opcodes.PUTFIELD) { | ||
| 93 | FieldInsnNode field = (FieldInsnNode) insn; | ||
| 94 | FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc); | ||
| 95 | indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none()); | ||
| 96 | } | ||
| 97 | |||
| 98 | return super.binaryOperation(insn, value1, value2); | ||
| 99 | } | ||
| 100 | |||
| 101 | @Override | ||
| 102 | public PairValue<BasicValue, SourceValue> naryOperation(AbstractInsnNode insn, List<? extends PairValue<BasicValue, SourceValue>> values) throws AnalyzerException { | ||
| 103 | if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { | ||
| 104 | MethodInsnNode methodInsn = (MethodInsnNode) insn; | ||
| 105 | indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn)); | ||
| 106 | } | ||
| 107 | |||
| 108 | if (insn.getOpcode() == Opcodes.INVOKESTATIC) { | ||
| 109 | MethodInsnNode methodInsn = (MethodInsnNode) insn; | ||
| 110 | indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none()); | ||
| 111 | } | ||
| 112 | |||
| 113 | if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) { | ||
| 114 | InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn; | ||
| 115 | List<AbstractInsnNode> args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).collect(Collectors.toList()); | ||
| 116 | |||
| 117 | if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) { | ||
| 118 | Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0]; | ||
| 119 | Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1]; | ||
| 120 | Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2]; | ||
| 121 | |||
| 122 | ReferenceTargetType targetType; | ||
| 123 | if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) { | ||
| 124 | if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) { | ||
| 125 | targetType = getReferenceTargetType(values.get(0), insn); | ||
| 126 | } else { | ||
| 127 | targetType = ReferenceTargetType.none(); // no "this" argument | ||
| 128 | } | ||
| 129 | } else { | ||
| 130 | targetType = ReferenceTargetType.none(); | ||
| 131 | } | ||
| 132 | |||
| 133 | indexer.indexLambda(callerEntry, new Lambda( | ||
| 134 | invokeDynamicInsn.name, | ||
| 135 | new MethodDescriptor(invokeDynamicInsn.desc), | ||
| 136 | new MethodDescriptor(samMethodType.getDescriptor()), | ||
| 137 | getHandleEntry(implMethod), | ||
| 138 | new MethodDescriptor(instantiatedMethodType.getDescriptor()) | ||
| 139 | ), targetType); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | return super.naryOperation(insn, values); | ||
| 144 | } | ||
| 145 | |||
| 146 | private ReferenceTargetType getReferenceTargetType(PairValue<BasicValue, SourceValue> target, AbstractInsnNode insn) throws AnalyzerException { | ||
| 147 | if (target.left == BasicValue.UNINITIALIZED_VALUE) { | ||
| 148 | return ReferenceTargetType.uninitialized(); | ||
| 149 | } | ||
| 150 | |||
| 151 | if (target.left.getType().getSort() == Type.OBJECT) { | ||
| 152 | return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName())); | ||
| 153 | } | ||
| 154 | |||
| 155 | if (target.left.getType().getSort() == Type.ARRAY) { | ||
| 156 | return ReferenceTargetType.classType(new ClassEntry("java/lang/Object")); | ||
| 157 | } | ||
| 158 | |||
| 159 | throw new AnalyzerException(insn, "called method on or accessed field of non-object type"); | ||
| 160 | } | ||
| 161 | |||
| 162 | private static ParentedEntry<?> getHandleEntry(Handle handle) { | ||
| 163 | switch (handle.getTag()) { | ||
| 164 | case Opcodes.H_GETFIELD: | ||
| 165 | case Opcodes.H_GETSTATIC: | ||
| 166 | case Opcodes.H_PUTFIELD: | ||
| 167 | case Opcodes.H_PUTSTATIC: | ||
| 168 | return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); | ||
| 169 | case Opcodes.H_INVOKEINTERFACE: | ||
| 170 | case Opcodes.H_INVOKESPECIAL: | ||
| 171 | case Opcodes.H_INVOKESTATIC: | ||
| 172 | case Opcodes.H_INVOKEVIRTUAL: | ||
| 173 | case Opcodes.H_NEWINVOKESPECIAL: | ||
| 174 | return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); | ||
| 175 | } | ||
| 176 | |||
| 177 | throw new RuntimeException("Invalid handle tag " + handle.getTag()); | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java deleted file mode 100644 index 1ab2abd..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java +++ /dev/null | |||
| @@ -1,127 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis.index; | ||
| 13 | |||
| 14 | import com.google.common.collect.HashMultimap; | ||
| 15 | import com.google.common.collect.Multimap; | ||
| 16 | import com.google.common.collect.Sets; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | |||
| 20 | import java.util.Collection; | ||
| 21 | import java.util.HashSet; | ||
| 22 | import java.util.LinkedList; | ||
| 23 | import java.util.Set; | ||
| 24 | |||
| 25 | public class InheritanceIndex implements JarIndexer { | ||
| 26 | private final EntryIndex entryIndex; | ||
| 27 | |||
| 28 | private Multimap<ClassEntry, ClassEntry> classParents = HashMultimap.create(); | ||
| 29 | private Multimap<ClassEntry, ClassEntry> classChildren = HashMultimap.create(); | ||
| 30 | |||
| 31 | public InheritanceIndex(EntryIndex entryIndex) { | ||
| 32 | this.entryIndex = entryIndex; | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void indexClass(ClassDefEntry classEntry) { | ||
| 37 | if (classEntry.isJre()) { | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | |||
| 41 | ClassEntry superClass = classEntry.getSuperClass(); | ||
| 42 | if (superClass != null && !superClass.getName().equals("java/lang/Object")) { | ||
| 43 | indexParent(classEntry, superClass); | ||
| 44 | } | ||
| 45 | |||
| 46 | for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { | ||
| 47 | indexParent(classEntry, interfaceEntry); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { | ||
| 52 | classParents.put(childEntry, parentEntry); | ||
| 53 | classChildren.put(parentEntry, childEntry); | ||
| 54 | } | ||
| 55 | |||
| 56 | public Collection<ClassEntry> getParents(ClassEntry classEntry) { | ||
| 57 | return classParents.get(classEntry); | ||
| 58 | } | ||
| 59 | |||
| 60 | public Collection<ClassEntry> getChildren(ClassEntry classEntry) { | ||
| 61 | return classChildren.get(classEntry); | ||
| 62 | } | ||
| 63 | |||
| 64 | public Collection<ClassEntry> getDescendants(ClassEntry classEntry) { | ||
| 65 | Collection<ClassEntry> descendants = new HashSet<>(); | ||
| 66 | |||
| 67 | LinkedList<ClassEntry> descendantQueue = new LinkedList<>(); | ||
| 68 | descendantQueue.push(classEntry); | ||
| 69 | |||
| 70 | while (!descendantQueue.isEmpty()) { | ||
| 71 | ClassEntry descendant = descendantQueue.pop(); | ||
| 72 | Collection<ClassEntry> children = getChildren(descendant); | ||
| 73 | |||
| 74 | children.forEach(descendantQueue::push); | ||
| 75 | descendants.addAll(children); | ||
| 76 | } | ||
| 77 | |||
| 78 | return descendants; | ||
| 79 | } | ||
| 80 | |||
| 81 | public Set<ClassEntry> getAncestors(ClassEntry classEntry) { | ||
| 82 | Set<ClassEntry> ancestors = Sets.newHashSet(); | ||
| 83 | |||
| 84 | LinkedList<ClassEntry> ancestorQueue = new LinkedList<>(); | ||
| 85 | ancestorQueue.push(classEntry); | ||
| 86 | |||
| 87 | while (!ancestorQueue.isEmpty()) { | ||
| 88 | ClassEntry ancestor = ancestorQueue.pop(); | ||
| 89 | Collection<ClassEntry> parents = getParents(ancestor); | ||
| 90 | |||
| 91 | parents.forEach(ancestorQueue::push); | ||
| 92 | ancestors.addAll(parents); | ||
| 93 | } | ||
| 94 | |||
| 95 | return ancestors; | ||
| 96 | } | ||
| 97 | |||
| 98 | public Relation computeClassRelation(ClassEntry classEntry, ClassEntry potentialAncestor) { | ||
| 99 | if (potentialAncestor.getName().equals("java/lang/Object")) return Relation.RELATED; | ||
| 100 | if (!entryIndex.hasClass(classEntry)) return Relation.UNKNOWN; | ||
| 101 | |||
| 102 | for (ClassEntry ancestor : getAncestors(classEntry)) { | ||
| 103 | if (potentialAncestor.equals(ancestor)) { | ||
| 104 | return Relation.RELATED; | ||
| 105 | } else if (!entryIndex.hasClass(ancestor)) { | ||
| 106 | return Relation.UNKNOWN; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | return Relation.UNRELATED; | ||
| 111 | } | ||
| 112 | |||
| 113 | public boolean isParent(ClassEntry classEntry) { | ||
| 114 | return classChildren.containsKey(classEntry); | ||
| 115 | } | ||
| 116 | |||
| 117 | public boolean hasParents(ClassEntry classEntry) { | ||
| 118 | Collection<ClassEntry> parents = classParents.get(classEntry); | ||
| 119 | return parents != null && !parents.isEmpty(); | ||
| 120 | } | ||
| 121 | |||
| 122 | public enum Relation { | ||
| 123 | RELATED, | ||
| 124 | UNRELATED, | ||
| 125 | UNKNOWN | ||
| 126 | } | ||
| 127 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java deleted file mode 100644 index e401c2f..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ /dev/null | |||
| @@ -1,171 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.analysis.index; | ||
| 13 | |||
| 14 | import com.google.common.collect.HashMultimap; | ||
| 15 | import com.google.common.collect.Multimap; | ||
| 16 | import cuchaz.enigma.ProgressListener; | ||
| 17 | import cuchaz.enigma.analysis.ClassCache; | ||
| 18 | import cuchaz.enigma.analysis.ReferenceTargetType; | ||
| 19 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 20 | import cuchaz.enigma.translation.mapping.IndexEntryResolver; | ||
| 21 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 23 | import cuchaz.enigma.utils.I18n; | ||
| 24 | |||
| 25 | import cuchaz.enigma.utils.Utils; | ||
| 26 | import org.objectweb.asm.ClassReader; | ||
| 27 | import org.objectweb.asm.Opcodes; | ||
| 28 | |||
| 29 | import java.util.Arrays; | ||
| 30 | import java.util.Collection; | ||
| 31 | |||
| 32 | public class JarIndex implements JarIndexer { | ||
| 33 | private final EntryIndex entryIndex; | ||
| 34 | private final InheritanceIndex inheritanceIndex; | ||
| 35 | private final ReferenceIndex referenceIndex; | ||
| 36 | private final BridgeMethodIndex bridgeMethodIndex; | ||
| 37 | private final PackageVisibilityIndex packageVisibilityIndex; | ||
| 38 | private final EntryResolver entryResolver; | ||
| 39 | |||
| 40 | private final Collection<JarIndexer> indexers; | ||
| 41 | |||
| 42 | private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); | ||
| 43 | |||
| 44 | public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { | ||
| 45 | this.entryIndex = entryIndex; | ||
| 46 | this.inheritanceIndex = inheritanceIndex; | ||
| 47 | this.referenceIndex = referenceIndex; | ||
| 48 | this.bridgeMethodIndex = bridgeMethodIndex; | ||
| 49 | this.packageVisibilityIndex = packageVisibilityIndex; | ||
| 50 | this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); | ||
| 51 | this.entryResolver = new IndexEntryResolver(this); | ||
| 52 | } | ||
| 53 | |||
| 54 | public static JarIndex empty() { | ||
| 55 | EntryIndex entryIndex = new EntryIndex(); | ||
| 56 | InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); | ||
| 57 | ReferenceIndex referenceIndex = new ReferenceIndex(); | ||
| 58 | BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); | ||
| 59 | PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex(); | ||
| 60 | return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); | ||
| 61 | } | ||
| 62 | |||
| 63 | public void indexJar(ClassCache classCache, ProgressListener progress) { | ||
| 64 | progress.init(4, I18n.translate("progress.jar.indexing")); | ||
| 65 | |||
| 66 | progress.step(1, I18n.translate("progress.jar.indexing.entries")); | ||
| 67 | classCache.visit(() -> new IndexClassVisitor(this, Utils.ASM_VERSION), ClassReader.SKIP_CODE); | ||
| 68 | |||
| 69 | progress.step(2, I18n.translate("progress.jar.indexing.references")); | ||
| 70 | classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Utils.ASM_VERSION), 0); | ||
| 71 | |||
| 72 | progress.step(3, I18n.translate("progress.jar.indexing.methods")); | ||
| 73 | bridgeMethodIndex.findBridgeMethods(); | ||
| 74 | |||
| 75 | progress.step(4, I18n.translate("progress.jar.indexing.process")); | ||
| 76 | processIndex(this); | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public void processIndex(JarIndex index) { | ||
| 81 | indexers.forEach(indexer -> indexer.processIndex(index)); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public void indexClass(ClassDefEntry classEntry) { | ||
| 86 | if (classEntry.isJre()) { | ||
| 87 | return; | ||
| 88 | } | ||
| 89 | |||
| 90 | for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { | ||
| 91 | if (classEntry.equals(interfaceEntry)) { | ||
| 92 | throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | indexers.forEach(indexer -> indexer.indexClass(classEntry)); | ||
| 97 | } | ||
| 98 | |||
| 99 | @Override | ||
| 100 | public void indexField(FieldDefEntry fieldEntry) { | ||
| 101 | if (fieldEntry.getParent().isJre()) { | ||
| 102 | return; | ||
| 103 | } | ||
| 104 | |||
| 105 | indexers.forEach(indexer -> indexer.indexField(fieldEntry)); | ||
| 106 | } | ||
| 107 | |||
| 108 | @Override | ||
| 109 | public void indexMethod(MethodDefEntry methodEntry) { | ||
| 110 | if (methodEntry.getParent().isJre()) { | ||
| 111 | return; | ||
| 112 | } | ||
| 113 | |||
| 114 | indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); | ||
| 115 | |||
| 116 | if (!methodEntry.isConstructor()) { | ||
| 117 | methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | @Override | ||
| 122 | public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { | ||
| 123 | if (callerEntry.getParent().isJre()) { | ||
| 124 | return; | ||
| 125 | } | ||
| 126 | |||
| 127 | indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry, targetType)); | ||
| 128 | } | ||
| 129 | |||
| 130 | @Override | ||
| 131 | public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { | ||
| 132 | if (callerEntry.getParent().isJre()) { | ||
| 133 | return; | ||
| 134 | } | ||
| 135 | |||
| 136 | indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry, targetType)); | ||
| 137 | } | ||
| 138 | |||
| 139 | @Override | ||
| 140 | public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { | ||
| 141 | if (callerEntry.getParent().isJre()) { | ||
| 142 | return; | ||
| 143 | } | ||
| 144 | |||
| 145 | indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda, targetType)); | ||
| 146 | } | ||
| 147 | |||
| 148 | public EntryIndex getEntryIndex() { | ||
| 149 | return entryIndex; | ||
| 150 | } | ||
| 151 | |||
| 152 | public InheritanceIndex getInheritanceIndex() { | ||
| 153 | return this.inheritanceIndex; | ||
| 154 | } | ||
| 155 | |||
| 156 | public ReferenceIndex getReferenceIndex() { | ||
| 157 | return referenceIndex; | ||
| 158 | } | ||
| 159 | |||
| 160 | public BridgeMethodIndex getBridgeMethodIndex() { | ||
| 161 | return bridgeMethodIndex; | ||
| 162 | } | ||
| 163 | |||
| 164 | public PackageVisibilityIndex getPackageVisibilityIndex() { | ||
| 165 | return packageVisibilityIndex; | ||
| 166 | } | ||
| 167 | |||
| 168 | public EntryResolver getEntryResolver() { | ||
| 169 | return entryResolver; | ||
| 170 | } | ||
| 171 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java deleted file mode 100644 index f17e7c9..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java +++ /dev/null | |||
| @@ -1,28 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.ReferenceTargetType; | ||
| 4 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 6 | |||
| 7 | public interface JarIndexer { | ||
| 8 | default void indexClass(ClassDefEntry classEntry) { | ||
| 9 | } | ||
| 10 | |||
| 11 | default void indexField(FieldDefEntry fieldEntry) { | ||
| 12 | } | ||
| 13 | |||
| 14 | default void indexMethod(MethodDefEntry methodEntry) { | ||
| 15 | } | ||
| 16 | |||
| 17 | default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { | ||
| 18 | } | ||
| 19 | |||
| 20 | default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { | ||
| 21 | } | ||
| 22 | |||
| 23 | default void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { | ||
| 24 | } | ||
| 25 | |||
| 26 | default void processIndex(JarIndex index) { | ||
| 27 | } | ||
| 28 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java deleted file mode 100644 index 63eb730..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java +++ /dev/null | |||
| @@ -1,147 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import com.google.common.collect.HashMultimap; | ||
| 4 | import com.google.common.collect.Lists; | ||
| 5 | import com.google.common.collect.Maps; | ||
| 6 | import com.google.common.collect.Sets; | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.analysis.ReferenceTargetType; | ||
| 9 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 11 | |||
| 12 | import java.util.*; | ||
| 13 | |||
| 14 | public class PackageVisibilityIndex implements JarIndexer { | ||
| 15 | private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { | ||
| 16 | if (entryAcc.isPublic()) { | ||
| 17 | return false; | ||
| 18 | } | ||
| 19 | |||
| 20 | if (entryAcc.isProtected()) { | ||
| 21 | ClassEntry contextClass = ref.context.getContainingClass(); | ||
| 22 | ClassEntry referencedClass = ref.entry.getContainingClass(); | ||
| 23 | |||
| 24 | if (!inheritanceIndex.getAncestors(contextClass).contains(referencedClass)) { | ||
| 25 | return true; // access to protected member not in superclass | ||
| 26 | } | ||
| 27 | |||
| 28 | if (ref.targetType.getKind() == ReferenceTargetType.Kind.NONE) { | ||
| 29 | return false; // access to superclass or static superclass member | ||
| 30 | } | ||
| 31 | |||
| 32 | // access to instance member only valid if target's class assignable to context class | ||
| 33 | return !(ref.targetType.getKind() == ReferenceTargetType.Kind.UNINITIALIZED || | ||
| 34 | ((ReferenceTargetType.ClassType) ref.targetType).getEntry().equals(contextClass) || | ||
| 35 | inheritanceIndex.getAncestors(((ReferenceTargetType.ClassType) ref.targetType).getEntry()).contains(contextClass)); | ||
| 36 | } | ||
| 37 | |||
| 38 | return true; | ||
| 39 | } | ||
| 40 | |||
| 41 | private final HashMultimap<ClassEntry, ClassEntry> connections = HashMultimap.create(); | ||
| 42 | private final List<Set<ClassEntry>> partitions = Lists.newArrayList(); | ||
| 43 | private final Map<ClassEntry, Set<ClassEntry>> classPartitions = Maps.newHashMap(); | ||
| 44 | |||
| 45 | private void addConnection(ClassEntry classA, ClassEntry classB) { | ||
| 46 | if (classA != classB) { | ||
| 47 | connections.put(classA, classB); | ||
| 48 | connections.put(classB, classA); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | private void buildPartition(Set<ClassEntry> unassignedClasses, Set<ClassEntry> partition, ClassEntry member) { | ||
| 53 | for (ClassEntry connected : connections.get(member)) { | ||
| 54 | if (unassignedClasses.remove(connected)) { | ||
| 55 | partition.add(connected); | ||
| 56 | buildPartition(unassignedClasses, partition, connected); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { | ||
| 62 | for (FieldEntry entry : entryIndex.getFields()) { | ||
| 63 | AccessFlags entryAcc = entryIndex.getFieldAccess(entry); | ||
| 64 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | ||
| 65 | for (EntryReference<FieldEntry, MethodDefEntry> ref : referenceIndex.getReferencesToField(entry)) { | ||
| 66 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 67 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | for (MethodEntry entry : entryIndex.getMethods()) { | ||
| 74 | AccessFlags entryAcc = entryIndex.getMethodAccess(entry); | ||
| 75 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | ||
| 76 | for (EntryReference<MethodEntry, MethodDefEntry> ref : referenceIndex.getReferencesToMethod(entry)) { | ||
| 77 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 78 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | for (ClassEntry entry : entryIndex.getClasses()) { | ||
| 85 | AccessFlags entryAcc = entryIndex.getClassAccess(entry); | ||
| 86 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | ||
| 87 | for (EntryReference<ClassEntry, FieldDefEntry> ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { | ||
| 88 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 89 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | for (EntryReference<ClassEntry, MethodDefEntry> ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { | ||
| 94 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 95 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | for (ClassEntry parent : inheritanceIndex.getParents(entry)) { | ||
| 101 | AccessFlags parentAcc = entryIndex.getClassAccess(parent); | ||
| 102 | if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) { | ||
| 103 | addConnection(entry, parent); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | ClassEntry outerClass = entry.getOuterClass(); | ||
| 108 | if (outerClass != null) { | ||
| 109 | addConnection(entry, outerClass); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | private void addPartitions(EntryIndex entryIndex) { | ||
| 115 | Set<ClassEntry> unassignedClasses = Sets.newHashSet(entryIndex.getClasses()); | ||
| 116 | while (!unassignedClasses.isEmpty()) { | ||
| 117 | Iterator<ClassEntry> iterator = unassignedClasses.iterator(); | ||
| 118 | ClassEntry initialEntry = iterator.next(); | ||
| 119 | iterator.remove(); | ||
| 120 | |||
| 121 | HashSet<ClassEntry> partition = Sets.newHashSet(); | ||
| 122 | partition.add(initialEntry); | ||
| 123 | buildPartition(unassignedClasses, partition, initialEntry); | ||
| 124 | partitions.add(partition); | ||
| 125 | for (ClassEntry entry : partition) { | ||
| 126 | classPartitions.put(entry, partition); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | public Collection<Set<ClassEntry>> getPartitions() { | ||
| 132 | return partitions; | ||
| 133 | } | ||
| 134 | |||
| 135 | public Set<ClassEntry> getPartition(ClassEntry classEntry) { | ||
| 136 | return classPartitions.get(classEntry); | ||
| 137 | } | ||
| 138 | |||
| 139 | @Override | ||
| 140 | public void processIndex(JarIndex index) { | ||
| 141 | EntryIndex entryIndex = index.getEntryIndex(); | ||
| 142 | ReferenceIndex referenceIndex = index.getReferenceIndex(); | ||
| 143 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | ||
| 144 | addConnections(entryIndex, referenceIndex, inheritanceIndex); | ||
| 145 | addPartitions(entryIndex); | ||
| 146 | } | ||
| 147 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java deleted file mode 100644 index b6797c2..0000000 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ /dev/null | |||
| @@ -1,148 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import com.google.common.collect.HashMultimap; | ||
| 4 | import com.google.common.collect.Multimap; | ||
| 5 | import cuchaz.enigma.analysis.EntryReference; | ||
| 6 | import cuchaz.enigma.analysis.ReferenceTargetType; | ||
| 7 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 8 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 9 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 10 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Map; | ||
| 15 | |||
| 16 | public class ReferenceIndex implements JarIndexer { | ||
| 17 | private Multimap<MethodEntry, MethodEntry> methodReferences = HashMultimap.create(); | ||
| 18 | |||
| 19 | private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create(); | ||
| 20 | private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create(); | ||
| 21 | private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create(); | ||
| 22 | private Multimap<ClassEntry, EntryReference<ClassEntry, FieldDefEntry>> fieldTypeReferences = HashMultimap.create(); | ||
| 23 | private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodTypeReferences = HashMultimap.create(); | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void indexMethod(MethodDefEntry methodEntry) { | ||
| 27 | indexMethodDescriptor(methodEntry, methodEntry.getDesc()); | ||
| 28 | } | ||
| 29 | |||
| 30 | private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { | ||
| 31 | for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { | ||
| 32 | indexMethodTypeDescriptor(entry, typeDescriptor); | ||
| 33 | } | ||
| 34 | indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); | ||
| 35 | } | ||
| 36 | |||
| 37 | private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { | ||
| 38 | if (typeDescriptor.isType()) { | ||
| 39 | ClassEntry referencedClass = typeDescriptor.getTypeEntry(); | ||
| 40 | methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); | ||
| 41 | } else if (typeDescriptor.isArray()) { | ||
| 42 | indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | @Override | ||
| 47 | public void indexField(FieldDefEntry fieldEntry) { | ||
| 48 | indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); | ||
| 49 | } | ||
| 50 | |||
| 51 | private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { | ||
| 52 | if (typeDescriptor.isType()) { | ||
| 53 | ClassEntry referencedClass = typeDescriptor.getTypeEntry(); | ||
| 54 | fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); | ||
| 55 | } else if (typeDescriptor.isArray()) { | ||
| 56 | indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { | ||
| 62 | referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); | ||
| 63 | methodReferences.put(callerEntry, referencedEntry); | ||
| 64 | |||
| 65 | if (referencedEntry.isConstructor()) { | ||
| 66 | ClassEntry referencedClass = referencedEntry.getParent(); | ||
| 67 | referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { | ||
| 73 | referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); | ||
| 74 | } | ||
| 75 | |||
| 76 | @Override | ||
| 77 | public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { | ||
| 78 | if (lambda.getImplMethod() instanceof MethodEntry) { | ||
| 79 | indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod(), targetType); | ||
| 80 | } else { | ||
| 81 | indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod(), targetType); | ||
| 82 | } | ||
| 83 | |||
| 84 | indexMethodDescriptor(callerEntry, lambda.getInvokedType()); | ||
| 85 | indexMethodDescriptor(callerEntry, lambda.getSamMethodType()); | ||
| 86 | indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType()); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public void processIndex(JarIndex index) { | ||
| 91 | methodReferences = remapReferences(index, methodReferences); | ||
| 92 | referencesToMethods = remapReferencesTo(index, referencesToMethods); | ||
| 93 | referencesToClasses = remapReferencesTo(index, referencesToClasses); | ||
| 94 | referencesToFields = remapReferencesTo(index, referencesToFields); | ||
| 95 | fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences); | ||
| 96 | methodTypeReferences = remapReferencesTo(index, methodTypeReferences); | ||
| 97 | } | ||
| 98 | |||
| 99 | private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> remapReferences(JarIndex index, Multimap<K, V> multimap) { | ||
| 100 | final int keySetSize = multimap.keySet().size(); | ||
| 101 | Multimap<K, V> resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize); | ||
| 102 | for (Map.Entry<K, V> entry : multimap.entries()) { | ||
| 103 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); | ||
| 104 | } | ||
| 105 | return resolved; | ||
| 106 | } | ||
| 107 | |||
| 108 | private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) { | ||
| 109 | final int keySetSize = multimap.keySet().size(); | ||
| 110 | Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); | ||
| 111 | for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) { | ||
| 112 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); | ||
| 113 | } | ||
| 114 | return resolved; | ||
| 115 | } | ||
| 116 | |||
| 117 | private <E extends Entry<?>> E remap(JarIndex index, E entry) { | ||
| 118 | return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); | ||
| 119 | } | ||
| 120 | |||
| 121 | private <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> remap(JarIndex index, EntryReference<E, C> reference) { | ||
| 122 | return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); | ||
| 123 | } | ||
| 124 | |||
| 125 | public Collection<MethodEntry> getMethodsReferencedBy(MethodEntry entry) { | ||
| 126 | return methodReferences.get(entry); | ||
| 127 | } | ||
| 128 | |||
| 129 | public Collection<EntryReference<FieldEntry, MethodDefEntry>> getReferencesToField(FieldEntry entry) { | ||
| 130 | return referencesToFields.get(entry); | ||
| 131 | } | ||
| 132 | |||
| 133 | public Collection<EntryReference<ClassEntry, MethodDefEntry>> getReferencesToClass(ClassEntry entry) { | ||
| 134 | return referencesToClasses.get(entry); | ||
| 135 | } | ||
| 136 | |||
| 137 | public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) { | ||
| 138 | return referencesToMethods.get(entry); | ||
| 139 | } | ||
| 140 | |||
| 141 | public Collection<EntryReference<ClassEntry, FieldDefEntry>> getFieldTypeReferencesToClass(ClassEntry entry) { | ||
| 142 | return fieldTypeReferences.get(entry); | ||
| 143 | } | ||
| 144 | |||
| 145 | public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodTypeReferencesToClass(ClassEntry entry) { | ||
| 146 | return methodTypeReferences.get(entry); | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java deleted file mode 100644 index bdd6015..0000000 --- a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java +++ /dev/null | |||
| @@ -1,5 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api; | ||
| 2 | |||
| 3 | public interface EnigmaPlugin { | ||
| 4 | void init(EnigmaPluginContext ctx); | ||
| 5 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java deleted file mode 100644 index a59051a..0000000 --- a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api; | ||
| 2 | |||
| 3 | import cuchaz.enigma.api.service.EnigmaService; | ||
| 4 | import cuchaz.enigma.api.service.EnigmaServiceFactory; | ||
| 5 | import cuchaz.enigma.api.service.EnigmaServiceType; | ||
| 6 | |||
| 7 | public interface EnigmaPluginContext { | ||
| 8 | <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory); | ||
| 9 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java deleted file mode 100644 index 526dda7..0000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java +++ /dev/null | |||
| @@ -1,4 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | public interface EnigmaService { | ||
| 4 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java deleted file mode 100644 index 9e433fb..0000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | import java.util.Optional; | ||
| 4 | |||
| 5 | public interface EnigmaServiceContext<T extends EnigmaService> { | ||
| 6 | static <T extends EnigmaService> EnigmaServiceContext<T> empty() { | ||
| 7 | return key -> Optional.empty(); | ||
| 8 | } | ||
| 9 | |||
| 10 | Optional<String> getArgument(String key); | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java deleted file mode 100644 index 7c10ac2..0000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java +++ /dev/null | |||
| @@ -1,5 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | public interface EnigmaServiceFactory<T extends EnigmaService> { | ||
| 4 | T create(EnigmaServiceContext<T> ctx); | ||
| 5 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java deleted file mode 100644 index 358828f..0000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java +++ /dev/null | |||
| @@ -1,29 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | public final class EnigmaServiceType<T extends EnigmaService> { | ||
| 4 | public final String key; | ||
| 5 | |||
| 6 | private EnigmaServiceType(String key) { | ||
| 7 | this.key = key; | ||
| 8 | } | ||
| 9 | |||
| 10 | public static <T extends EnigmaService> EnigmaServiceType<T> create(String key) { | ||
| 11 | return new EnigmaServiceType<>(key); | ||
| 12 | } | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public int hashCode() { | ||
| 16 | return key.hashCode(); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public boolean equals(Object obj) { | ||
| 21 | if (obj == this) return true; | ||
| 22 | |||
| 23 | if (obj instanceof EnigmaServiceType) { | ||
| 24 | return ((EnigmaServiceType) obj).key.equals(key); | ||
| 25 | } | ||
| 26 | |||
| 27 | return false; | ||
| 28 | } | ||
| 29 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java deleted file mode 100644 index 0cda199..0000000 --- a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.ClassCache; | ||
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 5 | |||
| 6 | public interface JarIndexerService extends EnigmaService { | ||
| 7 | EnigmaServiceType<JarIndexerService> TYPE = EnigmaServiceType.create("jar_indexer"); | ||
| 8 | |||
| 9 | void acceptJar(ClassCache classCache, JarIndex jarIndex); | ||
| 10 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java deleted file mode 100644 index 4c357db..0000000 --- a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java +++ /dev/null | |||
| @@ -1,12 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 4 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 5 | |||
| 6 | import java.util.Optional; | ||
| 7 | |||
| 8 | public interface NameProposalService extends EnigmaService { | ||
| 9 | EnigmaServiceType<NameProposalService> TYPE = EnigmaServiceType.create("name_proposal"); | ||
| 10 | |||
| 11 | Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper); | ||
| 12 | } | ||
diff --git a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java deleted file mode 100644 index af0cf30..0000000 --- a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | package cuchaz.enigma.api.service; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 4 | |||
| 5 | public interface ObfuscationTestService extends EnigmaService { | ||
| 6 | EnigmaServiceType<ObfuscationTestService> TYPE = EnigmaServiceType.create("obfuscation_test"); | ||
| 7 | |||
| 8 | boolean testDeobfuscated(Entry<?> entry); | ||
| 9 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java deleted file mode 100644 index 1a2b47f..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java +++ /dev/null | |||
| @@ -1,46 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translator; | ||
| 4 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 5 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 8 | import org.objectweb.asm.Handle; | ||
| 9 | import org.objectweb.asm.Type; | ||
| 10 | |||
| 11 | public class AsmObjectTranslator { | ||
| 12 | public static Type translateType(Translator translator, Type type) { | ||
| 13 | String descString = type.getDescriptor(); | ||
| 14 | switch (type.getSort()) { | ||
| 15 | case Type.OBJECT: { | ||
| 16 | ClassEntry classEntry = new ClassEntry(type.getInternalName()); | ||
| 17 | return Type.getObjectType(translator.translate(classEntry).getFullName()); | ||
| 18 | } | ||
| 19 | case Type.ARRAY: { | ||
| 20 | TypeDescriptor descriptor = new TypeDescriptor(descString); | ||
| 21 | return Type.getType(translator.translate(descriptor).toString()); | ||
| 22 | } | ||
| 23 | case Type.METHOD: { | ||
| 24 | MethodDescriptor descriptor = new MethodDescriptor(descString); | ||
| 25 | return Type.getMethodType(translator.translate(descriptor).toString()); | ||
| 26 | } | ||
| 27 | } | ||
| 28 | return type; | ||
| 29 | } | ||
| 30 | |||
| 31 | public static Handle translateHandle(Translator translator, Handle handle) { | ||
| 32 | MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); | ||
| 33 | MethodEntry translatedMethod = translator.translate(entry); | ||
| 34 | ClassEntry ownerClass = translatedMethod.getParent(); | ||
| 35 | return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); | ||
| 36 | } | ||
| 37 | |||
| 38 | public static Object translateValue(Translator translator, Object value) { | ||
| 39 | if (value instanceof Type) { | ||
| 40 | return translateType(translator, (Type) value); | ||
| 41 | } else if (value instanceof Handle) { | ||
| 42 | return translateHandle(translator, (Handle) value); | ||
| 43 | } | ||
| 44 | return value; | ||
| 45 | } | ||
| 46 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java deleted file mode 100644 index cfd8fbe..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java +++ /dev/null | |||
| @@ -1,126 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import com.google.common.base.CharMatcher; | ||
| 4 | import cuchaz.enigma.translation.LocalNameGenerator; | ||
| 5 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 8 | import org.objectweb.asm.ClassVisitor; | ||
| 9 | import org.objectweb.asm.Label; | ||
| 10 | import org.objectweb.asm.MethodVisitor; | ||
| 11 | import org.objectweb.asm.Opcodes; | ||
| 12 | |||
| 13 | import java.util.HashMap; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | |||
| 17 | public class LocalVariableFixVisitor extends ClassVisitor { | ||
| 18 | private ClassDefEntry ownerEntry; | ||
| 19 | |||
| 20 | public LocalVariableFixVisitor(int api, ClassVisitor visitor) { | ||
| 21 | super(api, visitor); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 26 | ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); | ||
| 27 | super.visit(version, access, name, signature, superName, interfaces); | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { | ||
| 32 | MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature); | ||
| 33 | return new Method(api, methodEntry, super.visitMethod(access, name, descriptor, signature, exceptions)); | ||
| 34 | } | ||
| 35 | |||
| 36 | private class Method extends MethodVisitor { | ||
| 37 | private final MethodDefEntry methodEntry; | ||
| 38 | private final Map<Integer, String> parameterNames = new HashMap<>(); | ||
| 39 | private final Map<Integer, Integer> parameterIndices = new HashMap<>(); | ||
| 40 | private boolean hasParameterTable; | ||
| 41 | private int parameterIndex = 0; | ||
| 42 | |||
| 43 | Method(int api, MethodDefEntry methodEntry, MethodVisitor visitor) { | ||
| 44 | super(api, visitor); | ||
| 45 | this.methodEntry = methodEntry; | ||
| 46 | |||
| 47 | int lvIndex = methodEntry.getAccess().isStatic() ? 0 : 1; | ||
| 48 | List<TypeDescriptor> parameters = methodEntry.getDesc().getArgumentDescs(); | ||
| 49 | for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) { | ||
| 50 | TypeDescriptor param = parameters.get(parameterIndex); | ||
| 51 | parameterIndices.put(lvIndex, parameterIndex); | ||
| 52 | lvIndex += param.getSize(); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public void visitParameter(String name, int access) { | ||
| 58 | hasParameterTable = true; | ||
| 59 | super.visitParameter(fixParameterName(parameterIndex, name), fixParameterAccess(parameterIndex, access)); | ||
| 60 | parameterIndex++; | ||
| 61 | } | ||
| 62 | |||
| 63 | @Override | ||
| 64 | public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { | ||
| 65 | if (index == 0 && !methodEntry.getAccess().isStatic()) { | ||
| 66 | name = "this"; | ||
| 67 | } else if (parameterIndices.containsKey(index)) { | ||
| 68 | name = fixParameterName(parameterIndices.get(index), name); | ||
| 69 | } else if (isInvalidName(name)) { | ||
| 70 | name = LocalNameGenerator.generateLocalVariableName(index, new TypeDescriptor(desc)); | ||
| 71 | } | ||
| 72 | |||
| 73 | super.visitLocalVariable(name, desc, signature, start, end, index); | ||
| 74 | } | ||
| 75 | |||
| 76 | private boolean isInvalidName(String name) { | ||
| 77 | return name == null || name.isEmpty() || !CharMatcher.ascii().matchesAllOf(name); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Override | ||
| 81 | public void visitEnd() { | ||
| 82 | if (!hasParameterTable) { | ||
| 83 | List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs(); | ||
| 84 | for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) { | ||
| 85 | super.visitParameter(fixParameterName(argumentIndex, null), fixParameterAccess(argumentIndex, 0)); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | super.visitEnd(); | ||
| 90 | } | ||
| 91 | |||
| 92 | private String fixParameterName(int index, String name) { | ||
| 93 | if (parameterNames.get(index) != null) { | ||
| 94 | return parameterNames.get(index); // to make sure that LVT names are consistent with parameter table names | ||
| 95 | } | ||
| 96 | |||
| 97 | if (isInvalidName(name)) { | ||
| 98 | List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs(); | ||
| 99 | name = LocalNameGenerator.generateArgumentName(index, arguments.get(index), arguments); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) { | ||
| 103 | name = "name"; | ||
| 104 | } | ||
| 105 | |||
| 106 | if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) { | ||
| 107 | name = "ordinal"; | ||
| 108 | } | ||
| 109 | |||
| 110 | parameterNames.put(index, name); | ||
| 111 | return name; | ||
| 112 | } | ||
| 113 | |||
| 114 | private int fixParameterAccess(int index, int access) { | ||
| 115 | if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) { | ||
| 116 | access |= Opcodes.ACC_SYNTHETIC; | ||
| 117 | } | ||
| 118 | |||
| 119 | if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) { | ||
| 120 | access |= Opcodes.ACC_SYNTHETIC; | ||
| 121 | } | ||
| 122 | |||
| 123 | return access; | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java deleted file mode 100644 index 2b750ea..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.index.BridgeMethodIndex; | ||
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 7 | import org.objectweb.asm.ClassVisitor; | ||
| 8 | import org.objectweb.asm.MethodVisitor; | ||
| 9 | import org.objectweb.asm.Opcodes; | ||
| 10 | |||
| 11 | public class SourceFixVisitor extends ClassVisitor { | ||
| 12 | private final JarIndex index; | ||
| 13 | private ClassDefEntry ownerEntry; | ||
| 14 | |||
| 15 | public SourceFixVisitor(int api, ClassVisitor visitor, JarIndex index) { | ||
| 16 | super(api, visitor); | ||
| 17 | this.index = index; | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 22 | ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); | ||
| 23 | super.visit(version, access, name, signature, superName, interfaces); | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { | ||
| 28 | MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature); | ||
| 29 | |||
| 30 | BridgeMethodIndex bridgeIndex = index.getBridgeMethodIndex(); | ||
| 31 | if (bridgeIndex.isBridgeMethod(methodEntry)) { | ||
| 32 | access |= Opcodes.ACC_BRIDGE; | ||
| 33 | } else if (bridgeIndex.isSpecializedMethod(methodEntry)) { | ||
| 34 | name = bridgeIndex.getBridgeFromSpecialized(methodEntry).getName(); | ||
| 35 | } | ||
| 36 | |||
| 37 | return super.visitMethod(access, name, descriptor, signature, exceptions); | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java deleted file mode 100644 index cb843ad..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java +++ /dev/null | |||
| @@ -1,51 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translator; | ||
| 4 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 7 | import org.objectweb.asm.AnnotationVisitor; | ||
| 8 | |||
| 9 | public class TranslationAnnotationVisitor extends AnnotationVisitor { | ||
| 10 | private final Translator translator; | ||
| 11 | private final ClassEntry annotationEntry; | ||
| 12 | |||
| 13 | public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) { | ||
| 14 | super(api, av); | ||
| 15 | this.translator = translator; | ||
| 16 | this.annotationEntry = annotationEntry; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public void visit(String name, Object value) { | ||
| 21 | super.visit(name, AsmObjectTranslator.translateValue(translator, value)); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public AnnotationVisitor visitArray(String name) { | ||
| 26 | return new TranslationAnnotationVisitor(translator, annotationEntry, api, super.visitArray(name)); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public AnnotationVisitor visitAnnotation(String name, String desc) { | ||
| 31 | TypeDescriptor type = new TypeDescriptor(desc); | ||
| 32 | if (name != null) { | ||
| 33 | FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); | ||
| 34 | return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString()); | ||
| 35 | } else { | ||
| 36 | return super.visitAnnotation(null, translator.translate(type).toString()); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public void visitEnum(String name, String desc, String value) { | ||
| 42 | TypeDescriptor type = new TypeDescriptor(desc); | ||
| 43 | FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type)); | ||
| 44 | if (name != null) { | ||
| 45 | FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); | ||
| 46 | super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName()); | ||
| 47 | } else { | ||
| 48 | super.visitEnum(null, translator.translate(type).toString(), enumField.getName()); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java deleted file mode 100644 index e4c41d3..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java +++ /dev/null | |||
| @@ -1,102 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.bytecode.translators; | ||
| 13 | |||
| 14 | import cuchaz.enigma.translation.Translator; | ||
| 15 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 16 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 18 | import org.objectweb.asm.*; | ||
| 19 | |||
| 20 | import java.util.Arrays; | ||
| 21 | |||
| 22 | public class TranslationClassVisitor extends ClassVisitor { | ||
| 23 | private final Translator translator; | ||
| 24 | |||
| 25 | private ClassDefEntry obfClassEntry; | ||
| 26 | |||
| 27 | public TranslationClassVisitor(Translator translator, int api, ClassVisitor cv) { | ||
| 28 | super(api, cv); | ||
| 29 | this.translator = translator; | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 34 | obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); | ||
| 35 | |||
| 36 | ClassDefEntry translatedEntry = translator.translate(obfClassEntry); | ||
| 37 | String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null; | ||
| 38 | String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new); | ||
| 39 | |||
| 40 | super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { | ||
| 45 | FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature); | ||
| 46 | FieldDefEntry translatedEntry = translator.translate(entry); | ||
| 47 | FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value); | ||
| 48 | return new TranslationFieldVisitor(translator, translatedEntry, api, fv); | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { | ||
| 53 | MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature); | ||
| 54 | MethodDefEntry translatedEntry = translator.translate(entry); | ||
| 55 | String[] translatedExceptions = new String[exceptions.length]; | ||
| 56 | for (int i = 0; i < exceptions.length; i++) { | ||
| 57 | translatedExceptions[i] = translator.translate(new ClassEntry(exceptions[i])).getFullName(); | ||
| 58 | } | ||
| 59 | MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions); | ||
| 60 | return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv); | ||
| 61 | } | ||
| 62 | |||
| 63 | @Override | ||
| 64 | public void visitInnerClass(String name, String outerName, String innerName, int access) { | ||
| 65 | ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]); | ||
| 66 | ClassDefEntry translatedEntry = translator.translate(classEntry); | ||
| 67 | ClassEntry translatedOuterClass = translatedEntry.getOuterClass(); | ||
| 68 | if (translatedOuterClass == null) { | ||
| 69 | throw new IllegalStateException("Translated inner class did not have outer class"); | ||
| 70 | } | ||
| 71 | |||
| 72 | // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null | ||
| 73 | String translatedName = translatedEntry.getFullName(); | ||
| 74 | String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null; | ||
| 75 | String translatedInnerName = innerName != null ? translatedEntry.getName() : null; | ||
| 76 | super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags()); | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public void visitOuterClass(String owner, String name, String desc) { | ||
| 81 | if (desc != null) { | ||
| 82 | MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); | ||
| 83 | super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); | ||
| 84 | } else { | ||
| 85 | super.visitOuterClass(owner, name, desc); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { | ||
| 91 | TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 92 | AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible); | ||
| 93 | return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); | ||
| 94 | } | ||
| 95 | |||
| 96 | @Override | ||
| 97 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { | ||
| 98 | TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 99 | AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible); | ||
| 100 | return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); | ||
| 101 | } | ||
| 102 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java deleted file mode 100644 index 28fc199..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translator; | ||
| 4 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.FieldDefEntry; | ||
| 6 | import org.objectweb.asm.AnnotationVisitor; | ||
| 7 | import org.objectweb.asm.FieldVisitor; | ||
| 8 | import org.objectweb.asm.TypePath; | ||
| 9 | |||
| 10 | public class TranslationFieldVisitor extends FieldVisitor { | ||
| 11 | private final FieldDefEntry fieldEntry; | ||
| 12 | private final Translator translator; | ||
| 13 | |||
| 14 | public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) { | ||
| 15 | super(api, fv); | ||
| 16 | this.translator = translator; | ||
| 17 | this.fieldEntry = fieldEntry; | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { | ||
| 22 | TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 23 | AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); | ||
| 24 | return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { | ||
| 29 | TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 30 | AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); | ||
| 31 | return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java deleted file mode 100644 index a82df1b..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translator; | ||
| 4 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 5 | import cuchaz.enigma.translation.representation.Signature; | ||
| 6 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 8 | import org.objectweb.asm.*; | ||
| 9 | |||
| 10 | public class TranslationMethodVisitor extends MethodVisitor { | ||
| 11 | private final MethodDefEntry methodEntry; | ||
| 12 | private final Translator translator; | ||
| 13 | |||
| 14 | private int parameterIndex = 0; | ||
| 15 | private int parameterLvIndex; | ||
| 16 | |||
| 17 | public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) { | ||
| 18 | super(api, mv); | ||
| 19 | this.translator = translator; | ||
| 20 | this.methodEntry = methodEntry; | ||
| 21 | |||
| 22 | parameterLvIndex = methodEntry.getAccess().isStatic() ? 0 : 1; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void visitParameter(String name, int access) { | ||
| 27 | name = translateVariableName(parameterLvIndex, name); | ||
| 28 | parameterLvIndex += methodEntry.getDesc().getArgumentDescs().get(parameterIndex++).getSize(); | ||
| 29 | |||
| 30 | super.visitParameter(name, access); | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public void visitFieldInsn(int opcode, String owner, String name, String desc) { | ||
| 35 | FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); | ||
| 36 | FieldEntry translatedEntry = translator.translate(entry); | ||
| 37 | super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { | ||
| 42 | MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); | ||
| 43 | MethodEntry translatedEntry = translator.translate(entry); | ||
| 44 | super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) { | ||
| 49 | Object[] translatedLocals = this.getTranslatedFrame(locals, localCount); | ||
| 50 | Object[] translatedStack = this.getTranslatedFrame(stack, stackCount); | ||
| 51 | super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack); | ||
| 52 | } | ||
| 53 | |||
| 54 | private Object[] getTranslatedFrame(Object[] array, int count) { | ||
| 55 | if (array == null) { | ||
| 56 | return null; | ||
| 57 | } | ||
| 58 | for (int i = 0; i < count; i++) { | ||
| 59 | Object object = array[i]; | ||
| 60 | if (object instanceof String) { | ||
| 61 | String type = (String) object; | ||
| 62 | array[i] = translator.translate(new ClassEntry(type)).getFullName(); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | return array; | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public AnnotationVisitor visitAnnotation(String desc, boolean visible) { | ||
| 70 | TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 71 | AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); | ||
| 72 | return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); | ||
| 73 | } | ||
| 74 | |||
| 75 | @Override | ||
| 76 | public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { | ||
| 77 | TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 78 | AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible); | ||
| 79 | return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); | ||
| 80 | } | ||
| 81 | |||
| 82 | @Override | ||
| 83 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { | ||
| 84 | TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); | ||
| 85 | AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible); | ||
| 86 | return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public void visitTypeInsn(int opcode, String type) { | ||
| 91 | ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); | ||
| 92 | super.visitTypeInsn(opcode, translatedEntry.getFullName()); | ||
| 93 | } | ||
| 94 | |||
| 95 | @Override | ||
| 96 | public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { | ||
| 97 | MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc)); | ||
| 98 | Object[] translatedBsmArgs = new Object[bsmArgs.length]; | ||
| 99 | for (int i = 0; i < bsmArgs.length; i++) { | ||
| 100 | translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]); | ||
| 101 | } | ||
| 102 | super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs); | ||
| 103 | } | ||
| 104 | |||
| 105 | @Override | ||
| 106 | public void visitLdcInsn(Object cst) { | ||
| 107 | super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst)); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Override | ||
| 111 | public void visitMultiANewArrayInsn(String desc, int dims) { | ||
| 112 | super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims); | ||
| 113 | } | ||
| 114 | |||
| 115 | @Override | ||
| 116 | public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { | ||
| 117 | if (type != null) { | ||
| 118 | ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); | ||
| 119 | super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName()); | ||
| 120 | } else { | ||
| 121 | super.visitTryCatchBlock(start, end, handler, type); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | @Override | ||
| 126 | public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { | ||
| 127 | signature = translator.translate(Signature.createTypedSignature(signature)).toString(); | ||
| 128 | name = translateVariableName(index, name); | ||
| 129 | desc = translator.translate(new TypeDescriptor(desc)).toString(); | ||
| 130 | |||
| 131 | super.visitLocalVariable(name, desc, signature, start, end, index); | ||
| 132 | } | ||
| 133 | |||
| 134 | private String translateVariableName(int index, String name) { | ||
| 135 | LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, "", true,null); | ||
| 136 | LocalVariableEntry translatedEntry = translator.translate(entry); | ||
| 137 | String translatedName = translatedEntry.getName(); | ||
| 138 | |||
| 139 | if (!translatedName.isEmpty()) { | ||
| 140 | return translatedName; | ||
| 141 | } | ||
| 142 | |||
| 143 | return name; | ||
| 144 | } | ||
| 145 | } | ||
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java deleted file mode 100644 index eebd650..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java +++ /dev/null | |||
| @@ -1,129 +0,0 @@ | |||
| 1 | package cuchaz.enigma.bytecode.translators; | ||
| 2 | |||
| 3 | import cuchaz.enigma.utils.Utils; | ||
| 4 | import org.objectweb.asm.signature.SignatureVisitor; | ||
| 5 | |||
| 6 | import java.util.Stack; | ||
| 7 | import java.util.function.Function; | ||
| 8 | |||
| 9 | public class TranslationSignatureVisitor extends SignatureVisitor { | ||
| 10 | private final Function<String, String> remapper; | ||
| 11 | |||
| 12 | private final SignatureVisitor sv; | ||
| 13 | private final Stack<String> classStack = new Stack<>(); | ||
| 14 | |||
| 15 | public TranslationSignatureVisitor(Function<String, String> remapper, SignatureVisitor sv) { | ||
| 16 | super(Utils.ASM_VERSION); | ||
| 17 | this.remapper = remapper; | ||
| 18 | this.sv = sv; | ||
| 19 | } | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public void visitClassType(String name) { | ||
| 23 | classStack.push(name); | ||
| 24 | String translatedEntry = this.remapper.apply(name); | ||
| 25 | this.sv.visitClassType(translatedEntry); | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void visitInnerClassType(String name) { | ||
| 30 | String lastClass = classStack.pop(); | ||
| 31 | if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not | ||
| 32 | name = lastClass+"$"+name; | ||
| 33 | } | ||
| 34 | String translatedEntry = this.remapper.apply(name); | ||
| 35 | if (translatedEntry.contains("/")){ | ||
| 36 | translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1); | ||
| 37 | } | ||
| 38 | if (translatedEntry.contains("$")){ | ||
| 39 | translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1); | ||
| 40 | } | ||
| 41 | this.sv.visitInnerClassType(translatedEntry); | ||
| 42 | } | ||
| 43 | |||
| 44 | @Override | ||
| 45 | public void visitFormalTypeParameter(String name) { | ||
| 46 | this.sv.visitFormalTypeParameter(name); | ||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | public void visitTypeVariable(String name) { | ||
| 51 | this.sv.visitTypeVariable(name); | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public SignatureVisitor visitArrayType() { | ||
| 56 | this.sv.visitArrayType(); | ||
| 57 | return this; | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public void visitBaseType(char descriptor) { | ||
| 62 | this.sv.visitBaseType(descriptor); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Override | ||
| 66 | public SignatureVisitor visitClassBound() { | ||
| 67 | this.sv.visitClassBound(); | ||
| 68 | return this; | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public SignatureVisitor visitExceptionType() { | ||
| 73 | this.sv.visitExceptionType(); | ||
| 74 | return this; | ||
| 75 | } | ||
| 76 | |||
| 77 | @Override | ||
| 78 | public SignatureVisitor visitInterface() { | ||
| 79 | this.sv.visitInterface(); | ||
| 80 | return this; | ||
| 81 | } | ||
| 82 | |||
| 83 | @Override | ||
| 84 | public SignatureVisitor visitInterfaceBound() { | ||
| 85 | this.sv.visitInterfaceBound(); | ||
| 86 | return this; | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public SignatureVisitor visitParameterType() { | ||
| 91 | this.sv.visitParameterType(); | ||
| 92 | return this; | ||
| 93 | } | ||
| 94 | |||
| 95 | @Override | ||
| 96 | public SignatureVisitor visitReturnType() { | ||
| 97 | this.sv.visitReturnType(); | ||
| 98 | return this; | ||
| 99 | } | ||
| 100 | |||
| 101 | @Override | ||
| 102 | public SignatureVisitor visitSuperclass() { | ||
| 103 | this.sv.visitSuperclass(); | ||
| 104 | return this; | ||
| 105 | } | ||
| 106 | |||
| 107 | @Override | ||
| 108 | public void visitTypeArgument() { | ||
| 109 | this.sv.visitTypeArgument(); | ||
| 110 | } | ||
| 111 | |||
| 112 | @Override | ||
| 113 | public SignatureVisitor visitTypeArgument(char wildcard) { | ||
| 114 | this.sv.visitTypeArgument(wildcard); | ||
| 115 | return this; | ||
| 116 | } | ||
| 117 | |||
| 118 | @Override | ||
| 119 | public void visitEnd() { | ||
| 120 | this.sv.visitEnd(); | ||
| 121 | if (!classStack.empty()) | ||
| 122 | classStack.pop(); | ||
| 123 | } | ||
| 124 | |||
| 125 | @Override | ||
| 126 | public String toString() { | ||
| 127 | return this.sv.toString(); | ||
| 128 | } | ||
| 129 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java deleted file mode 100644 index 9d238e3..0000000 --- a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java +++ /dev/null | |||
| @@ -1,77 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Enigma; | ||
| 4 | import cuchaz.enigma.EnigmaProject; | ||
| 5 | import cuchaz.enigma.ProgressListener; | ||
| 6 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 8 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 9 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 10 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 12 | |||
| 13 | import java.nio.file.Path; | ||
| 14 | import java.util.Set; | ||
| 15 | import java.util.stream.Collectors; | ||
| 16 | |||
| 17 | public class CheckMappingsCommand extends Command { | ||
| 18 | |||
| 19 | public CheckMappingsCommand() { | ||
| 20 | super("checkmappings"); | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public String getUsage() { | ||
| 25 | return "<in jar> <mappings file>"; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public boolean isValidArgument(int length) { | ||
| 30 | return length == 2; | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public void run(String... args) throws Exception { | ||
| 35 | Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath(); | ||
| 36 | Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); | ||
| 37 | |||
| 38 | Enigma enigma = Enigma.create(); | ||
| 39 | |||
| 40 | System.out.println("Reading JAR..."); | ||
| 41 | |||
| 42 | EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none()); | ||
| 43 | |||
| 44 | System.out.println("Reading mappings..."); | ||
| 45 | |||
| 46 | MappingFormat format = chooseEnigmaFormat(fileMappings); | ||
| 47 | MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); | ||
| 48 | |||
| 49 | EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.none(), saveParameters); | ||
| 50 | project.setMappings(mappings); | ||
| 51 | |||
| 52 | JarIndex idx = project.getJarIndex(); | ||
| 53 | |||
| 54 | boolean error = false; | ||
| 55 | |||
| 56 | for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) { | ||
| 57 | long packages = partition.stream() | ||
| 58 | .map(project.getMapper()::deobfuscate) | ||
| 59 | .map(ClassEntry::getPackageName) | ||
| 60 | .distinct() | ||
| 61 | .count(); | ||
| 62 | if (packages > 1) { | ||
| 63 | error = true; | ||
| 64 | System.err.println("ERROR: Must be in one package:\n" + partition.stream() | ||
| 65 | .map(project.getMapper()::deobfuscate) | ||
| 66 | .map(ClassEntry::toString) | ||
| 67 | .sorted() | ||
| 68 | .collect(Collectors.joining("\n")) | ||
| 69 | ); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | if (error) { | ||
| 74 | throw new IllegalStateException("Errors in package visibility detected, see SysErr above"); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java deleted file mode 100644 index 09dd321..0000000 --- a/src/main/java/cuchaz/enigma/command/Command.java +++ /dev/null | |||
| @@ -1,154 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Enigma; | ||
| 4 | import cuchaz.enigma.EnigmaProject; | ||
| 5 | import cuchaz.enigma.ProgressListener; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 8 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 9 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 10 | |||
| 11 | import java.io.File; | ||
| 12 | import java.nio.file.Files; | ||
| 13 | import java.nio.file.Path; | ||
| 14 | import java.nio.file.Paths; | ||
| 15 | |||
| 16 | import com.google.common.io.MoreFiles; | ||
| 17 | |||
| 18 | public abstract class Command { | ||
| 19 | public final String name; | ||
| 20 | |||
| 21 | protected Command(String name) { | ||
| 22 | this.name = name; | ||
| 23 | } | ||
| 24 | |||
| 25 | public abstract String getUsage(); | ||
| 26 | |||
| 27 | public abstract boolean isValidArgument(int length); | ||
| 28 | |||
| 29 | public abstract void run(String... args) throws Exception; | ||
| 30 | |||
| 31 | protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception { | ||
| 32 | ProgressListener progress = new ConsoleProgressListener(); | ||
| 33 | |||
| 34 | Enigma enigma = Enigma.create(); | ||
| 35 | |||
| 36 | System.out.println("Reading jar..."); | ||
| 37 | EnigmaProject project = enigma.openJar(fileJarIn, progress); | ||
| 38 | |||
| 39 | if (fileMappings != null) { | ||
| 40 | System.out.println("Reading mappings..."); | ||
| 41 | |||
| 42 | MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); | ||
| 43 | EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress, saveParameters); | ||
| 44 | |||
| 45 | project.setMappings(mappings); | ||
| 46 | } | ||
| 47 | |||
| 48 | return project; | ||
| 49 | } | ||
| 50 | |||
| 51 | protected static MappingFormat chooseEnigmaFormat(Path path) { | ||
| 52 | if (Files.isDirectory(path)) { | ||
| 53 | return MappingFormat.ENIGMA_DIRECTORY; | ||
| 54 | } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) { | ||
| 55 | return MappingFormat.ENIGMA_ZIP; | ||
| 56 | } else { | ||
| 57 | return MappingFormat.ENIGMA_FILE; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | protected static File getWritableFile(String path) { | ||
| 62 | if (path == null) { | ||
| 63 | return null; | ||
| 64 | } | ||
| 65 | File file = new File(path).getAbsoluteFile(); | ||
| 66 | File dir = file.getParentFile(); | ||
| 67 | if (dir == null) { | ||
| 68 | throw new IllegalArgumentException("Cannot write file: " + path); | ||
| 69 | } | ||
| 70 | // quick fix to avoid stupid stuff in Gradle code | ||
| 71 | if (!dir.isDirectory()) { | ||
| 72 | dir.mkdirs(); | ||
| 73 | } | ||
| 74 | return file; | ||
| 75 | } | ||
| 76 | |||
| 77 | protected static File getWritableFolder(String path) { | ||
| 78 | if (path == null) { | ||
| 79 | return null; | ||
| 80 | } | ||
| 81 | File dir = new File(path).getAbsoluteFile(); | ||
| 82 | if (!dir.exists()) { | ||
| 83 | throw new IllegalArgumentException("Cannot write to folder: " + dir); | ||
| 84 | } | ||
| 85 | return dir; | ||
| 86 | } | ||
| 87 | |||
| 88 | protected static File getReadableFile(String path) { | ||
| 89 | if (path == null) { | ||
| 90 | return null; | ||
| 91 | } | ||
| 92 | File file = new File(path).getAbsoluteFile(); | ||
| 93 | if (!file.exists()) { | ||
| 94 | throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); | ||
| 95 | } | ||
| 96 | return file; | ||
| 97 | } | ||
| 98 | |||
| 99 | protected static Path getReadablePath(String path) { | ||
| 100 | if (path == null) { | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | Path file = Paths.get(path).toAbsolutePath(); | ||
| 104 | if (!Files.exists(file)) { | ||
| 105 | throw new IllegalArgumentException("Cannot find file: " + file.toString()); | ||
| 106 | } | ||
| 107 | return file; | ||
| 108 | } | ||
| 109 | |||
| 110 | protected static String getArg(String[] args, int i, String name, boolean required) { | ||
| 111 | if (i >= args.length) { | ||
| 112 | if (required) { | ||
| 113 | throw new IllegalArgumentException(name + " is required"); | ||
| 114 | } else { | ||
| 115 | return null; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | return args[i]; | ||
| 119 | } | ||
| 120 | |||
| 121 | public static class ConsoleProgressListener implements ProgressListener { | ||
| 122 | |||
| 123 | private static final int ReportTime = 5000; // 5s | ||
| 124 | |||
| 125 | private int totalWork; | ||
| 126 | private long startTime; | ||
| 127 | private long lastReportTime; | ||
| 128 | |||
| 129 | @Override | ||
| 130 | public void init(int totalWork, String title) { | ||
| 131 | this.totalWork = totalWork; | ||
| 132 | this.startTime = System.currentTimeMillis(); | ||
| 133 | this.lastReportTime = this.startTime; | ||
| 134 | System.out.println(title); | ||
| 135 | } | ||
| 136 | |||
| 137 | @Override | ||
| 138 | public void step(int numDone, String message) { | ||
| 139 | long now = System.currentTimeMillis(); | ||
| 140 | boolean isLastUpdate = numDone == this.totalWork; | ||
| 141 | boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; | ||
| 142 | |||
| 143 | if (shouldReport) { | ||
| 144 | int percent = numDone * 100 / this.totalWork; | ||
| 145 | System.out.println(String.format("\tProgress: %3d%%", percent)); | ||
| 146 | this.lastReportTime = now; | ||
| 147 | } | ||
| 148 | if (isLastUpdate) { | ||
| 149 | double elapsedSeconds = (now - this.startTime) / 1000.0; | ||
| 150 | System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java deleted file mode 100644 index f57f1fa..0000000 --- a/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.mapping.MappingFileNameFormat; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | |||
| 10 | import java.io.IOException; | ||
| 11 | import java.nio.file.Path; | ||
| 12 | import java.nio.file.Paths; | ||
| 13 | |||
| 14 | public class ComposeMappingsCommand extends Command { | ||
| 15 | public ComposeMappingsCommand() { | ||
| 16 | super("compose-mappings"); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public String getUsage() { | ||
| 21 | return "<left-format> <left> <right-format> <right> <result-format> <result> <keep-mode>"; | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public boolean isValidArgument(int length) { | ||
| 26 | return length == 7; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void run(String... args) throws IOException, MappingParseException { | ||
| 31 | MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); | ||
| 32 | |||
| 33 | EntryTree<EntryMapping> left = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); | ||
| 34 | EntryTree<EntryMapping> right = MappingCommandsUtil.read(args[2], Paths.get(args[3]), saveParameters); | ||
| 35 | EntryTree<EntryMapping> result = MappingCommandsUtil.compose(left, right, args[6].equals("left") || args[6].equals("both"), args[6].equals("right") || args[6].equals("both")); | ||
| 36 | |||
| 37 | Path output = Paths.get(args[5]); | ||
| 38 | Utils.delete(output); | ||
| 39 | MappingCommandsUtil.write(result, args[4], output, saveParameters); | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java deleted file mode 100644 index 689df02..0000000 --- a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.mapping.MappingFileNameFormat; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | |||
| 10 | import java.io.IOException; | ||
| 11 | import java.nio.file.Path; | ||
| 12 | import java.nio.file.Paths; | ||
| 13 | |||
| 14 | public class ConvertMappingsCommand extends Command { | ||
| 15 | public ConvertMappingsCommand() { | ||
| 16 | super("convert-mappings"); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public String getUsage() { | ||
| 21 | return "<source-format> <source> <result-format> <result>"; | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public boolean isValidArgument(int length) { | ||
| 26 | return length == 4; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void run(String... args) throws IOException, MappingParseException { | ||
| 31 | MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); | ||
| 32 | |||
| 33 | EntryTree<EntryMapping> mappings = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); | ||
| 34 | |||
| 35 | Path output = Paths.get(args[3]); | ||
| 36 | Utils.delete(output); | ||
| 37 | MappingCommandsUtil.write(mappings, args[2], output, saveParameters); | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java deleted file mode 100644 index 3d15dac..0000000 --- a/src/main/java/cuchaz/enigma/command/DecompileCommand.java +++ /dev/null | |||
| @@ -1,54 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.source.DecompilerService; | ||
| 6 | import cuchaz.enigma.source.Decompilers; | ||
| 7 | |||
| 8 | import java.lang.reflect.Field; | ||
| 9 | import java.nio.file.Path; | ||
| 10 | import java.util.Locale; | ||
| 11 | |||
| 12 | public class DecompileCommand extends Command { | ||
| 13 | |||
| 14 | public DecompileCommand() { | ||
| 15 | super("decompile"); | ||
| 16 | } | ||
| 17 | |||
| 18 | @Override | ||
| 19 | public String getUsage() { | ||
| 20 | return "<decompiler> <in jar> <out folder> [<mappings file>]"; | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public boolean isValidArgument(int length) { | ||
| 25 | return length == 2 || length == 3; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void run(String... args) throws Exception { | ||
| 30 | String decompilerName = getArg(args, 1, "decompiler", true); | ||
| 31 | Path fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)).toPath(); | ||
| 32 | Path fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)).toPath(); | ||
| 33 | Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); | ||
| 34 | |||
| 35 | DecompilerService decompilerService; | ||
| 36 | |||
| 37 | try { | ||
| 38 | Field decompilerField = Decompilers.class.getField(decompilerName.toUpperCase(Locale.ROOT)); | ||
| 39 | decompilerService = (DecompilerService) decompilerField.get(null); | ||
| 40 | } catch (NoSuchFieldException e) { | ||
| 41 | System.err.println("Decompiler not found."); | ||
| 42 | return; | ||
| 43 | } | ||
| 44 | |||
| 45 | EnigmaProject project = openProject(fileJarIn, fileMappings); | ||
| 46 | |||
| 47 | ProgressListener progress = new ConsoleProgressListener(); | ||
| 48 | |||
| 49 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | ||
| 50 | EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); | ||
| 51 | |||
| 52 | source.write(fileJarOut, progress); | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java deleted file mode 100644 index b0d2a7d..0000000 --- a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java +++ /dev/null | |||
| @@ -1,37 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | |||
| 6 | import java.nio.file.Path; | ||
| 7 | |||
| 8 | public class DeobfuscateCommand extends Command { | ||
| 9 | |||
| 10 | public DeobfuscateCommand() { | ||
| 11 | super("deobfuscate"); | ||
| 12 | } | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public String getUsage() { | ||
| 16 | return "<in jar> <out jar> [<mappings file>]"; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public boolean isValidArgument(int length) { | ||
| 21 | return length == 2 || length == 3; | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void run(String... args) throws Exception { | ||
| 26 | Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true)); | ||
| 27 | Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath(); | ||
| 28 | Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); | ||
| 29 | |||
| 30 | EnigmaProject project = openProject(fileJarIn, fileMappings); | ||
| 31 | |||
| 32 | ProgressListener progress = new ConsoleProgressListener(); | ||
| 33 | |||
| 34 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | ||
| 35 | jar.write(fileJarOut, progress); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java deleted file mode 100644 index cd11e2e..0000000 --- a/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.mapping.MappingFileNameFormat; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | |||
| 10 | import java.io.IOException; | ||
| 11 | import java.nio.file.Path; | ||
| 12 | import java.nio.file.Paths; | ||
| 13 | |||
| 14 | public class InvertMappingsCommand extends Command { | ||
| 15 | public InvertMappingsCommand() { | ||
| 16 | super("invert-mappings"); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public String getUsage() { | ||
| 21 | return "<source-format> <source> <result-format> <result>"; | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public boolean isValidArgument(int length) { | ||
| 26 | return length == 4; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void run(String... args) throws IOException, MappingParseException { | ||
| 31 | MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); | ||
| 32 | |||
| 33 | EntryTree<EntryMapping> source = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); | ||
| 34 | EntryTree<EntryMapping> result = MappingCommandsUtil.invert(source); | ||
| 35 | |||
| 36 | Path output = Paths.get(args[3]); | ||
| 37 | Utils.delete(output); | ||
| 38 | MappingCommandsUtil.write(result, args[2], output, saveParameters); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java b/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java deleted file mode 100644 index eb8d5dc..0000000 --- a/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java +++ /dev/null | |||
| @@ -1,69 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.analysis.ClassCache; | ||
| 5 | import cuchaz.enigma.analysis.index.BridgeMethodIndex; | ||
| 6 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 7 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 8 | import cuchaz.enigma.translation.MappingTranslator; | ||
| 9 | import cuchaz.enigma.translation.Translator; | ||
| 10 | import cuchaz.enigma.translation.mapping.*; | ||
| 11 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 12 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 13 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 15 | import cuchaz.enigma.utils.Utils; | ||
| 16 | |||
| 17 | import java.io.IOException; | ||
| 18 | import java.nio.file.Path; | ||
| 19 | import java.nio.file.Paths; | ||
| 20 | import java.util.Map; | ||
| 21 | |||
| 22 | public class MapSpecializedMethodsCommand extends Command { | ||
| 23 | public MapSpecializedMethodsCommand() { | ||
| 24 | super("map-specialized-methods"); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public String getUsage() { | ||
| 29 | return "<jar> <source-format> <source> <result-format> <result>"; | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public boolean isValidArgument(int length) { | ||
| 34 | return length == 5; | ||
| 35 | } | ||
| 36 | |||
| 37 | @Override | ||
| 38 | public void run(String... args) throws IOException, MappingParseException { | ||
| 39 | run(Paths.get(args[0]), args[1], Paths.get(args[2]), args[3], Paths.get(args[4])); | ||
| 40 | } | ||
| 41 | |||
| 42 | public static void run(Path jar, String sourceFormat, Path sourcePath, String resultFormat, Path output) throws IOException, MappingParseException { | ||
| 43 | MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); | ||
| 44 | EntryTree<EntryMapping> source = MappingCommandsUtil.read(sourceFormat, sourcePath, saveParameters); | ||
| 45 | EntryTree<EntryMapping> result = new HashEntryTree<>(); | ||
| 46 | ClassCache classCache = ClassCache.of(jar); | ||
| 47 | JarIndex jarIndex = classCache.index(ProgressListener.none()); | ||
| 48 | BridgeMethodIndex bridgeMethodIndex = jarIndex.getBridgeMethodIndex(); | ||
| 49 | Translator translator = new MappingTranslator(source, jarIndex.getEntryResolver()); | ||
| 50 | |||
| 51 | // Copy all non-specialized methods | ||
| 52 | for (EntryTreeNode<EntryMapping> node : source) { | ||
| 53 | if (!(node.getEntry() instanceof MethodEntry) || !bridgeMethodIndex.isSpecializedMethod((MethodEntry) node.getEntry())) { | ||
| 54 | result.insert(node.getEntry(), node.getValue()); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | // Add correct mappings for specialized methods | ||
| 59 | for (Map.Entry<MethodEntry, MethodEntry> entry : bridgeMethodIndex.getBridgeToSpecialized().entrySet()) { | ||
| 60 | MethodEntry bridge = entry.getKey(); | ||
| 61 | MethodEntry specialized = entry.getValue(); | ||
| 62 | String name = translator.translate(bridge).getName(); | ||
| 63 | result.insert(specialized, new EntryMapping(name)); | ||
| 64 | } | ||
| 65 | |||
| 66 | Utils.delete(output); | ||
| 67 | MappingCommandsUtil.write(result, resultFormat, output, saveParameters); | ||
| 68 | } | ||
| 69 | } | ||
diff --git a/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java b/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java deleted file mode 100644 index fc7afbc..0000000 --- a/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java +++ /dev/null | |||
| @@ -1,148 +0,0 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 5 | import cuchaz.enigma.translation.MappingTranslator; | ||
| 6 | import cuchaz.enigma.translation.Translator; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 8 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 9 | import cuchaz.enigma.translation.mapping.VoidEntryResolver; | ||
| 10 | import cuchaz.enigma.translation.mapping.serde.*; | ||
| 11 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 12 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 13 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 18 | |||
| 19 | import java.io.IOException; | ||
| 20 | import java.nio.file.Files; | ||
| 21 | import java.nio.file.Path; | ||
| 22 | import java.util.HashSet; | ||
| 23 | import java.util.Set; | ||
| 24 | |||
| 25 | public final class MappingCommandsUtil { | ||
| 26 | private MappingCommandsUtil() {} | ||
| 27 | |||
| 28 | public static EntryTree<EntryMapping> invert(EntryTree<EntryMapping> mappings) { | ||
| 29 | Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); | ||
| 30 | EntryTree<EntryMapping> result = new HashEntryTree<>(); | ||
| 31 | |||
| 32 | for (EntryTreeNode<EntryMapping> node : mappings) { | ||
| 33 | Entry<?> leftEntry = node.getEntry(); | ||
| 34 | EntryMapping leftMapping = node.getValue(); | ||
| 35 | |||
| 36 | if (!(leftEntry instanceof ClassEntry || leftEntry instanceof MethodEntry || leftEntry instanceof FieldEntry)) { | ||
| 37 | result.insert(translator.translate(leftEntry), leftMapping); | ||
| 38 | continue; | ||
| 39 | } | ||
| 40 | |||
| 41 | Entry<?> rightEntry = translator.translate(leftEntry); | ||
| 42 | |||
| 43 | result.insert(rightEntry, leftMapping == null ? null : new EntryMapping(leftEntry.getName())); // TODO: leftMapping.withName once javadoc PR is merged | ||
| 44 | } | ||
| 45 | |||
| 46 | return result; | ||
| 47 | } | ||
| 48 | |||
| 49 | public static EntryTree<EntryMapping> compose(EntryTree<EntryMapping> left, EntryTree<EntryMapping> right, boolean keepLeftOnly, boolean keepRightOnly) { | ||
| 50 | Translator leftTranslator = new MappingTranslator(left, VoidEntryResolver.INSTANCE); | ||
| 51 | EntryTree<EntryMapping> result = new HashEntryTree<>(); | ||
| 52 | Set<Entry<?>> addedMappings = new HashSet<>(); | ||
| 53 | |||
| 54 | for (EntryTreeNode<EntryMapping> node : left) { | ||
| 55 | Entry<?> leftEntry = node.getEntry(); | ||
| 56 | EntryMapping leftMapping = node.getValue(); | ||
| 57 | |||
| 58 | Entry<?> rightEntry = leftTranslator.translate(leftEntry); | ||
| 59 | |||
| 60 | EntryMapping rightMapping = right.get(rightEntry); | ||
| 61 | if (rightMapping != null) { | ||
| 62 | result.insert(leftEntry, rightMapping); | ||
| 63 | addedMappings.add(rightEntry); | ||
| 64 | } else if (keepLeftOnly) { | ||
| 65 | result.insert(leftEntry, leftMapping); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | if (keepRightOnly) { | ||
| 70 | Translator leftInverseTranslator = new MappingTranslator(invert(left), VoidEntryResolver.INSTANCE); | ||
| 71 | for (EntryTreeNode<EntryMapping> node : right) { | ||
| 72 | Entry<?> rightEntry = node.getEntry(); | ||
| 73 | EntryMapping rightMapping = node.getValue(); | ||
| 74 | |||
| 75 | if (!addedMappings.contains(rightEntry)) { | ||
| 76 | result.insert(leftInverseTranslator.translate(rightEntry), rightMapping); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | return result; | ||
| 81 | } | ||
| 82 | |||
| 83 | public static EntryTree<EntryMapping> read(String type, Path path, MappingSaveParameters saveParameters) throws MappingParseException, IOException { | ||
| 84 | if (type.equals("enigma")) { | ||
| 85 | return (Files.isDirectory(path) ? EnigmaMappingsReader.DIRECTORY : EnigmaMappingsReader.ZIP).read(path, ProgressListener.none(), saveParameters); | ||
| 86 | } | ||
| 87 | |||
| 88 | if (type.equals("tiny")) { | ||
| 89 | return TinyMappingsReader.INSTANCE.read(path, ProgressListener.none(), saveParameters); | ||
| 90 | } | ||
| 91 | |||
| 92 | MappingFormat format = null; | ||
| 93 | try { | ||
| 94 | format = MappingFormat.valueOf(type.toUpperCase()); | ||
| 95 | } catch (IllegalArgumentException ignored) { | ||
| 96 | if (type.equals("tinyv2")) { | ||
| 97 | format = MappingFormat.TINY_V2; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | if (format != null) { | ||
| 102 | return format.getReader().read(path, ProgressListener.none(), saveParameters); | ||
| 103 | } | ||
| 104 | |||
| 105 | throw new IllegalArgumentException("no reader for " + type); | ||
| 106 | } | ||
| 107 | |||
| 108 | public static void write(EntryTree<EntryMapping> mappings, String type, Path path, MappingSaveParameters saveParameters) { | ||
| 109 | if (type.equals("enigma")) { | ||
| 110 | EnigmaMappingsWriter.DIRECTORY.write(mappings, path, ProgressListener.none(), saveParameters); | ||
| 111 | return; | ||
| 112 | } | ||
| 113 | |||
| 114 | if (type.startsWith("tinyv2:") || type.startsWith("tiny_v2:")) { | ||
| 115 | String[] split = type.split(":"); | ||
| 116 | |||
| 117 | if (split.length != 3) { | ||
| 118 | throw new IllegalArgumentException("specify column names as 'tinyv2:from_namespace:to_namespace'"); | ||
| 119 | } | ||
| 120 | |||
| 121 | new TinyV2Writer(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters); | ||
| 122 | return; | ||
| 123 | } | ||
| 124 | |||
| 125 | if (type.startsWith("tiny:")) { | ||
| 126 | String[] split = type.split(":"); | ||
| 127 | |||
| 128 | if (split.length != 3) { | ||
| 129 | throw new IllegalArgumentException("specify column names as 'tiny:from_column:to_column'"); | ||
| 130 | } | ||
| 131 | |||
| 132 | new TinyMappingsWriter(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters); | ||
| 133 | return; | ||
| 134 | } | ||
| 135 | |||
| 136 | MappingFormat format = null; | ||
| 137 | try { | ||
| 138 | format = MappingFormat.valueOf(type.toUpperCase()); | ||
| 139 | } catch (IllegalArgumentException ignored) {} | ||
| 140 | |||
| 141 | if (format != null) { | ||
| 142 | format.getWriter().write(mappings, path, ProgressListener.none(), saveParameters); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | |||
| 146 | throw new IllegalArgumentException("no writer for " + type); | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/main/java/cuchaz/enigma/config/Config.java b/src/main/java/cuchaz/enigma/config/Config.java deleted file mode 100644 index e116fce..0000000 --- a/src/main/java/cuchaz/enigma/config/Config.java +++ /dev/null | |||
| @@ -1,261 +0,0 @@ | |||
| 1 | package cuchaz.enigma.config; | ||
| 2 | |||
| 3 | import com.bulenkov.darcula.DarculaLaf; | ||
| 4 | import com.google.common.io.Files; | ||
| 5 | import com.google.gson.*; | ||
| 6 | import cuchaz.enigma.source.DecompilerService; | ||
| 7 | import cuchaz.enigma.source.Decompilers; | ||
| 8 | |||
| 9 | import cuchaz.enigma.utils.I18n; | ||
| 10 | |||
| 11 | import javax.swing.*; | ||
| 12 | import javax.swing.plaf.metal.MetalLookAndFeel; | ||
| 13 | import java.awt.*; | ||
| 14 | import java.awt.image.BufferedImage; | ||
| 15 | import java.io.File; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.lang.reflect.Type; | ||
| 18 | import java.nio.charset.Charset; | ||
| 19 | |||
| 20 | public class Config { | ||
| 21 | public static class AlphaColorEntry { | ||
| 22 | public Integer rgb; | ||
| 23 | public float alpha = 1.0f; | ||
| 24 | |||
| 25 | public AlphaColorEntry(Integer rgb, float alpha) { | ||
| 26 | this.rgb = rgb; | ||
| 27 | this.alpha = alpha; | ||
| 28 | } | ||
| 29 | |||
| 30 | public Color get() { | ||
| 31 | if (rgb == null) { | ||
| 32 | return new Color(0, 0, 0, 0); | ||
| 33 | } | ||
| 34 | |||
| 35 | Color baseColor = new Color(rgb); | ||
| 36 | return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha)); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | public enum LookAndFeel { | ||
| 41 | DEFAULT("Default"), | ||
| 42 | DARCULA("Darcula"), | ||
| 43 | SYSTEM("System"), | ||
| 44 | NONE("None (JVM default)"); | ||
| 45 | |||
| 46 | // the "JVM default" look and feel, get it at the beginning and store it so we can set it later | ||
| 47 | private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); | ||
| 48 | private final String name; | ||
| 49 | |||
| 50 | LookAndFeel(String name) { | ||
| 51 | this.name = name; | ||
| 52 | } | ||
| 53 | |||
| 54 | public String getName() { | ||
| 55 | return name; | ||
| 56 | } | ||
| 57 | |||
| 58 | public void setGlobalLAF() { | ||
| 59 | try { | ||
| 60 | switch (this) { | ||
| 61 | case NONE: | ||
| 62 | UIManager.setLookAndFeel(NONE_LAF); | ||
| 63 | break; | ||
| 64 | case DEFAULT: | ||
| 65 | UIManager.setLookAndFeel(new MetalLookAndFeel()); | ||
| 66 | break; | ||
| 67 | case DARCULA: | ||
| 68 | UIManager.setLookAndFeel(new DarculaLaf()); | ||
| 69 | break; | ||
| 70 | case SYSTEM: | ||
| 71 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); | ||
| 72 | } | ||
| 73 | } catch (Exception e){ | ||
| 74 | throw new Error("Failed to set global look and feel", e); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | public static boolean isDarkLaf() { | ||
| 79 | // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel | ||
| 80 | JPanel panel = new JPanel(); | ||
| 81 | panel.setSize(new Dimension(10, 10)); | ||
| 82 | panel.doLayout(); | ||
| 83 | |||
| 84 | BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB); | ||
| 85 | panel.printAll(image.getGraphics()); | ||
| 86 | |||
| 87 | Color c = new Color(image.getRGB(0, 0)); | ||
| 88 | |||
| 89 | // convert the color we got to grayscale | ||
| 90 | int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); | ||
| 91 | return b < 85; | ||
| 92 | } | ||
| 93 | |||
| 94 | public void apply(Config config) { | ||
| 95 | boolean isDark = this == LookAndFeel.DARCULA || isDarkLaf(); | ||
| 96 | if (!isDark) {//Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139 | ||
| 97 | config.lineNumbersForeground = 0x333300; | ||
| 98 | config.lineNumbersBackground = 0xEEEEFF; | ||
| 99 | config.lineNumbersSelected = 0xCCCCEE; | ||
| 100 | config.obfuscatedColor = new AlphaColorEntry(0xFFDCDC, 1.0f); | ||
| 101 | config.obfuscatedColorOutline = new AlphaColorEntry(0xA05050, 1.0f); | ||
| 102 | config.proposedColor = new AlphaColorEntry(0x000000, 0.075f); | ||
| 103 | config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f); | ||
| 104 | config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f); | ||
| 105 | config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f); | ||
| 106 | config.editorBackground = 0xFFFFFF; | ||
| 107 | config.highlightColor = 0x3333EE; | ||
| 108 | config.caretColor = 0x000000; | ||
| 109 | config.selectionHighlightColor = 0x000000; | ||
| 110 | config.stringColor = 0xCC6600; | ||
| 111 | config.numberColor = 0x999933; | ||
| 112 | config.operatorColor = 0x000000; | ||
| 113 | config.delimiterColor = 0x000000; | ||
| 114 | config.typeColor = 0x000000; | ||
| 115 | config.identifierColor = 0x000000; | ||
| 116 | config.defaultTextColor = 0x000000; | ||
| 117 | } else {//Based off colors found here: https://github.com/dracula/dracula-theme/ | ||
| 118 | config.lineNumbersForeground = 0xA4A4A3; | ||
| 119 | config.lineNumbersBackground = 0x313335; | ||
| 120 | config.lineNumbersSelected = 0x606366; | ||
| 121 | config.obfuscatedColor = new AlphaColorEntry(0xFF5555, 0.3f); | ||
| 122 | config.obfuscatedColorOutline = new AlphaColorEntry(0xFF5555, 0.5f); | ||
| 123 | config.deobfuscatedColor = new AlphaColorEntry(0x50FA7B, 0.3f); | ||
| 124 | config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f); | ||
| 125 | config.proposedColor = new AlphaColorEntry(0x606366, 0.3f); | ||
| 126 | config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f); | ||
| 127 | config.editorBackground = 0x282A36; | ||
| 128 | config.highlightColor = 0xFF79C6; | ||
| 129 | config.caretColor = 0xF8F8F2; | ||
| 130 | config.selectionHighlightColor = 0xF8F8F2; | ||
| 131 | config.stringColor = 0xF1FA8C; | ||
| 132 | config.numberColor = 0xBD93F9; | ||
| 133 | config.operatorColor = 0xF8F8F2; | ||
| 134 | config.delimiterColor = 0xF8F8F2; | ||
| 135 | config.typeColor = 0xF8F8F2; | ||
| 136 | config.identifierColor = 0xF8F8F2; | ||
| 137 | config.defaultTextColor = 0xF8F8F2; | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | public enum Decompiler { | ||
| 143 | PROCYON("Procyon", Decompilers.PROCYON), | ||
| 144 | CFR("CFR", Decompilers.CFR); | ||
| 145 | |||
| 146 | public final DecompilerService service; | ||
| 147 | public final String name; | ||
| 148 | |||
| 149 | Decompiler(String name, DecompilerService service) { | ||
| 150 | this.name = name; | ||
| 151 | this.service = service; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | private static final File DIR_HOME = new File(System.getProperty("user.home")); | ||
| 156 | private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma"); | ||
| 157 | private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json"); | ||
| 158 | private static final Config INSTANCE = new Config(); | ||
| 159 | |||
| 160 | private final transient Gson gson; // transient to exclude it from being exposed | ||
| 161 | |||
| 162 | public AlphaColorEntry obfuscatedColor; | ||
| 163 | public AlphaColorEntry obfuscatedColorOutline; | ||
| 164 | public AlphaColorEntry proposedColor; | ||
| 165 | public AlphaColorEntry proposedColorOutline; | ||
| 166 | public AlphaColorEntry deobfuscatedColor; | ||
| 167 | public AlphaColorEntry deobfuscatedColorOutline; | ||
| 168 | |||
| 169 | public Integer editorBackground; | ||
| 170 | public Integer highlightColor; | ||
| 171 | public Integer caretColor; | ||
| 172 | public Integer selectionHighlightColor; | ||
| 173 | |||
| 174 | public Integer stringColor; | ||
| 175 | public Integer numberColor; | ||
| 176 | public Integer operatorColor; | ||
| 177 | public Integer delimiterColor; | ||
| 178 | public Integer typeColor; | ||
| 179 | public Integer identifierColor; | ||
| 180 | public Integer defaultTextColor; | ||
| 181 | |||
| 182 | public Integer lineNumbersBackground; | ||
| 183 | public Integer lineNumbersSelected; | ||
| 184 | public Integer lineNumbersForeground; | ||
| 185 | |||
| 186 | public String language = I18n.DEFAULT_LANGUAGE; | ||
| 187 | |||
| 188 | public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT; | ||
| 189 | |||
| 190 | public float scaleFactor = 1.0f; | ||
| 191 | |||
| 192 | public Decompiler decompiler = Decompiler.PROCYON; | ||
| 193 | |||
| 194 | private Config() { | ||
| 195 | gson = new GsonBuilder() | ||
| 196 | .registerTypeAdapter(Integer.class, new IntSerializer()) | ||
| 197 | .registerTypeAdapter(Integer.class, new IntDeserializer()) | ||
| 198 | .registerTypeAdapter(Config.class, (InstanceCreator<Config>) type -> this) | ||
| 199 | .setPrettyPrinting() | ||
| 200 | .create(); | ||
| 201 | try { | ||
| 202 | this.loadConfig(); | ||
| 203 | } catch (IOException ignored) { | ||
| 204 | try { | ||
| 205 | this.reset(); | ||
| 206 | } catch (IOException ignored1) { | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | public void loadConfig() throws IOException { | ||
| 212 | if (!ENIGMA_DIR.exists()) ENIGMA_DIR.mkdirs(); | ||
| 213 | File configFile = new File(ENIGMA_DIR, "config.json"); | ||
| 214 | boolean loaded = false; | ||
| 215 | |||
| 216 | if (configFile.exists()) { | ||
| 217 | try { | ||
| 218 | gson.fromJson(Files.asCharSource(configFile, Charset.defaultCharset()).read(), Config.class); | ||
| 219 | loaded = true; | ||
| 220 | } catch (Exception e) { | ||
| 221 | e.printStackTrace(); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | if (!loaded) { | ||
| 226 | this.reset(); | ||
| 227 | Files.touch(configFile); | ||
| 228 | } | ||
| 229 | saveConfig(); | ||
| 230 | } | ||
| 231 | |||
| 232 | public void saveConfig() throws IOException { | ||
| 233 | Files.asCharSink(CONFIG_FILE, Charset.defaultCharset()).write(gson.toJson(this)); | ||
| 234 | } | ||
| 235 | |||
| 236 | public void reset() throws IOException { | ||
| 237 | this.lookAndFeel = LookAndFeel.DEFAULT; | ||
| 238 | this.lookAndFeel.apply(this); | ||
| 239 | this.decompiler = Decompiler.PROCYON; | ||
| 240 | this.language = I18n.DEFAULT_LANGUAGE; | ||
| 241 | this.saveConfig(); | ||
| 242 | } | ||
| 243 | |||
| 244 | private static class IntSerializer implements JsonSerializer<Integer> { | ||
| 245 | @Override | ||
| 246 | public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) { | ||
| 247 | return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase()); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | private static class IntDeserializer implements JsonDeserializer<Integer> { | ||
| 252 | @Override | ||
| 253 | public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { | ||
| 254 | return (int) Long.parseLong(json.getAsString().replace("#", ""), 16); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | public static Config getInstance() { | ||
| 259 | return INSTANCE; | ||
| 260 | } | ||
| 261 | } | ||
diff --git a/src/main/java/cuchaz/enigma/config/Themes.java b/src/main/java/cuchaz/enigma/config/Themes.java deleted file mode 100644 index 547a420..0000000 --- a/src/main/java/cuchaz/enigma/config/Themes.java +++ /dev/null | |||
| @@ -1,48 +0,0 @@ | |||
| 1 | package cuchaz.enigma.config; | ||
| 2 | |||
| 3 | import java.awt.Font; | ||
| 4 | import java.io.IOException; | ||
| 5 | import java.lang.reflect.Field; | ||
| 6 | |||
| 7 | import javax.swing.SwingUtilities; | ||
| 8 | |||
| 9 | import com.github.swingdpi.UiDefaultsScaler; | ||
| 10 | import com.google.common.collect.ImmutableMap; | ||
| 11 | import cuchaz.enigma.gui.EnigmaSyntaxKit; | ||
| 12 | import cuchaz.enigma.gui.Gui; | ||
| 13 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | ||
| 14 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 15 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 16 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 17 | |||
| 18 | public class Themes { | ||
| 19 | |||
| 20 | public static void setLookAndFeel(Gui gui, Config.LookAndFeel lookAndFeel) { | ||
| 21 | Config.getInstance().lookAndFeel = lookAndFeel; | ||
| 22 | updateTheme(gui); | ||
| 23 | } | ||
| 24 | |||
| 25 | public static void updateTheme(Gui gui) { | ||
| 26 | Config config = Config.getInstance(); | ||
| 27 | config.lookAndFeel.setGlobalLAF(); | ||
| 28 | config.lookAndFeel.apply(config); | ||
| 29 | try { | ||
| 30 | config.saveConfig(); | ||
| 31 | } catch (IOException e) { | ||
| 32 | e.printStackTrace(); | ||
| 33 | } | ||
| 34 | EnigmaSyntaxKit.invalidate(); | ||
| 35 | DefaultSyntaxKit.initKit(); | ||
| 36 | DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); | ||
| 37 | gui.boxHighlightPainters = ImmutableMap.of( | ||
| 38 | TokenHighlightType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), | ||
| 39 | TokenHighlightType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), | ||
| 40 | TokenHighlightType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) | ||
| 41 | ); | ||
| 42 | gui.setEditorTheme(config.lookAndFeel); | ||
| 43 | SwingUtilities.updateComponentTreeUI(gui.getFrame()); | ||
| 44 | ScaleUtil.applyScaling(); | ||
| 45 | } | ||
| 46 | |||
| 47 | |||
| 48 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java deleted file mode 100644 index af105db..0000000 --- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java +++ /dev/null | |||
| @@ -1,28 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import javax.swing.text.DefaultCaret; | ||
| 15 | |||
| 16 | public class BrowserCaret extends DefaultCaret { | ||
| 17 | |||
| 18 | @Override | ||
| 19 | public boolean isSelectionVisible() { | ||
| 20 | return true; | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public boolean isVisible() { | ||
| 25 | return true; | ||
| 26 | } | ||
| 27 | |||
| 28 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java deleted file mode 100644 index a23e24c..0000000 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ /dev/null | |||
| @@ -1,532 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import java.awt.event.MouseAdapter; | ||
| 15 | import java.awt.event.MouseEvent; | ||
| 16 | import java.util.*; | ||
| 17 | |||
| 18 | import javax.annotation.Nullable; | ||
| 19 | import javax.swing.JOptionPane; | ||
| 20 | import javax.swing.JTree; | ||
| 21 | import javax.swing.event.CellEditorListener; | ||
| 22 | import javax.swing.event.ChangeEvent; | ||
| 23 | import javax.swing.tree.*; | ||
| 24 | |||
| 25 | import com.google.common.collect.ArrayListMultimap; | ||
| 26 | import com.google.common.collect.Lists; | ||
| 27 | import com.google.common.collect.Maps; | ||
| 28 | import com.google.common.collect.Multimap; | ||
| 29 | import cuchaz.enigma.gui.node.ClassSelectorClassNode; | ||
| 30 | import cuchaz.enigma.gui.node.ClassSelectorPackageNode; | ||
| 31 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 32 | import cuchaz.enigma.translation.Translator; | ||
| 33 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 34 | |||
| 35 | public class ClassSelector extends JTree { | ||
| 36 | |||
| 37 | public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); | ||
| 38 | |||
| 39 | private final GuiController controller; | ||
| 40 | |||
| 41 | private DefaultMutableTreeNode rootNodes; | ||
| 42 | private ClassSelectionListener selectionListener; | ||
| 43 | private RenameSelectionListener renameSelectionListener; | ||
| 44 | private Comparator<ClassEntry> comparator; | ||
| 45 | |||
| 46 | private final Map<ClassEntry, ClassEntry> displayedObfToDeobf = new HashMap<>(); | ||
| 47 | |||
| 48 | public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) { | ||
| 49 | this.comparator = comparator; | ||
| 50 | this.controller = gui.getController(); | ||
| 51 | |||
| 52 | // configure the tree control | ||
| 53 | setEditable(true); | ||
| 54 | setRootVisible(false); | ||
| 55 | setShowsRootHandles(false); | ||
| 56 | setModel(null); | ||
| 57 | |||
| 58 | // hook events | ||
| 59 | addMouseListener(new MouseAdapter() { | ||
| 60 | @Override | ||
| 61 | public void mouseClicked(MouseEvent event) { | ||
| 62 | if (selectionListener != null && event.getClickCount() == 2) { | ||
| 63 | // get the selected node | ||
| 64 | TreePath path = getSelectionPath(); | ||
| 65 | if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { | ||
| 66 | ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); | ||
| 67 | selectionListener.onSelectClass(node.getObfEntry()); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | }); | ||
| 72 | |||
| 73 | final JTree tree = this; | ||
| 74 | |||
| 75 | final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, | ||
| 76 | (DefaultTreeCellRenderer) tree.getCellRenderer()) { | ||
| 77 | @Override | ||
| 78 | public boolean isCellEditable(EventObject event) { | ||
| 79 | return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | this.setCellEditor(editor); | ||
| 83 | editor.addCellEditorListener(new CellEditorListener() { | ||
| 84 | @Override | ||
| 85 | public void editingStopped(ChangeEvent e) { | ||
| 86 | String data = editor.getCellEditorValue().toString(); | ||
| 87 | TreePath path = getSelectionPath(); | ||
| 88 | |||
| 89 | Object realPath = path.getLastPathComponent(); | ||
| 90 | if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { | ||
| 91 | DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; | ||
| 92 | TreeNode parentNode = node.getParent(); | ||
| 93 | if (parentNode == null) | ||
| 94 | return; | ||
| 95 | boolean allowEdit = true; | ||
| 96 | for (int i = 0; i < parentNode.getChildCount(); i++) { | ||
| 97 | TreeNode childNode = parentNode.getChildAt(i); | ||
| 98 | if (childNode != null && childNode.toString().equals(data) && childNode != node) { | ||
| 99 | allowEdit = false; | ||
| 100 | break; | ||
| 101 | } | ||
| 102 | } | ||
| 103 | if (allowEdit && renameSelectionListener != null) { | ||
| 104 | Object prevData = node.getUserObject(); | ||
| 105 | Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; | ||
| 106 | try { | ||
| 107 | renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); | ||
| 108 | node.setUserObject(objectData); // Make sure that it's modified | ||
| 109 | } catch (IllegalNameException ex) { | ||
| 110 | JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, | ||
| 111 | JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK"); | ||
| 112 | editor.cancelCellEditing(); | ||
| 113 | } | ||
| 114 | } else | ||
| 115 | editor.cancelCellEditing(); | ||
| 116 | } | ||
| 117 | |||
| 118 | } | ||
| 119 | |||
| 120 | @Override | ||
| 121 | public void editingCanceled(ChangeEvent e) { | ||
| 122 | // NOP | ||
| 123 | } | ||
| 124 | }); | ||
| 125 | // init defaults | ||
| 126 | this.selectionListener = null; | ||
| 127 | this.renameSelectionListener = null; | ||
| 128 | } | ||
| 129 | |||
| 130 | public boolean isDuplicate(Object[] nodes, String data) { | ||
| 131 | int count = 0; | ||
| 132 | |||
| 133 | for (Object node : nodes) { | ||
| 134 | if (node.toString().equals(data)) { | ||
| 135 | count++; | ||
| 136 | if (count == 2) | ||
| 137 | return true; | ||
| 138 | } | ||
| 139 | } | ||
| 140 | return false; | ||
| 141 | } | ||
| 142 | |||
| 143 | public void setSelectionListener(ClassSelectionListener val) { | ||
| 144 | this.selectionListener = val; | ||
| 145 | } | ||
| 146 | |||
| 147 | public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) { | ||
| 148 | this.renameSelectionListener = renameSelectionListener; | ||
| 149 | } | ||
| 150 | |||
| 151 | public void setClasses(Collection<ClassEntry> classEntries) { | ||
| 152 | displayedObfToDeobf.clear(); | ||
| 153 | |||
| 154 | List<StateEntry> state = getExpansionState(this); | ||
| 155 | if (classEntries == null) { | ||
| 156 | setModel(null); | ||
| 157 | return; | ||
| 158 | } | ||
| 159 | |||
| 160 | Translator translator = controller.project.getMapper().getDeobfuscator(); | ||
| 161 | |||
| 162 | // build the package names | ||
| 163 | Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap(); | ||
| 164 | for (ClassEntry obfClass : classEntries) { | ||
| 165 | ClassEntry deobfClass = translator.translate(obfClass); | ||
| 166 | packages.put(deobfClass.getPackageName(), null); | ||
| 167 | } | ||
| 168 | |||
| 169 | // sort the packages | ||
| 170 | List<String> sortedPackageNames = Lists.newArrayList(packages.keySet()); | ||
| 171 | sortedPackageNames.sort((a, b) -> | ||
| 172 | { | ||
| 173 | // I can never keep this rule straight when writing these damn things... | ||
| 174 | // a < b => -1, a == b => 0, a > b => +1 | ||
| 175 | |||
| 176 | if (b == null || a == null) { | ||
| 177 | return 0; | ||
| 178 | } | ||
| 179 | |||
| 180 | String[] aparts = a.split("/"); | ||
| 181 | String[] bparts = b.split("/"); | ||
| 182 | for (int i = 0; true; i++) { | ||
| 183 | if (i >= aparts.length) { | ||
| 184 | return -1; | ||
| 185 | } else if (i >= bparts.length) { | ||
| 186 | return 1; | ||
| 187 | } | ||
| 188 | |||
| 189 | int result = aparts[i].compareTo(bparts[i]); | ||
| 190 | if (result != 0) { | ||
| 191 | return result; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | }); | ||
| 195 | |||
| 196 | // create the rootNodes node and the package nodes | ||
| 197 | rootNodes = new DefaultMutableTreeNode(); | ||
| 198 | for (String packageName : sortedPackageNames) { | ||
| 199 | ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); | ||
| 200 | packages.put(packageName, node); | ||
| 201 | rootNodes.add(node); | ||
| 202 | } | ||
| 203 | |||
| 204 | // put the classes into packages | ||
| 205 | Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create(); | ||
| 206 | for (ClassEntry obfClass : classEntries) { | ||
| 207 | ClassEntry deobfClass = translator.translate(obfClass); | ||
| 208 | packagedClassEntries.put(deobfClass.getPackageName(), obfClass); | ||
| 209 | } | ||
| 210 | |||
| 211 | // build the class nodes | ||
| 212 | for (String packageName : packagedClassEntries.keySet()) { | ||
| 213 | // sort the class entries | ||
| 214 | List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); | ||
| 215 | classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2))); | ||
| 216 | |||
| 217 | // create the nodes in order | ||
| 218 | for (ClassEntry obfClass : classEntriesInPackage) { | ||
| 219 | ClassEntry deobfClass = translator.translate(obfClass); | ||
| 220 | ClassSelectorPackageNode node = packages.get(packageName); | ||
| 221 | ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass); | ||
| 222 | displayedObfToDeobf.put(obfClass, deobfClass); | ||
| 223 | node.add(classNode); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | // finally, update the tree control | ||
| 228 | setModel(new DefaultTreeModel(rootNodes)); | ||
| 229 | |||
| 230 | restoreExpansionState(this, state); | ||
| 231 | } | ||
| 232 | |||
| 233 | public ClassEntry getSelectedClass() { | ||
| 234 | if (!isSelectionEmpty()) { | ||
| 235 | Object selectedNode = getSelectionPath().getLastPathComponent(); | ||
| 236 | if (selectedNode instanceof ClassSelectorClassNode) { | ||
| 237 | ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; | ||
| 238 | return classNode.getClassEntry(); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | return null; | ||
| 242 | } | ||
| 243 | |||
| 244 | public String getSelectedPackage() { | ||
| 245 | if (!isSelectionEmpty()) { | ||
| 246 | Object selectedNode = getSelectionPath().getLastPathComponent(); | ||
| 247 | if (selectedNode instanceof ClassSelectorPackageNode) { | ||
| 248 | ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode; | ||
| 249 | return packageNode.getPackageName(); | ||
| 250 | } else if (selectedNode instanceof ClassSelectorClassNode) { | ||
| 251 | ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; | ||
| 252 | return classNode.getClassEntry().getPackageName(); | ||
| 253 | } | ||
| 254 | } | ||
| 255 | return null; | ||
| 256 | } | ||
| 257 | |||
| 258 | public boolean isDescendant(TreePath path1, TreePath path2) { | ||
| 259 | int count1 = path1.getPathCount(); | ||
| 260 | int count2 = path2.getPathCount(); | ||
| 261 | if (count1 <= count2) { | ||
| 262 | return false; | ||
| 263 | } | ||
| 264 | while (count1 != count2) { | ||
| 265 | path1 = path1.getParentPath(); | ||
| 266 | count1--; | ||
| 267 | } | ||
| 268 | return path1.equals(path2); | ||
| 269 | } | ||
| 270 | |||
| 271 | public enum State { | ||
| 272 | EXPANDED, | ||
| 273 | SELECTED | ||
| 274 | } | ||
| 275 | |||
| 276 | public static class StateEntry { | ||
| 277 | public final State state; | ||
| 278 | public final TreePath path; | ||
| 279 | |||
| 280 | public StateEntry(State state, TreePath path) { | ||
| 281 | this.state = state; | ||
| 282 | this.path = path; | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | public List<StateEntry> getExpansionState(JTree tree) { | ||
| 287 | List<StateEntry> state = new ArrayList<>(); | ||
| 288 | int rowCount = tree.getRowCount(); | ||
| 289 | for (int i = 0; i < rowCount; i++) { | ||
| 290 | TreePath path = tree.getPathForRow(i); | ||
| 291 | if (tree.isPathSelected(path)) { | ||
| 292 | state.add(new StateEntry(State.SELECTED, path)); | ||
| 293 | } | ||
| 294 | if (tree.isExpanded(path)) { | ||
| 295 | state.add(new StateEntry(State.EXPANDED, path)); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | return state; | ||
| 299 | } | ||
| 300 | |||
| 301 | public void restoreExpansionState(JTree tree, List<StateEntry> expansionState) { | ||
| 302 | tree.clearSelection(); | ||
| 303 | |||
| 304 | for (StateEntry entry : expansionState) { | ||
| 305 | switch (entry.state) { | ||
| 306 | case SELECTED: | ||
| 307 | tree.addSelectionPath(entry.path); | ||
| 308 | break; | ||
| 309 | case EXPANDED: | ||
| 310 | tree.expandPath(entry.path); | ||
| 311 | break; | ||
| 312 | } | ||
| 313 | } | ||
| 314 | } | ||
| 315 | |||
| 316 | public List<ClassSelectorPackageNode> packageNodes() { | ||
| 317 | List<ClassSelectorPackageNode> nodes = Lists.newArrayList(); | ||
| 318 | DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); | ||
| 319 | Enumeration<?> children = root.children(); | ||
| 320 | while (children.hasMoreElements()) { | ||
| 321 | ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement(); | ||
| 322 | nodes.add(packageNode); | ||
| 323 | } | ||
| 324 | return nodes; | ||
| 325 | } | ||
| 326 | |||
| 327 | public List<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) { | ||
| 328 | List<ClassSelectorClassNode> nodes = Lists.newArrayList(); | ||
| 329 | Enumeration<?> children = packageNode.children(); | ||
| 330 | while (children.hasMoreElements()) { | ||
| 331 | ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement(); | ||
| 332 | nodes.add(classNode); | ||
| 333 | } | ||
| 334 | return nodes; | ||
| 335 | } | ||
| 336 | |||
| 337 | public void expandPackage(String packageName) { | ||
| 338 | if (packageName == null) { | ||
| 339 | return; | ||
| 340 | } | ||
| 341 | for (ClassSelectorPackageNode packageNode : packageNodes()) { | ||
| 342 | if (packageNode.getPackageName().equals(packageName)) { | ||
| 343 | expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); | ||
| 344 | return; | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | |||
| 349 | public void expandAll() { | ||
| 350 | for (ClassSelectorPackageNode packageNode : packageNodes()) { | ||
| 351 | expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | public ClassEntry getFirstClass() { | ||
| 356 | ClassSelectorPackageNode packageNode = packageNodes().get(0); | ||
| 357 | if (packageNode != null) { | ||
| 358 | ClassSelectorClassNode classNode = classNodes(packageNode).get(0); | ||
| 359 | if (classNode != null) { | ||
| 360 | return classNode.getClassEntry(); | ||
| 361 | } | ||
| 362 | } | ||
| 363 | return null; | ||
| 364 | } | ||
| 365 | |||
| 366 | public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { | ||
| 367 | String packageName = entry.getPackageName(); | ||
| 368 | if (packageName == null) { | ||
| 369 | packageName = "(none)"; | ||
| 370 | } | ||
| 371 | for (ClassSelectorPackageNode packageNode : packageNodes()) { | ||
| 372 | if (packageNode.getPackageName().equals(packageName)) { | ||
| 373 | return packageNode; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | return null; | ||
| 377 | } | ||
| 378 | |||
| 379 | @Nullable | ||
| 380 | public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) { | ||
| 381 | return displayedObfToDeobf.get(obfEntry); | ||
| 382 | } | ||
| 383 | |||
| 384 | public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { | ||
| 385 | ClassSelectorPackageNode packageNode = getPackageNode(entry); | ||
| 386 | |||
| 387 | if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) | ||
| 388 | return selector.getPackageNode(entry); | ||
| 389 | return packageNode; | ||
| 390 | } | ||
| 391 | |||
| 392 | public ClassEntry getNextClass(ClassEntry entry) { | ||
| 393 | boolean foundIt = false; | ||
| 394 | for (ClassSelectorPackageNode packageNode : packageNodes()) { | ||
| 395 | if (!foundIt) { | ||
| 396 | // skip to the package with our target in it | ||
| 397 | if (packageNode.getPackageName().equals(entry.getPackageName())) { | ||
| 398 | for (ClassSelectorClassNode classNode : classNodes(packageNode)) { | ||
| 399 | if (!foundIt) { | ||
| 400 | if (classNode.getClassEntry().equals(entry)) { | ||
| 401 | foundIt = true; | ||
| 402 | } | ||
| 403 | } else { | ||
| 404 | // return the next class | ||
| 405 | return classNode.getClassEntry(); | ||
| 406 | } | ||
| 407 | } | ||
| 408 | } | ||
| 409 | } else { | ||
| 410 | // return the next class | ||
| 411 | ClassSelectorClassNode classNode = classNodes(packageNode).get(0); | ||
| 412 | if (classNode != null) { | ||
| 413 | return classNode.getClassEntry(); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | } | ||
| 417 | return null; | ||
| 418 | } | ||
| 419 | |||
| 420 | public void setSelectionClass(ClassEntry classEntry) { | ||
| 421 | expandPackage(classEntry.getPackageName()); | ||
| 422 | for (ClassSelectorPackageNode packageNode : packageNodes()) { | ||
| 423 | for (ClassSelectorClassNode classNode : classNodes(packageNode)) { | ||
| 424 | if (classNode.getClassEntry().equals(classEntry)) { | ||
| 425 | TreePath path = new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}); | ||
| 426 | setSelectionPath(path); | ||
| 427 | scrollPathToVisible(path); | ||
| 428 | } | ||
| 429 | } | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 433 | public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { | ||
| 434 | DefaultTreeModel model = (DefaultTreeModel) getModel(); | ||
| 435 | |||
| 436 | if (packageNode == null) | ||
| 437 | return; | ||
| 438 | |||
| 439 | for (int i = 0; i < packageNode.getChildCount(); i++) { | ||
| 440 | DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); | ||
| 441 | if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { | ||
| 442 | model.removeNodeFromParent(childNode); | ||
| 443 | if (childNode instanceof ClassSelectorClassNode) { | ||
| 444 | displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry()); | ||
| 445 | } | ||
| 446 | break; | ||
| 447 | } | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) { | ||
| 452 | if (packageNode != null && packageNode.getChildCount() == 0) | ||
| 453 | ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); | ||
| 454 | } | ||
| 455 | |||
| 456 | public void moveClassIn(ClassEntry classEntry) { | ||
| 457 | removeEntry(classEntry); | ||
| 458 | insertNode(classEntry); | ||
| 459 | } | ||
| 460 | |||
| 461 | public void moveClassOut(ClassEntry classEntry) { | ||
| 462 | removeEntry(classEntry); | ||
| 463 | } | ||
| 464 | |||
| 465 | private void removeEntry(ClassEntry classEntry) { | ||
| 466 | ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry); | ||
| 467 | if (previousDeobf != null) { | ||
| 468 | ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf); | ||
| 469 | removeNode(packageNode, previousDeobf); | ||
| 470 | removeNodeIfEmpty(packageNode); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) { | ||
| 475 | DefaultTreeModel model = (DefaultTreeModel) getModel(); | ||
| 476 | ClassSelectorPackageNode newPackageNode = getPackageNode(entry); | ||
| 477 | if (newPackageNode == null) { | ||
| 478 | newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); | ||
| 479 | model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); | ||
| 480 | } | ||
| 481 | return newPackageNode; | ||
| 482 | } | ||
| 483 | |||
| 484 | public void insertNode(ClassEntry obfEntry) { | ||
| 485 | ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); | ||
| 486 | ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry); | ||
| 487 | |||
| 488 | DefaultTreeModel model = (DefaultTreeModel) getModel(); | ||
| 489 | ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry); | ||
| 490 | model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); | ||
| 491 | |||
| 492 | displayedObfToDeobf.put(obfEntry, deobfEntry); | ||
| 493 | } | ||
| 494 | |||
| 495 | public void reload() { | ||
| 496 | DefaultTreeModel model = (DefaultTreeModel) getModel(); | ||
| 497 | model.reload(rootNodes); | ||
| 498 | } | ||
| 499 | |||
| 500 | private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) { | ||
| 501 | List<ClassSelectorClassNode> classNodes = classNodes(newPackageNode); | ||
| 502 | classNodes.add(classNode); | ||
| 503 | classNodes.sort((a, b) -> comparator.compare(a.getClassEntry(), b.getClassEntry())); | ||
| 504 | for (int i = 0; i < classNodes.size(); i++) | ||
| 505 | if (classNodes.get(i) == classNode) | ||
| 506 | return i; | ||
| 507 | |||
| 508 | return 0; | ||
| 509 | } | ||
| 510 | |||
| 511 | private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) { | ||
| 512 | List<ClassSelectorPackageNode> packageNodes = packageNodes(); | ||
| 513 | if (!packageNodes.contains(newPackageNode)) { | ||
| 514 | packageNodes.add(newPackageNode); | ||
| 515 | packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); | ||
| 516 | } | ||
| 517 | |||
| 518 | for (int i = 0; i < packageNodes.size(); i++) | ||
| 519 | if (packageNodes.get(i) == newPackageNode) | ||
| 520 | return i; | ||
| 521 | |||
| 522 | return 0; | ||
| 523 | } | ||
| 524 | |||
| 525 | public interface ClassSelectionListener { | ||
| 526 | void onSelectClass(ClassEntry classEntry); | ||
| 527 | } | ||
| 528 | |||
| 529 | public interface RenameSelectionListener { | ||
| 530 | void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); | ||
| 531 | } | ||
| 532 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java deleted file mode 100644 index e119640..0000000 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ /dev/null | |||
| @@ -1,73 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.Token; | ||
| 15 | |||
| 16 | import javax.swing.*; | ||
| 17 | import javax.swing.text.BadLocationException; | ||
| 18 | import javax.swing.text.Document; | ||
| 19 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 20 | import java.awt.*; | ||
| 21 | import java.awt.event.ActionEvent; | ||
| 22 | import java.awt.event.ActionListener; | ||
| 23 | |||
| 24 | public class CodeReader extends JEditorPane { | ||
| 25 | private static final long serialVersionUID = 3673180950485748810L; | ||
| 26 | |||
| 27 | // HACKHACK: someday we can update the main GUI to use this code reader | ||
| 28 | public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { | ||
| 29 | |||
| 30 | // set the caret position to the token | ||
| 31 | Document document = editor.getDocument(); | ||
| 32 | int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength()); | ||
| 33 | |||
| 34 | editor.setCaretPosition(clampedPosition); | ||
| 35 | editor.grabFocus(); | ||
| 36 | |||
| 37 | try { | ||
| 38 | // make sure the token is visible in the scroll window | ||
| 39 | Rectangle start = editor.modelToView(token.start); | ||
| 40 | Rectangle end = editor.modelToView(token.end); | ||
| 41 | final Rectangle show = start.union(end); | ||
| 42 | show.grow(start.width * 10, start.height * 6); | ||
| 43 | SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); | ||
| 44 | } catch (BadLocationException ex) { | ||
| 45 | throw new Error(ex); | ||
| 46 | } | ||
| 47 | |||
| 48 | // highlight the token momentarily | ||
| 49 | final Timer timer = new Timer(200, new ActionListener() { | ||
| 50 | private int counter = 0; | ||
| 51 | private Object highlight = null; | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public void actionPerformed(ActionEvent event) { | ||
| 55 | if (counter % 2 == 0) { | ||
| 56 | try { | ||
| 57 | highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); | ||
| 58 | } catch (BadLocationException ex) { | ||
| 59 | // don't care | ||
| 60 | } | ||
| 61 | } else if (highlight != null) { | ||
| 62 | editor.getHighlighter().removeHighlight(highlight); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (counter++ > 6) { | ||
| 66 | Timer timer = (Timer) event.getSource(); | ||
| 67 | timer.stop(); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | }); | ||
| 71 | timer.start(); | ||
| 72 | } | ||
| 73 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/ConnectionState.java b/src/main/java/cuchaz/enigma/gui/ConnectionState.java deleted file mode 100644 index db6590d..0000000 --- a/src/main/java/cuchaz/enigma/gui/ConnectionState.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | public enum ConnectionState { | ||
| 4 | NOT_CONNECTED, | ||
| 5 | HOSTING, | ||
| 6 | CONNECTED, | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java deleted file mode 100644 index 08df3e7..0000000 --- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ /dev/null | |||
| @@ -1,159 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | ||
| 4 | import cuchaz.enigma.EnigmaServices; | ||
| 5 | import cuchaz.enigma.analysis.EntryReference; | ||
| 6 | import cuchaz.enigma.analysis.Token; | ||
| 7 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 8 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 9 | import cuchaz.enigma.source.SourceIndex; | ||
| 10 | import cuchaz.enigma.translation.LocalNameGenerator; | ||
| 11 | import cuchaz.enigma.translation.Translator; | ||
| 12 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 13 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 14 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; | ||
| 18 | |||
| 19 | import javax.annotation.Nullable; | ||
| 20 | import java.util.*; | ||
| 21 | |||
| 22 | public class DecompiledClassSource { | ||
| 23 | private final ClassEntry classEntry; | ||
| 24 | |||
| 25 | private final SourceIndex obfuscatedIndex; | ||
| 26 | private SourceIndex remappedIndex; | ||
| 27 | |||
| 28 | private final Map<TokenHighlightType, Collection<Token>> highlightedTokens = new EnumMap<>(TokenHighlightType.class); | ||
| 29 | |||
| 30 | public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { | ||
| 31 | this.classEntry = classEntry; | ||
| 32 | this.obfuscatedIndex = index; | ||
| 33 | this.remappedIndex = index; | ||
| 34 | } | ||
| 35 | |||
| 36 | public static DecompiledClassSource text(ClassEntry classEntry, String text) { | ||
| 37 | return new DecompiledClassSource(classEntry, new SourceIndex(text)); | ||
| 38 | } | ||
| 39 | |||
| 40 | public void remapSource(EnigmaProject project, Translator translator) { | ||
| 41 | highlightedTokens.clear(); | ||
| 42 | |||
| 43 | SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); | ||
| 44 | |||
| 45 | SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator)); | ||
| 46 | remappedIndex = obfuscatedIndex.remapTo(remapResult); | ||
| 47 | } | ||
| 48 | |||
| 49 | private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) { | ||
| 50 | EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token); | ||
| 51 | |||
| 52 | Entry<?> entry = reference.getNameableEntry(); | ||
| 53 | Entry<?> translatedEntry = translator.translate(entry); | ||
| 54 | |||
| 55 | if (project.isRenamable(reference)) { | ||
| 56 | if (isDeobfuscated(entry, translatedEntry)) { | ||
| 57 | highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); | ||
| 58 | return translatedEntry.getSourceRemapName(); | ||
| 59 | } else { | ||
| 60 | Optional<String> proposedName = proposeName(project, entry); | ||
| 61 | if (proposedName.isPresent()) { | ||
| 62 | highlightToken(movedToken, TokenHighlightType.PROPOSED); | ||
| 63 | return proposedName.get(); | ||
| 64 | } | ||
| 65 | |||
| 66 | highlightToken(movedToken, TokenHighlightType.OBFUSCATED); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | String defaultName = generateDefaultName(translatedEntry); | ||
| 71 | if (defaultName != null) { | ||
| 72 | return defaultName; | ||
| 73 | } | ||
| 74 | |||
| 75 | return null; | ||
| 76 | } | ||
| 77 | |||
| 78 | private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) { | ||
| 79 | EnigmaServices services = project.getEnigma().getServices(); | ||
| 80 | |||
| 81 | return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> { | ||
| 82 | EntryRemapper mapper = project.getMapper(); | ||
| 83 | Collection<Entry<?>> resolved = mapper.getObfResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 84 | |||
| 85 | return resolved.stream() | ||
| 86 | .map(e -> nameProposalService.proposeName(e, mapper)) | ||
| 87 | .filter(Optional::isPresent) | ||
| 88 | .map(Optional::get); | ||
| 89 | }).findFirst(); | ||
| 90 | } | ||
| 91 | |||
| 92 | @Nullable | ||
| 93 | private String generateDefaultName(Entry<?> entry) { | ||
| 94 | if (entry instanceof LocalVariableDefEntry) { | ||
| 95 | LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry; | ||
| 96 | |||
| 97 | int index = localVariable.getIndex(); | ||
| 98 | if (localVariable.isArgument()) { | ||
| 99 | List<TypeDescriptor> arguments = localVariable.getParent().getDesc().getArgumentDescs(); | ||
| 100 | return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments); | ||
| 101 | } else { | ||
| 102 | return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc()); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | return null; | ||
| 107 | } | ||
| 108 | |||
| 109 | private boolean isDeobfuscated(Entry<?> entry, Entry<?> translatedEntry) { | ||
| 110 | return !entry.getName().equals(translatedEntry.getName()); | ||
| 111 | } | ||
| 112 | |||
| 113 | public ClassEntry getEntry() { | ||
| 114 | return classEntry; | ||
| 115 | } | ||
| 116 | |||
| 117 | public SourceIndex getIndex() { | ||
| 118 | return remappedIndex; | ||
| 119 | } | ||
| 120 | |||
| 121 | public Map<TokenHighlightType, Collection<Token>> getHighlightedTokens() { | ||
| 122 | return highlightedTokens; | ||
| 123 | } | ||
| 124 | |||
| 125 | private void highlightToken(Token token, TokenHighlightType highlightType) { | ||
| 126 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); | ||
| 127 | } | ||
| 128 | |||
| 129 | public int getObfuscatedOffset(int deobfOffset) { | ||
| 130 | return getOffset(remappedIndex, obfuscatedIndex, deobfOffset); | ||
| 131 | } | ||
| 132 | |||
| 133 | public int getDeobfuscatedOffset(int obfOffset) { | ||
| 134 | return getOffset(obfuscatedIndex, remappedIndex, obfOffset); | ||
| 135 | } | ||
| 136 | |||
| 137 | private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) { | ||
| 138 | int relativeOffset = 0; | ||
| 139 | |||
| 140 | Iterator<Token> fromTokenItr = fromIndex.referenceTokens().iterator(); | ||
| 141 | Iterator<Token> toTokenItr = toIndex.referenceTokens().iterator(); | ||
| 142 | while (fromTokenItr.hasNext() && toTokenItr.hasNext()) { | ||
| 143 | Token fromToken = fromTokenItr.next(); | ||
| 144 | Token toToken = toTokenItr.next(); | ||
| 145 | if (fromToken.end > fromOffset) { | ||
| 146 | break; | ||
| 147 | } | ||
| 148 | |||
| 149 | relativeOffset = toToken.end - fromToken.end; | ||
| 150 | } | ||
| 151 | |||
| 152 | return fromOffset + relativeOffset; | ||
| 153 | } | ||
| 154 | |||
| 155 | @Override | ||
| 156 | public String toString() { | ||
| 157 | return remappedIndex.getSource(); | ||
| 158 | } | ||
| 159 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java b/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java deleted file mode 100644 index c912be3..0000000 --- a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java +++ /dev/null | |||
| @@ -1,90 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import de.sciss.syntaxpane.actions.DocumentSearchData; | ||
| 4 | import de.sciss.syntaxpane.actions.gui.QuickFindDialog; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import javax.swing.text.JTextComponent; | ||
| 8 | import java.awt.*; | ||
| 9 | import java.awt.event.KeyAdapter; | ||
| 10 | import java.awt.event.KeyEvent; | ||
| 11 | import java.util.stream.IntStream; | ||
| 12 | import java.util.stream.Stream; | ||
| 13 | |||
| 14 | public class EnigmaQuickFindDialog extends QuickFindDialog { | ||
| 15 | public EnigmaQuickFindDialog(JTextComponent target) { | ||
| 16 | super(target, DocumentSearchData.getFromEditor(target)); | ||
| 17 | |||
| 18 | JToolBar toolBar = getToolBar(); | ||
| 19 | JTextField textField = getTextField(toolBar); | ||
| 20 | |||
| 21 | textField.addKeyListener(new KeyAdapter() { | ||
| 22 | @Override | ||
| 23 | public void keyPressed(KeyEvent e) { | ||
| 24 | super.keyPressed(e); | ||
| 25 | if (e.getKeyCode() == KeyEvent.VK_ENTER) { | ||
| 26 | JToolBar toolBar = getToolBar(); | ||
| 27 | boolean next = !e.isShiftDown(); | ||
| 28 | JButton button = next ? getNextButton(toolBar) : getPrevButton(toolBar); | ||
| 29 | button.doClick(); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | }); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void showFor(JTextComponent target) { | ||
| 37 | String selectedText = target.getSelectedText(); | ||
| 38 | |||
| 39 | try { | ||
| 40 | super.showFor(target); | ||
| 41 | } catch (Exception e) { | ||
| 42 | e.printStackTrace(); | ||
| 43 | return; | ||
| 44 | } | ||
| 45 | |||
| 46 | Container view = target.getParent(); | ||
| 47 | Point loc = new Point(0, view.getHeight() - getSize().height); | ||
| 48 | setLocationRelativeTo(view); | ||
| 49 | SwingUtilities.convertPointToScreen(loc, view); | ||
| 50 | setLocation(loc); | ||
| 51 | |||
| 52 | JToolBar toolBar = getToolBar(); | ||
| 53 | JTextField textField = getTextField(toolBar); | ||
| 54 | |||
| 55 | if (selectedText != null) { | ||
| 56 | textField.setText(selectedText); | ||
| 57 | } | ||
| 58 | |||
| 59 | textField.selectAll(); | ||
| 60 | } | ||
| 61 | |||
| 62 | private JToolBar getToolBar() { | ||
| 63 | return components(getContentPane(), JToolBar.class).findFirst().orElse(null); | ||
| 64 | } | ||
| 65 | |||
| 66 | private JTextField getTextField(JToolBar toolBar) { | ||
| 67 | return components(toolBar, JTextField.class).findFirst().orElse(null); | ||
| 68 | } | ||
| 69 | |||
| 70 | private JButton getNextButton(JToolBar toolBar) { | ||
| 71 | Stream<JButton> buttons = components(toolBar, JButton.class); | ||
| 72 | return buttons.skip(1).findFirst().orElse(null); | ||
| 73 | } | ||
| 74 | |||
| 75 | private JButton getPrevButton(JToolBar toolBar) { | ||
| 76 | Stream<JButton> buttons = components(toolBar, JButton.class); | ||
| 77 | return buttons.findFirst().orElse(null); | ||
| 78 | } | ||
| 79 | |||
| 80 | private static Stream<Component> components(Container container) { | ||
| 81 | return IntStream.range(0, container.getComponentCount()) | ||
| 82 | .mapToObj(container::getComponent); | ||
| 83 | } | ||
| 84 | |||
| 85 | private static <T extends Component> Stream<T> components(Container container, Class<T> type) { | ||
| 86 | return components(container) | ||
| 87 | .filter(type::isInstance) | ||
| 88 | .map(type::cast); | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java deleted file mode 100644 index 42eaa60..0000000 --- a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import cuchaz.enigma.config.Config; | ||
| 4 | import de.sciss.syntaxpane.components.LineNumbersRuler; | ||
| 5 | import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; | ||
| 6 | import de.sciss.syntaxpane.util.Configuration; | ||
| 7 | |||
| 8 | public class EnigmaSyntaxKit extends JavaSyntaxKit { | ||
| 9 | private static Configuration configuration = null; | ||
| 10 | |||
| 11 | @Override | ||
| 12 | public Configuration getConfig() { | ||
| 13 | if(configuration == null){ | ||
| 14 | initConfig(super.getConfig(JavaSyntaxKit.class)); | ||
| 15 | } | ||
| 16 | return configuration; | ||
| 17 | } | ||
| 18 | |||
| 19 | public void initConfig(Configuration baseConfig){ | ||
| 20 | configuration = baseConfig; | ||
| 21 | //See de.sciss.syntaxpane.TokenType | ||
| 22 | configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); | ||
| 23 | configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3"); | ||
| 24 | configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0"); | ||
| 25 | configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1"); | ||
| 26 | configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1"); | ||
| 27 | configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0"); | ||
| 28 | configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1"); | ||
| 29 | configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2"); | ||
| 30 | configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1"); | ||
| 31 | configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0"); | ||
| 32 | configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0"); | ||
| 33 | configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + ""); | ||
| 34 | configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + ""); | ||
| 35 | configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + ""); | ||
| 36 | configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config | ||
| 37 | |||
| 38 | configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); | ||
| 39 | } | ||
| 40 | |||
| 41 | public static void invalidate(){ | ||
| 42 | configuration = null; | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java deleted file mode 100644 index ed32469..0000000 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ /dev/null | |||
| @@ -1,1058 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import java.awt.*; | ||
| 15 | import java.awt.event.*; | ||
| 16 | import java.nio.file.Path; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.*; | ||
| 19 | import java.util.function.Function; | ||
| 20 | |||
| 21 | import javax.swing.*; | ||
| 22 | import javax.swing.text.BadLocationException; | ||
| 23 | import javax.swing.text.Highlighter; | ||
| 24 | import javax.swing.tree.*; | ||
| 25 | |||
| 26 | import com.google.common.base.Strings; | ||
| 27 | import com.google.common.collect.Lists; | ||
| 28 | import cuchaz.enigma.Constants; | ||
| 29 | import cuchaz.enigma.EnigmaProfile; | ||
| 30 | import cuchaz.enigma.ExceptionIgnorer; | ||
| 31 | import cuchaz.enigma.analysis.*; | ||
| 32 | import cuchaz.enigma.config.Config; | ||
| 33 | import cuchaz.enigma.config.Themes; | ||
| 34 | import cuchaz.enigma.gui.dialog.CrashDialog; | ||
| 35 | import cuchaz.enigma.gui.dialog.JavadocDialog; | ||
| 36 | import cuchaz.enigma.gui.dialog.SearchDialog; | ||
| 37 | import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; | ||
| 38 | import cuchaz.enigma.gui.elements.MenuBar; | ||
| 39 | import cuchaz.enigma.gui.elements.PopupMenuBar; | ||
| 40 | import cuchaz.enigma.gui.filechooser.FileChooserAny; | ||
| 41 | import cuchaz.enigma.gui.filechooser.FileChooserFolder; | ||
| 42 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | ||
| 43 | import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; | ||
| 44 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 45 | import cuchaz.enigma.gui.panels.PanelDeobf; | ||
| 46 | import cuchaz.enigma.gui.panels.PanelEditor; | ||
| 47 | import cuchaz.enigma.gui.panels.PanelIdentifier; | ||
| 48 | import cuchaz.enigma.gui.panels.PanelObf; | ||
| 49 | import cuchaz.enigma.gui.util.History; | ||
| 50 | import cuchaz.enigma.network.packet.*; | ||
| 51 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 52 | import cuchaz.enigma.translation.mapping.*; | ||
| 53 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 54 | import cuchaz.enigma.utils.I18n; | ||
| 55 | import cuchaz.enigma.utils.Message; | ||
| 56 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 57 | import cuchaz.enigma.utils.Utils; | ||
| 58 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 59 | |||
| 60 | public class Gui { | ||
| 61 | |||
| 62 | public final PopupMenuBar popupMenu; | ||
| 63 | private final PanelObf obfPanel; | ||
| 64 | private final PanelDeobf deobfPanel; | ||
| 65 | |||
| 66 | private final MenuBar menuBar; | ||
| 67 | // state | ||
| 68 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; | ||
| 69 | public EntryReference<Entry<?>, Entry<?>> renamingReference; | ||
| 70 | public EntryReference<Entry<?>, Entry<?>> cursorReference; | ||
| 71 | private boolean shouldNavigateOnClick; | ||
| 72 | private ConnectionState connectionState; | ||
| 73 | private boolean isJarOpen; | ||
| 74 | |||
| 75 | public FileDialog jarFileChooser; | ||
| 76 | public FileDialog tinyMappingsFileChooser; | ||
| 77 | public SearchDialog searchDialog; | ||
| 78 | public JFileChooser enigmaMappingsFileChooser; | ||
| 79 | public JFileChooser exportSourceFileChooser; | ||
| 80 | public FileDialog exportJarFileChooser; | ||
| 81 | private GuiController controller; | ||
| 82 | private JFrame frame; | ||
| 83 | public Config.LookAndFeel editorFeel; | ||
| 84 | public PanelEditor editor; | ||
| 85 | public JScrollPane sourceScroller; | ||
| 86 | private JPanel classesPanel; | ||
| 87 | private JSplitPane splitClasses; | ||
| 88 | private PanelIdentifier infoPanel; | ||
| 89 | public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters; | ||
| 90 | private SelectionHighlightPainter selectionHighlightPainter; | ||
| 91 | private JTree inheritanceTree; | ||
| 92 | private JTree implementationsTree; | ||
| 93 | private JTree callsTree; | ||
| 94 | private JList<Token> tokens; | ||
| 95 | private JTabbedPane tabs; | ||
| 96 | |||
| 97 | private JSplitPane splitRight; | ||
| 98 | private JSplitPane logSplit; | ||
| 99 | private CollapsibleTabbedPane logTabs; | ||
| 100 | private JList<String> users; | ||
| 101 | private DefaultListModel<String> userModel; | ||
| 102 | private JScrollPane messageScrollPane; | ||
| 103 | private JList<Message> messages; | ||
| 104 | private DefaultListModel<Message> messageModel; | ||
| 105 | private JTextField chatBox; | ||
| 106 | |||
| 107 | private JPanel statusBar; | ||
| 108 | private JLabel connectionStatusLabel; | ||
| 109 | private JLabel statusLabel; | ||
| 110 | |||
| 111 | public JTextField renameTextField; | ||
| 112 | public JTextArea javadocTextArea; | ||
| 113 | |||
| 114 | public void setEditorTheme(Config.LookAndFeel feel) { | ||
| 115 | if (editor != null && (editorFeel == null || editorFeel != feel)) { | ||
| 116 | editor.updateUI(); | ||
| 117 | editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 118 | if (editorFeel != null) { | ||
| 119 | getController().refreshCurrentClass(); | ||
| 120 | } | ||
| 121 | |||
| 122 | editorFeel = feel; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | public Gui(EnigmaProfile profile) { | ||
| 127 | Config.getInstance().lookAndFeel.setGlobalLAF(); | ||
| 128 | |||
| 129 | // init frame | ||
| 130 | this.frame = new JFrame(Constants.NAME); | ||
| 131 | final Container pane = this.frame.getContentPane(); | ||
| 132 | pane.setLayout(new BorderLayout()); | ||
| 133 | |||
| 134 | if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { | ||
| 135 | // install a global exception handler to the event thread | ||
| 136 | CrashDialog.init(this.frame); | ||
| 137 | Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { | ||
| 138 | t.printStackTrace(System.err); | ||
| 139 | if (!ExceptionIgnorer.shouldIgnore(t)) { | ||
| 140 | CrashDialog.show(t); | ||
| 141 | } | ||
| 142 | }); | ||
| 143 | } | ||
| 144 | |||
| 145 | this.controller = new GuiController(this, profile); | ||
| 146 | |||
| 147 | Themes.updateTheme(this); | ||
| 148 | |||
| 149 | // init file choosers | ||
| 150 | this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD); | ||
| 151 | |||
| 152 | this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD); | ||
| 153 | this.enigmaMappingsFileChooser = new FileChooserAny(); | ||
| 154 | this.exportSourceFileChooser = new FileChooserFolder(); | ||
| 155 | this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE); | ||
| 156 | |||
| 157 | this.obfPanel = new PanelObf(this); | ||
| 158 | this.deobfPanel = new PanelDeobf(this); | ||
| 159 | |||
| 160 | // set up classes panel (don't add the splitter yet) | ||
| 161 | splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); | ||
| 162 | splitClasses.setResizeWeight(0.3); | ||
| 163 | this.classesPanel = new JPanel(); | ||
| 164 | this.classesPanel.setLayout(new BorderLayout()); | ||
| 165 | this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); | ||
| 166 | |||
| 167 | // init info panel | ||
| 168 | infoPanel = new PanelIdentifier(this); | ||
| 169 | infoPanel.clearReference(); | ||
| 170 | |||
| 171 | // init editor | ||
| 172 | selectionHighlightPainter = new SelectionHighlightPainter(); | ||
| 173 | this.editor = new PanelEditor(this); | ||
| 174 | this.sourceScroller = new JScrollPane(this.editor); | ||
| 175 | this.editor.setContentType("text/enigma-sources"); | ||
| 176 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 177 | DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); | ||
| 178 | kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); | ||
| 179 | |||
| 180 | // init editor popup menu | ||
| 181 | this.popupMenu = new PopupMenuBar(this); | ||
| 182 | this.editor.setComponentPopupMenu(this.popupMenu); | ||
| 183 | |||
| 184 | // init inheritance panel | ||
| 185 | inheritanceTree = new JTree(); | ||
| 186 | inheritanceTree.setModel(null); | ||
| 187 | inheritanceTree.addMouseListener(new MouseAdapter() { | ||
| 188 | @Override | ||
| 189 | public void mouseClicked(MouseEvent event) { | ||
| 190 | if (event.getClickCount() >= 2) { | ||
| 191 | // get the selected node | ||
| 192 | TreePath path = inheritanceTree.getSelectionPath(); | ||
| 193 | if (path == null) { | ||
| 194 | return; | ||
| 195 | } | ||
| 196 | |||
| 197 | Object node = path.getLastPathComponent(); | ||
| 198 | if (node instanceof ClassInheritanceTreeNode) { | ||
| 199 | ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; | ||
| 200 | controller.navigateTo(new ClassEntry(classNode.getObfClassName())); | ||
| 201 | } else if (node instanceof MethodInheritanceTreeNode) { | ||
| 202 | MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; | ||
| 203 | if (methodNode.isImplemented()) { | ||
| 204 | controller.navigateTo(methodNode.getMethodEntry()); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | }); | ||
| 210 | TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer(); | ||
| 211 | inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer)); | ||
| 212 | |||
| 213 | JPanel inheritancePanel = new JPanel(); | ||
| 214 | inheritancePanel.setLayout(new BorderLayout()); | ||
| 215 | inheritancePanel.add(new JScrollPane(inheritanceTree)); | ||
| 216 | |||
| 217 | // init implementations panel | ||
| 218 | implementationsTree = new JTree(); | ||
| 219 | implementationsTree.setModel(null); | ||
| 220 | implementationsTree.addMouseListener(new MouseAdapter() { | ||
| 221 | @Override | ||
| 222 | public void mouseClicked(MouseEvent event) { | ||
| 223 | if (event.getClickCount() >= 2) { | ||
| 224 | // get the selected node | ||
| 225 | TreePath path = implementationsTree.getSelectionPath(); | ||
| 226 | if (path == null) { | ||
| 227 | return; | ||
| 228 | } | ||
| 229 | |||
| 230 | Object node = path.getLastPathComponent(); | ||
| 231 | if (node instanceof ClassImplementationsTreeNode) { | ||
| 232 | ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; | ||
| 233 | controller.navigateTo(classNode.getClassEntry()); | ||
| 234 | } else if (node instanceof MethodImplementationsTreeNode) { | ||
| 235 | MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; | ||
| 236 | controller.navigateTo(methodNode.getMethodEntry()); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | }); | ||
| 241 | JPanel implementationsPanel = new JPanel(); | ||
| 242 | implementationsPanel.setLayout(new BorderLayout()); | ||
| 243 | implementationsPanel.add(new JScrollPane(implementationsTree)); | ||
| 244 | |||
| 245 | // init call panel | ||
| 246 | callsTree = new JTree(); | ||
| 247 | callsTree.setModel(null); | ||
| 248 | callsTree.addMouseListener(new MouseAdapter() { | ||
| 249 | @SuppressWarnings("unchecked") | ||
| 250 | @Override | ||
| 251 | public void mouseClicked(MouseEvent event) { | ||
| 252 | if (event.getClickCount() >= 2) { | ||
| 253 | // get the selected node | ||
| 254 | TreePath path = callsTree.getSelectionPath(); | ||
| 255 | if (path == null) { | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | |||
| 259 | Object node = path.getLastPathComponent(); | ||
| 260 | if (node instanceof ReferenceTreeNode) { | ||
| 261 | ReferenceTreeNode<Entry<?>, Entry<?>> referenceNode = ((ReferenceTreeNode<Entry<?>, Entry<?>>) node); | ||
| 262 | if (referenceNode.getReference() != null) { | ||
| 263 | controller.navigateTo(referenceNode.getReference()); | ||
| 264 | } else { | ||
| 265 | controller.navigateTo(referenceNode.getEntry()); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | } | ||
| 270 | }); | ||
| 271 | tokens = new JList<>(); | ||
| 272 | tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); | ||
| 273 | tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
| 274 | tokens.setLayoutOrientation(JList.VERTICAL); | ||
| 275 | tokens.addMouseListener(new MouseAdapter() { | ||
| 276 | @Override | ||
| 277 | public void mouseClicked(MouseEvent event) { | ||
| 278 | if (event.getClickCount() == 2) { | ||
| 279 | Token selected = tokens.getSelectedValue(); | ||
| 280 | if (selected != null) { | ||
| 281 | showToken(selected); | ||
| 282 | } | ||
| 283 | } | ||
| 284 | } | ||
| 285 | }); | ||
| 286 | tokens.setPreferredSize(ScaleUtil.getDimension(0, 200)); | ||
| 287 | tokens.setMinimumSize(ScaleUtil.getDimension(0, 200)); | ||
| 288 | JSplitPane callPanel = new JSplitPane( | ||
| 289 | JSplitPane.VERTICAL_SPLIT, | ||
| 290 | true, | ||
| 291 | new JScrollPane(callsTree), | ||
| 292 | new JScrollPane(tokens) | ||
| 293 | ); | ||
| 294 | callPanel.setResizeWeight(1); // let the top side take all the slack | ||
| 295 | callPanel.resetToPreferredSizes(); | ||
| 296 | |||
| 297 | // layout controls | ||
| 298 | JPanel centerPanel = new JPanel(); | ||
| 299 | centerPanel.setLayout(new BorderLayout()); | ||
| 300 | centerPanel.add(infoPanel, BorderLayout.NORTH); | ||
| 301 | centerPanel.add(sourceScroller, BorderLayout.CENTER); | ||
| 302 | tabs = new JTabbedPane(); | ||
| 303 | tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); | ||
| 304 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); | ||
| 305 | tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); | ||
| 306 | tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); | ||
| 307 | logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); | ||
| 308 | userModel = new DefaultListModel<>(); | ||
| 309 | users = new JList<>(userModel); | ||
| 310 | messageModel = new DefaultListModel<>(); | ||
| 311 | messages = new JList<>(messageModel); | ||
| 312 | messages.setCellRenderer(new MessageListCellRenderer()); | ||
| 313 | JPanel messagePanel = new JPanel(new BorderLayout()); | ||
| 314 | messageScrollPane = new JScrollPane(this.messages); | ||
| 315 | messagePanel.add(messageScrollPane, BorderLayout.CENTER); | ||
| 316 | JPanel chatPanel = new JPanel(new BorderLayout()); | ||
| 317 | chatBox = new JTextField(); | ||
| 318 | AbstractAction sendListener = new AbstractAction("Send") { | ||
| 319 | @Override | ||
| 320 | public void actionPerformed(ActionEvent e) { | ||
| 321 | sendMessage(); | ||
| 322 | } | ||
| 323 | }; | ||
| 324 | chatBox.addActionListener(sendListener); | ||
| 325 | JButton chatSendButton = new JButton(sendListener); | ||
| 326 | chatPanel.add(chatBox, BorderLayout.CENTER); | ||
| 327 | chatPanel.add(chatSendButton, BorderLayout.EAST); | ||
| 328 | messagePanel.add(chatPanel, BorderLayout.SOUTH); | ||
| 329 | logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); | ||
| 330 | logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); | ||
| 331 | logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); | ||
| 332 | logSplit.setResizeWeight(0.5); | ||
| 333 | logSplit.resetToPreferredSizes(); | ||
| 334 | splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); | ||
| 335 | splitRight.setResizeWeight(1); // let the left side take all the slack | ||
| 336 | splitRight.resetToPreferredSizes(); | ||
| 337 | JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); | ||
| 338 | splitCenter.setResizeWeight(0); // let the right side take all the slack | ||
| 339 | pane.add(splitCenter, BorderLayout.CENTER); | ||
| 340 | |||
| 341 | // init menus | ||
| 342 | this.menuBar = new MenuBar(this); | ||
| 343 | this.frame.setJMenuBar(this.menuBar); | ||
| 344 | |||
| 345 | // init status bar | ||
| 346 | statusBar = new JPanel(new BorderLayout()); | ||
| 347 | statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); | ||
| 348 | connectionStatusLabel = new JLabel(); | ||
| 349 | statusLabel = new JLabel(); | ||
| 350 | statusBar.add(statusLabel, BorderLayout.CENTER); | ||
| 351 | statusBar.add(connectionStatusLabel, BorderLayout.EAST); | ||
| 352 | pane.add(statusBar, BorderLayout.SOUTH); | ||
| 353 | |||
| 354 | // init state | ||
| 355 | setConnectionState(ConnectionState.NOT_CONNECTED); | ||
| 356 | onCloseJar(); | ||
| 357 | |||
| 358 | this.frame.addWindowListener(new WindowAdapter() { | ||
| 359 | @Override | ||
| 360 | public void windowClosing(WindowEvent event) { | ||
| 361 | close(); | ||
| 362 | } | ||
| 363 | }); | ||
| 364 | |||
| 365 | // show the frame | ||
| 366 | pane.doLayout(); | ||
| 367 | this.frame.setSize(ScaleUtil.getDimension(1024, 576)); | ||
| 368 | this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); | ||
| 369 | this.frame.setVisible(true); | ||
| 370 | this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 371 | this.frame.setLocationRelativeTo(null); | ||
| 372 | } | ||
| 373 | |||
| 374 | public JFrame getFrame() { | ||
| 375 | return this.frame; | ||
| 376 | } | ||
| 377 | |||
| 378 | public GuiController getController() { | ||
| 379 | return this.controller; | ||
| 380 | } | ||
| 381 | |||
| 382 | public void onStartOpenJar() { | ||
| 383 | this.classesPanel.removeAll(); | ||
| 384 | redraw(); | ||
| 385 | } | ||
| 386 | |||
| 387 | public void onFinishOpenJar(String jarName) { | ||
| 388 | // update gui | ||
| 389 | this.frame.setTitle(Constants.NAME + " - " + jarName); | ||
| 390 | this.classesPanel.removeAll(); | ||
| 391 | this.classesPanel.add(splitClasses); | ||
| 392 | setEditorText(null); | ||
| 393 | |||
| 394 | // update menu | ||
| 395 | isJarOpen = true; | ||
| 396 | |||
| 397 | updateUiState(); | ||
| 398 | redraw(); | ||
| 399 | } | ||
| 400 | |||
| 401 | public void onCloseJar() { | ||
| 402 | |||
| 403 | // update gui | ||
| 404 | this.frame.setTitle(Constants.NAME); | ||
| 405 | setObfClasses(null); | ||
| 406 | setDeobfClasses(null); | ||
| 407 | setEditorText(null); | ||
| 408 | this.classesPanel.removeAll(); | ||
| 409 | |||
| 410 | // update menu | ||
| 411 | isJarOpen = false; | ||
| 412 | setMappingsFile(null); | ||
| 413 | |||
| 414 | updateUiState(); | ||
| 415 | redraw(); | ||
| 416 | } | ||
| 417 | |||
| 418 | public void setObfClasses(Collection<ClassEntry> obfClasses) { | ||
| 419 | this.obfPanel.obfClasses.setClasses(obfClasses); | ||
| 420 | } | ||
| 421 | |||
| 422 | public void setDeobfClasses(Collection<ClassEntry> deobfClasses) { | ||
| 423 | this.deobfPanel.deobfClasses.setClasses(deobfClasses); | ||
| 424 | } | ||
| 425 | |||
| 426 | public void setMappingsFile(Path path) { | ||
| 427 | this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); | ||
| 428 | updateUiState(); | ||
| 429 | } | ||
| 430 | |||
| 431 | public void setEditorText(String source) { | ||
| 432 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 433 | this.editor.setText(source); | ||
| 434 | } | ||
| 435 | |||
| 436 | public void setSource(DecompiledClassSource source) { | ||
| 437 | editor.setText(source.toString()); | ||
| 438 | setHighlightedTokens(source.getHighlightedTokens()); | ||
| 439 | } | ||
| 440 | |||
| 441 | public void showToken(final Token token) { | ||
| 442 | if (token == null) { | ||
| 443 | throw new IllegalArgumentException("Token cannot be null!"); | ||
| 444 | } | ||
| 445 | CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); | ||
| 446 | redraw(); | ||
| 447 | } | ||
| 448 | |||
| 449 | public void showTokens(Collection<Token> tokens) { | ||
| 450 | Vector<Token> sortedTokens = new Vector<>(tokens); | ||
| 451 | Collections.sort(sortedTokens); | ||
| 452 | if (sortedTokens.size() > 1) { | ||
| 453 | // sort the tokens and update the tokens panel | ||
| 454 | this.tokens.setListData(sortedTokens); | ||
| 455 | this.tokens.setSelectedIndex(0); | ||
| 456 | } else { | ||
| 457 | this.tokens.setListData(new Vector<>()); | ||
| 458 | } | ||
| 459 | |||
| 460 | // show the first token | ||
| 461 | showToken(sortedTokens.get(0)); | ||
| 462 | } | ||
| 463 | |||
| 464 | public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) { | ||
| 465 | // remove any old highlighters | ||
| 466 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 467 | |||
| 468 | if (boxHighlightPainters != null) { | ||
| 469 | for (TokenHighlightType type : tokens.keySet()) { | ||
| 470 | BoxHighlightPainter painter = boxHighlightPainters.get(type); | ||
| 471 | if (painter != null) { | ||
| 472 | setHighlightedTokens(tokens.get(type), painter); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | redraw(); | ||
| 478 | } | ||
| 479 | |||
| 480 | private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) { | ||
| 481 | for (Token token : tokens) { | ||
| 482 | try { | ||
| 483 | this.editor.getHighlighter().addHighlight(token.start, token.end, painter); | ||
| 484 | } catch (BadLocationException ex) { | ||
| 485 | throw new IllegalArgumentException(ex); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | } | ||
| 489 | |||
| 490 | private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 491 | if (reference == null) { | ||
| 492 | infoPanel.clearReference(); | ||
| 493 | return; | ||
| 494 | } | ||
| 495 | |||
| 496 | this.cursorReference = reference; | ||
| 497 | |||
| 498 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference); | ||
| 499 | |||
| 500 | infoPanel.removeAll(); | ||
| 501 | if (translatedReference.entry instanceof ClassEntry) { | ||
| 502 | showClassEntry((ClassEntry) translatedReference.entry); | ||
| 503 | } else if (translatedReference.entry instanceof FieldEntry) { | ||
| 504 | showFieldEntry((FieldEntry) translatedReference.entry); | ||
| 505 | } else if (translatedReference.entry instanceof MethodEntry) { | ||
| 506 | showMethodEntry((MethodEntry) translatedReference.entry); | ||
| 507 | } else if (translatedReference.entry instanceof LocalVariableEntry) { | ||
| 508 | showLocalVariableEntry((LocalVariableEntry) translatedReference.entry); | ||
| 509 | } else { | ||
| 510 | throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName()); | ||
| 511 | } | ||
| 512 | |||
| 513 | redraw(); | ||
| 514 | } | ||
| 515 | |||
| 516 | private void showLocalVariableEntry(LocalVariableEntry entry) { | ||
| 517 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName()); | ||
| 518 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName()); | ||
| 519 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName()); | ||
| 520 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex())); | ||
| 521 | } | ||
| 522 | |||
| 523 | private void showClassEntry(ClassEntry entry) { | ||
| 524 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName()); | ||
| 525 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 526 | } | ||
| 527 | |||
| 528 | private void showFieldEntry(FieldEntry entry) { | ||
| 529 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName()); | ||
| 530 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); | ||
| 531 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString()); | ||
| 532 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 533 | } | ||
| 534 | |||
| 535 | private void showMethodEntry(MethodEntry entry) { | ||
| 536 | if (entry.isConstructor()) { | ||
| 537 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName()); | ||
| 538 | } else { | ||
| 539 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName()); | ||
| 540 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); | ||
| 541 | } | ||
| 542 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString()); | ||
| 543 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 544 | } | ||
| 545 | |||
| 546 | private void addNameValue(JPanel container, String name, String value) { | ||
| 547 | JPanel panel = new JPanel(); | ||
| 548 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | ||
| 549 | |||
| 550 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 551 | label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 552 | panel.add(label); | ||
| 553 | |||
| 554 | panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); | ||
| 555 | |||
| 556 | container.add(panel); | ||
| 557 | } | ||
| 558 | |||
| 559 | private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry<?> entry) { | ||
| 560 | if (!getController().project.isRenamable(entry)) | ||
| 561 | return null; | ||
| 562 | JPanel panel = new JPanel(); | ||
| 563 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | ||
| 564 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 565 | label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 566 | panel.add(label); | ||
| 567 | JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); | ||
| 568 | ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); | ||
| 569 | combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 570 | |||
| 571 | EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry); | ||
| 572 | if (mapping != null) { | ||
| 573 | combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); | ||
| 574 | } else { | ||
| 575 | combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); | ||
| 576 | } | ||
| 577 | |||
| 578 | combo.addItemListener(controller::modifierChange); | ||
| 579 | |||
| 580 | panel.add(combo); | ||
| 581 | |||
| 582 | container.add(panel); | ||
| 583 | |||
| 584 | return combo; | ||
| 585 | } | ||
| 586 | |||
| 587 | public void onCaretMove(int pos, boolean fromClick) { | ||
| 588 | if (controller.project == null) | ||
| 589 | return; | ||
| 590 | EntryRemapper mapper = controller.project.getMapper(); | ||
| 591 | Token token = this.controller.getToken(pos); | ||
| 592 | boolean isToken = token != null; | ||
| 593 | |||
| 594 | cursorReference = this.controller.getReference(token); | ||
| 595 | Entry<?> referenceEntry = cursorReference != null ? cursorReference.entry : null; | ||
| 596 | |||
| 597 | if (referenceEntry != null && shouldNavigateOnClick && fromClick) { | ||
| 598 | shouldNavigateOnClick = false; | ||
| 599 | Entry<?> navigationEntry = referenceEntry; | ||
| 600 | if (cursorReference.context == null) { | ||
| 601 | EntryResolver resolver = mapper.getObfResolver(); | ||
| 602 | navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 603 | } | ||
| 604 | controller.navigateTo(navigationEntry); | ||
| 605 | return; | ||
| 606 | } | ||
| 607 | |||
| 608 | boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; | ||
| 609 | boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; | ||
| 610 | boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); | ||
| 611 | boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); | ||
| 612 | boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); | ||
| 613 | |||
| 614 | if (!isRenaming()) { | ||
| 615 | if (isToken) { | ||
| 616 | showCursorReference(cursorReference); | ||
| 617 | } else { | ||
| 618 | infoPanel.clearReference(); | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | this.popupMenu.renameMenu.setEnabled(isRenamable); | ||
| 623 | this.popupMenu.editJavadocMenu.setEnabled(isRenamable); | ||
| 624 | this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); | ||
| 625 | this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); | ||
| 626 | this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); | ||
| 627 | this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); | ||
| 628 | this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); | ||
| 629 | this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); | ||
| 630 | this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); | ||
| 631 | this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); | ||
| 632 | |||
| 633 | if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) { | ||
| 634 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated")); | ||
| 635 | } else { | ||
| 636 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated")); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | |||
| 640 | public void startDocChange() { | ||
| 641 | EntryReference<Entry<?>, Entry<?>> curReference = cursorReference; | ||
| 642 | if (isRenaming()) { | ||
| 643 | finishRename(false); | ||
| 644 | } | ||
| 645 | renamingReference = curReference; | ||
| 646 | |||
| 647 | // init the text box | ||
| 648 | javadocTextArea = new JTextArea(10, 40); | ||
| 649 | |||
| 650 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); | ||
| 651 | javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs())); | ||
| 652 | |||
| 653 | JavadocDialog.init(frame, javadocTextArea, this::finishDocChange); | ||
| 654 | javadocTextArea.grabFocus(); | ||
| 655 | |||
| 656 | redraw(); | ||
| 657 | } | ||
| 658 | |||
| 659 | private void finishDocChange(JFrame ui, boolean saveName) { | ||
| 660 | String newName = javadocTextArea.getText(); | ||
| 661 | if (saveName) { | ||
| 662 | try { | ||
| 663 | this.controller.changeDocs(renamingReference, newName); | ||
| 664 | this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName)); | ||
| 665 | } catch (IllegalNameException ex) { | ||
| 666 | javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 667 | javadocTextArea.setToolTipText(ex.getReason()); | ||
| 668 | Utils.showToolTipNow(javadocTextArea); | ||
| 669 | return; | ||
| 670 | } | ||
| 671 | |||
| 672 | ui.setVisible(false); | ||
| 673 | showCursorReference(cursorReference); | ||
| 674 | return; | ||
| 675 | } | ||
| 676 | |||
| 677 | // abort the jd change | ||
| 678 | javadocTextArea = null; | ||
| 679 | ui.setVisible(false); | ||
| 680 | showCursorReference(cursorReference); | ||
| 681 | |||
| 682 | this.editor.grabFocus(); | ||
| 683 | |||
| 684 | redraw(); | ||
| 685 | } | ||
| 686 | |||
| 687 | public void startRename() { | ||
| 688 | |||
| 689 | // init the text box | ||
| 690 | renameTextField = new JTextField(); | ||
| 691 | |||
| 692 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); | ||
| 693 | renameTextField.setText(translatedReference.getNameableName()); | ||
| 694 | |||
| 695 | renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height))); | ||
| 696 | renameTextField.addKeyListener(new KeyAdapter() { | ||
| 697 | @Override | ||
| 698 | public void keyPressed(KeyEvent event) { | ||
| 699 | switch (event.getKeyCode()) { | ||
| 700 | case KeyEvent.VK_ENTER: | ||
| 701 | finishRename(true); | ||
| 702 | break; | ||
| 703 | |||
| 704 | case KeyEvent.VK_ESCAPE: | ||
| 705 | finishRename(false); | ||
| 706 | break; | ||
| 707 | default: | ||
| 708 | break; | ||
| 709 | } | ||
| 710 | } | ||
| 711 | }); | ||
| 712 | |||
| 713 | // find the label with the name and replace it with the text box | ||
| 714 | JPanel panel = (JPanel) infoPanel.getComponent(0); | ||
| 715 | panel.remove(panel.getComponentCount() - 1); | ||
| 716 | panel.add(renameTextField); | ||
| 717 | renameTextField.grabFocus(); | ||
| 718 | |||
| 719 | int offset = renameTextField.getText().lastIndexOf('/') + 1; | ||
| 720 | // If it's a class and isn't in the default package, assume that it's deobfuscated. | ||
| 721 | if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0) | ||
| 722 | renameTextField.select(offset, renameTextField.getText().length()); | ||
| 723 | else | ||
| 724 | renameTextField.selectAll(); | ||
| 725 | |||
| 726 | renamingReference = cursorReference; | ||
| 727 | |||
| 728 | redraw(); | ||
| 729 | } | ||
| 730 | |||
| 731 | private void finishRename(boolean saveName) { | ||
| 732 | String newName = renameTextField.getText(); | ||
| 733 | |||
| 734 | if (saveName && newName != null && !newName.isEmpty()) { | ||
| 735 | try { | ||
| 736 | this.controller.rename(renamingReference, newName, true); | ||
| 737 | this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true)); | ||
| 738 | renameTextField = null; | ||
| 739 | } catch (IllegalNameException ex) { | ||
| 740 | renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 741 | renameTextField.setToolTipText(ex.getReason()); | ||
| 742 | Utils.showToolTipNow(renameTextField); | ||
| 743 | } | ||
| 744 | return; | ||
| 745 | } | ||
| 746 | |||
| 747 | renameTextField = null; | ||
| 748 | |||
| 749 | // abort the rename | ||
| 750 | showCursorReference(cursorReference); | ||
| 751 | |||
| 752 | this.editor.grabFocus(); | ||
| 753 | |||
| 754 | redraw(); | ||
| 755 | } | ||
| 756 | |||
| 757 | private boolean isRenaming() { | ||
| 758 | return renameTextField != null; | ||
| 759 | } | ||
| 760 | |||
| 761 | public void showInheritance() { | ||
| 762 | |||
| 763 | if (cursorReference == null) { | ||
| 764 | return; | ||
| 765 | } | ||
| 766 | |||
| 767 | inheritanceTree.setModel(null); | ||
| 768 | |||
| 769 | if (cursorReference.entry instanceof ClassEntry) { | ||
| 770 | // get the class inheritance | ||
| 771 | ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry); | ||
| 772 | |||
| 773 | // show the tree at the root | ||
| 774 | TreePath path = getPathToRoot(classNode); | ||
| 775 | inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 776 | inheritanceTree.expandPath(path); | ||
| 777 | inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); | ||
| 778 | } else if (cursorReference.entry instanceof MethodEntry) { | ||
| 779 | // get the method inheritance | ||
| 780 | MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry); | ||
| 781 | |||
| 782 | // show the tree at the root | ||
| 783 | TreePath path = getPathToRoot(classNode); | ||
| 784 | inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 785 | inheritanceTree.expandPath(path); | ||
| 786 | inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); | ||
| 787 | } | ||
| 788 | |||
| 789 | tabs.setSelectedIndex(0); | ||
| 790 | |||
| 791 | redraw(); | ||
| 792 | } | ||
| 793 | |||
| 794 | public void showImplementations() { | ||
| 795 | |||
| 796 | if (cursorReference == null) { | ||
| 797 | return; | ||
| 798 | } | ||
| 799 | |||
| 800 | implementationsTree.setModel(null); | ||
| 801 | |||
| 802 | DefaultMutableTreeNode node = null; | ||
| 803 | |||
| 804 | // get the class implementations | ||
| 805 | if (cursorReference.entry instanceof ClassEntry) | ||
| 806 | node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry); | ||
| 807 | else // get the method implementations | ||
| 808 | if (cursorReference.entry instanceof MethodEntry) | ||
| 809 | node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry); | ||
| 810 | |||
| 811 | if (node != null) { | ||
| 812 | // show the tree at the root | ||
| 813 | TreePath path = getPathToRoot(node); | ||
| 814 | implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); | ||
| 815 | implementationsTree.expandPath(path); | ||
| 816 | implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); | ||
| 817 | } | ||
| 818 | |||
| 819 | tabs.setSelectedIndex(1); | ||
| 820 | |||
| 821 | redraw(); | ||
| 822 | } | ||
| 823 | |||
| 824 | public void showCalls(boolean recurse) { | ||
| 825 | if (cursorReference == null) { | ||
| 826 | return; | ||
| 827 | } | ||
| 828 | |||
| 829 | if (cursorReference.entry instanceof ClassEntry) { | ||
| 830 | ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); | ||
| 831 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 832 | } else if (cursorReference.entry instanceof FieldEntry) { | ||
| 833 | FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry); | ||
| 834 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 835 | } else if (cursorReference.entry instanceof MethodEntry) { | ||
| 836 | MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse); | ||
| 837 | callsTree.setModel(new DefaultTreeModel(node)); | ||
| 838 | } | ||
| 839 | |||
| 840 | tabs.setSelectedIndex(2); | ||
| 841 | |||
| 842 | redraw(); | ||
| 843 | } | ||
| 844 | |||
| 845 | public void toggleMapping() { | ||
| 846 | Entry<?> obfEntry = cursorReference.entry; | ||
| 847 | Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); | ||
| 848 | |||
| 849 | if (!Objects.equals(obfEntry, deobfEntry)) { | ||
| 850 | this.controller.removeMapping(cursorReference); | ||
| 851 | this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); | ||
| 852 | } else { | ||
| 853 | this.controller.markAsDeobfuscated(cursorReference); | ||
| 854 | this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); | ||
| 855 | } | ||
| 856 | } | ||
| 857 | |||
| 858 | private TreePath getPathToRoot(TreeNode node) { | ||
| 859 | List<TreeNode> nodes = Lists.newArrayList(); | ||
| 860 | TreeNode n = node; | ||
| 861 | do { | ||
| 862 | nodes.add(n); | ||
| 863 | n = n.getParent(); | ||
| 864 | } while (n != null); | ||
| 865 | Collections.reverse(nodes); | ||
| 866 | return new TreePath(nodes.toArray()); | ||
| 867 | } | ||
| 868 | |||
| 869 | public void showDiscardDiag(Function<Integer, Void> callback, String... options) { | ||
| 870 | int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, | ||
| 871 | JOptionPane.QUESTION_MESSAGE, null, options, options[2]); | ||
| 872 | callback.apply(response); | ||
| 873 | } | ||
| 874 | |||
| 875 | public void saveMapping() { | ||
| 876 | if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) | ||
| 877 | this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); | ||
| 878 | } | ||
| 879 | |||
| 880 | public void close() { | ||
| 881 | if (!this.controller.isDirty()) { | ||
| 882 | // everything is saved, we can exit safely | ||
| 883 | exit(); | ||
| 884 | } else { | ||
| 885 | // ask to save before closing | ||
| 886 | showDiscardDiag((response) -> { | ||
| 887 | if (response == JOptionPane.YES_OPTION) { | ||
| 888 | this.saveMapping(); | ||
| 889 | exit(); | ||
| 890 | } else if (response == JOptionPane.NO_OPTION) { | ||
| 891 | exit(); | ||
| 892 | } | ||
| 893 | |||
| 894 | return null; | ||
| 895 | }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); | ||
| 896 | } | ||
| 897 | } | ||
| 898 | |||
| 899 | private void exit() { | ||
| 900 | if (searchDialog != null) { | ||
| 901 | searchDialog.dispose(); | ||
| 902 | } | ||
| 903 | this.frame.dispose(); | ||
| 904 | System.exit(0); | ||
| 905 | } | ||
| 906 | |||
| 907 | public void redraw() { | ||
| 908 | this.frame.validate(); | ||
| 909 | this.frame.repaint(); | ||
| 910 | } | ||
| 911 | |||
| 912 | public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { | ||
| 913 | // package rename | ||
| 914 | if (data instanceof String) { | ||
| 915 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 916 | DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); | ||
| 917 | ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); | ||
| 918 | ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); | ||
| 919 | this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); | ||
| 920 | this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); | ||
| 921 | childNode.setUserObject(dataChild); | ||
| 922 | } | ||
| 923 | node.setUserObject(data); | ||
| 924 | // Ob package will never be modified, just reload deob view | ||
| 925 | this.deobfPanel.deobfClasses.reload(); | ||
| 926 | } | ||
| 927 | // class rename | ||
| 928 | else if (data instanceof ClassEntry) { | ||
| 929 | this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); | ||
| 930 | this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false)); | ||
| 931 | } | ||
| 932 | } | ||
| 933 | |||
| 934 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) { | ||
| 935 | String oldEntry = obfReference.entry.getContainingClass().getPackageName(); | ||
| 936 | String newEntry = new ClassEntry(newName).getPackageName(); | ||
| 937 | moveClassTree(obfReference, oldEntry == null, newEntry == null); | ||
| 938 | } | ||
| 939 | |||
| 940 | // TODO: getExpansionState will *not* actually update itself based on name changes! | ||
| 941 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) { | ||
| 942 | ClassEntry classEntry = obfReference.entry.getContainingClass(); | ||
| 943 | |||
| 944 | List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); | ||
| 945 | List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); | ||
| 946 | |||
| 947 | // Ob -> deob | ||
| 948 | if (!isNewOb) { | ||
| 949 | this.deobfPanel.deobfClasses.moveClassIn(classEntry); | ||
| 950 | this.obfPanel.obfClasses.moveClassOut(classEntry); | ||
| 951 | this.deobfPanel.deobfClasses.reload(); | ||
| 952 | this.obfPanel.obfClasses.reload(); | ||
| 953 | } | ||
| 954 | // Deob -> ob | ||
| 955 | else if (!isOldOb) { | ||
| 956 | this.obfPanel.obfClasses.moveClassIn(classEntry); | ||
| 957 | this.deobfPanel.deobfClasses.moveClassOut(classEntry); | ||
| 958 | this.deobfPanel.deobfClasses.reload(); | ||
| 959 | this.obfPanel.obfClasses.reload(); | ||
| 960 | } | ||
| 961 | // Local move | ||
| 962 | else if (isOldOb) { | ||
| 963 | this.obfPanel.obfClasses.moveClassIn(classEntry); | ||
| 964 | this.obfPanel.obfClasses.reload(); | ||
| 965 | } else { | ||
| 966 | this.deobfPanel.deobfClasses.moveClassIn(classEntry); | ||
| 967 | this.deobfPanel.deobfClasses.reload(); | ||
| 968 | } | ||
| 969 | |||
| 970 | this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf); | ||
| 971 | this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf); | ||
| 972 | } | ||
| 973 | |||
| 974 | public PanelObf getObfPanel() { | ||
| 975 | return obfPanel; | ||
| 976 | } | ||
| 977 | |||
| 978 | public PanelDeobf getDeobfPanel() { | ||
| 979 | return deobfPanel; | ||
| 980 | } | ||
| 981 | |||
| 982 | public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) { | ||
| 983 | this.shouldNavigateOnClick = shouldNavigateOnClick; | ||
| 984 | } | ||
| 985 | |||
| 986 | public SearchDialog getSearchDialog() { | ||
| 987 | if (searchDialog == null) { | ||
| 988 | searchDialog = new SearchDialog(this); | ||
| 989 | } | ||
| 990 | return searchDialog; | ||
| 991 | } | ||
| 992 | |||
| 993 | |||
| 994 | public MenuBar getMenuBar() { | ||
| 995 | return menuBar; | ||
| 996 | } | ||
| 997 | |||
| 998 | public void addMessage(Message message) { | ||
| 999 | JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); | ||
| 1000 | boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); | ||
| 1001 | messageModel.addElement(message); | ||
| 1002 | if (isAtBottom) { | ||
| 1003 | SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); | ||
| 1004 | } | ||
| 1005 | statusLabel.setText(message.translate()); | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | public void setUserList(List<String> users) { | ||
| 1009 | userModel.clear(); | ||
| 1010 | users.forEach(userModel::addElement); | ||
| 1011 | connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size())); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | private void sendMessage() { | ||
| 1015 | String text = chatBox.getText().trim(); | ||
| 1016 | if (!text.isEmpty()) { | ||
| 1017 | getController().sendPacket(new MessageC2SPacket(text)); | ||
| 1018 | } | ||
| 1019 | chatBox.setText(""); | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | /** | ||
| 1023 | * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state. | ||
| 1024 | * This is a central place to update the UI state to prevent multiple code paths from changing the same state, | ||
| 1025 | * causing inconsistencies. | ||
| 1026 | */ | ||
| 1027 | public void updateUiState() { | ||
| 1028 | menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING); | ||
| 1029 | menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect")); | ||
| 1030 | menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED); | ||
| 1031 | menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop")); | ||
| 1032 | |||
| 1033 | menuBar.closeJarMenu.setEnabled(isJarOpen); | ||
| 1034 | menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); | ||
| 1035 | menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); | ||
| 1036 | menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); | ||
| 1037 | menuBar.closeMappingsMenu.setEnabled(isJarOpen); | ||
| 1038 | menuBar.exportSourceMenu.setEnabled(isJarOpen); | ||
| 1039 | menuBar.exportJarMenu.setEnabled(isJarOpen); | ||
| 1040 | |||
| 1041 | connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); | ||
| 1042 | |||
| 1043 | if (connectionState == ConnectionState.NOT_CONNECTED) { | ||
| 1044 | logSplit.setLeftComponent(null); | ||
| 1045 | splitRight.setRightComponent(tabs); | ||
| 1046 | } else { | ||
| 1047 | splitRight.setRightComponent(logSplit); | ||
| 1048 | logSplit.setLeftComponent(tabs); | ||
| 1049 | } | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | public void setConnectionState(ConnectionState state) { | ||
| 1053 | connectionState = state; | ||
| 1054 | statusLabel.setText(I18n.translate("status.ready")); | ||
| 1055 | updateUiState(); | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java deleted file mode 100644 index cccc9e8..0000000 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ /dev/null | |||
| @@ -1,729 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
| 16 | import cuchaz.enigma.Enigma; | ||
| 17 | import cuchaz.enigma.EnigmaProfile; | ||
| 18 | import cuchaz.enigma.EnigmaProject; | ||
| 19 | import cuchaz.enigma.analysis.*; | ||
| 20 | import cuchaz.enigma.api.service.ObfuscationTestService; | ||
| 21 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | ||
| 22 | import cuchaz.enigma.config.Config; | ||
| 23 | import cuchaz.enigma.gui.dialog.ProgressDialog; | ||
| 24 | import cuchaz.enigma.gui.stats.StatsGenerator; | ||
| 25 | import cuchaz.enigma.gui.stats.StatsMember; | ||
| 26 | import cuchaz.enigma.gui.util.History; | ||
| 27 | import cuchaz.enigma.network.EnigmaClient; | ||
| 28 | import cuchaz.enigma.network.EnigmaServer; | ||
| 29 | import cuchaz.enigma.network.IntegratedEnigmaServer; | ||
| 30 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 31 | import cuchaz.enigma.network.packet.LoginC2SPacket; | ||
| 32 | import cuchaz.enigma.network.packet.Packet; | ||
| 33 | import cuchaz.enigma.source.*; | ||
| 34 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 35 | import cuchaz.enigma.translation.Translator; | ||
| 36 | import cuchaz.enigma.translation.mapping.*; | ||
| 37 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 38 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 39 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 40 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 41 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 42 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 43 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 44 | import cuchaz.enigma.utils.I18n; | ||
| 45 | import cuchaz.enigma.utils.Message; | ||
| 46 | import cuchaz.enigma.utils.ReadableToken; | ||
| 47 | import cuchaz.enigma.utils.Utils; | ||
| 48 | import org.objectweb.asm.tree.ClassNode; | ||
| 49 | |||
| 50 | import javax.annotation.Nullable; | ||
| 51 | import javax.swing.JOptionPane; | ||
| 52 | import javax.swing.SwingUtilities; | ||
| 53 | import java.awt.*; | ||
| 54 | import java.awt.event.ItemEvent; | ||
| 55 | import java.io.*; | ||
| 56 | import java.nio.file.Path; | ||
| 57 | import java.util.Collection; | ||
| 58 | import java.util.List; | ||
| 59 | import java.util.Set; | ||
| 60 | import java.util.concurrent.CompletableFuture; | ||
| 61 | import java.util.concurrent.ExecutorService; | ||
| 62 | import java.util.concurrent.Executors; | ||
| 63 | import java.util.stream.Collectors; | ||
| 64 | import java.util.stream.Stream; | ||
| 65 | |||
| 66 | public class GuiController { | ||
| 67 | private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor( | ||
| 68 | new ThreadFactoryBuilder() | ||
| 69 | .setDaemon(true) | ||
| 70 | .setNameFormat("decompiler-thread") | ||
| 71 | .build() | ||
| 72 | ); | ||
| 73 | |||
| 74 | private final Gui gui; | ||
| 75 | public final Enigma enigma; | ||
| 76 | |||
| 77 | public EnigmaProject project; | ||
| 78 | private DecompilerService decompilerService; | ||
| 79 | private Decompiler decompiler; | ||
| 80 | private IndexTreeBuilder indexTreeBuilder; | ||
| 81 | |||
| 82 | private Path loadedMappingPath; | ||
| 83 | private MappingFormat loadedMappingFormat; | ||
| 84 | |||
| 85 | private DecompiledClassSource currentSource; | ||
| 86 | private Source uncommentedSource; | ||
| 87 | |||
| 88 | private EnigmaClient client; | ||
| 89 | private EnigmaServer server; | ||
| 90 | |||
| 91 | public GuiController(Gui gui, EnigmaProfile profile) { | ||
| 92 | this.gui = gui; | ||
| 93 | this.enigma = Enigma.builder() | ||
| 94 | .setProfile(profile) | ||
| 95 | .build(); | ||
| 96 | |||
| 97 | decompilerService = Config.getInstance().decompiler.service; | ||
| 98 | } | ||
| 99 | |||
| 100 | public boolean isDirty() { | ||
| 101 | return project != null && project.getMapper().isDirty(); | ||
| 102 | } | ||
| 103 | |||
| 104 | public CompletableFuture<Void> openJar(final Path jarPath) { | ||
| 105 | this.gui.onStartOpenJar(); | ||
| 106 | |||
| 107 | return ProgressDialog.runOffThread(gui.getFrame(), progress -> { | ||
| 108 | project = enigma.openJar(jarPath, progress); | ||
| 109 | indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); | ||
| 110 | decompiler = createDecompiler(); | ||
| 111 | gui.onFinishOpenJar(jarPath.getFileName().toString()); | ||
| 112 | refreshClasses(); | ||
| 113 | }); | ||
| 114 | } | ||
| 115 | |||
| 116 | private Decompiler createDecompiler() { | ||
| 117 | return decompilerService.create(name -> { | ||
| 118 | ClassNode node = project.getClassCache().getClassNode(name); | ||
| 119 | |||
| 120 | if (node == null) { | ||
| 121 | return null; | ||
| 122 | } | ||
| 123 | |||
| 124 | ClassNode fixedNode = new ClassNode(); | ||
| 125 | node.accept(new SourceFixVisitor(Utils.ASM_VERSION, fixedNode, project.getJarIndex())); | ||
| 126 | return fixedNode; | ||
| 127 | }, new SourceSettings(true, true)); | ||
| 128 | } | ||
| 129 | |||
| 130 | public void closeJar() { | ||
| 131 | this.project = null; | ||
| 132 | this.gui.onCloseJar(); | ||
| 133 | } | ||
| 134 | |||
| 135 | public CompletableFuture<Void> openMappings(MappingFormat format, Path path) { | ||
| 136 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 137 | |||
| 138 | gui.setMappingsFile(path); | ||
| 139 | |||
| 140 | return ProgressDialog.runOffThread(gui.getFrame(), progress -> { | ||
| 141 | try { | ||
| 142 | MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); | ||
| 143 | |||
| 144 | EntryTree<EntryMapping> mappings = format.read(path, progress, saveParameters); | ||
| 145 | project.setMappings(mappings); | ||
| 146 | |||
| 147 | loadedMappingFormat = format; | ||
| 148 | loadedMappingPath = path; | ||
| 149 | |||
| 150 | refreshClasses(); | ||
| 151 | refreshCurrentClass(); | ||
| 152 | } catch (MappingParseException e) { | ||
| 153 | JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); | ||
| 154 | } | ||
| 155 | }); | ||
| 156 | } | ||
| 157 | |||
| 158 | public void openMappings(EntryTree<EntryMapping> mappings) { | ||
| 159 | if (project == null) return; | ||
| 160 | |||
| 161 | project.setMappings(mappings); | ||
| 162 | refreshClasses(); | ||
| 163 | refreshCurrentClass(); | ||
| 164 | } | ||
| 165 | |||
| 166 | public CompletableFuture<Void> saveMappings(Path path) { | ||
| 167 | return saveMappings(path, loadedMappingFormat); | ||
| 168 | } | ||
| 169 | |||
| 170 | public CompletableFuture<Void> saveMappings(Path path, MappingFormat format) { | ||
| 171 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 172 | |||
| 173 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | ||
| 174 | EntryRemapper mapper = project.getMapper(); | ||
| 175 | MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); | ||
| 176 | |||
| 177 | MappingDelta<EntryMapping> delta = mapper.takeMappingDelta(); | ||
| 178 | boolean saveAll = !path.equals(loadedMappingPath); | ||
| 179 | |||
| 180 | loadedMappingFormat = format; | ||
| 181 | loadedMappingPath = path; | ||
| 182 | |||
| 183 | if (saveAll) { | ||
| 184 | format.write(mapper.getObfToDeobf(), path, progress, saveParameters); | ||
| 185 | } else { | ||
| 186 | format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters); | ||
| 187 | } | ||
| 188 | }); | ||
| 189 | } | ||
| 190 | |||
| 191 | public void closeMappings() { | ||
| 192 | if (project == null) return; | ||
| 193 | |||
| 194 | project.setMappings(null); | ||
| 195 | |||
| 196 | this.gui.setMappingsFile(null); | ||
| 197 | refreshClasses(); | ||
| 198 | refreshCurrentClass(); | ||
| 199 | } | ||
| 200 | |||
| 201 | public CompletableFuture<Void> dropMappings() { | ||
| 202 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 203 | |||
| 204 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress)); | ||
| 205 | } | ||
| 206 | |||
| 207 | public CompletableFuture<Void> exportSource(final Path path) { | ||
| 208 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 209 | |||
| 210 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | ||
| 211 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | ||
| 212 | EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); | ||
| 213 | |||
| 214 | source.write(path, progress); | ||
| 215 | }); | ||
| 216 | } | ||
| 217 | |||
| 218 | public CompletableFuture<Void> exportJar(final Path path) { | ||
| 219 | if (project == null) return CompletableFuture.completedFuture(null); | ||
| 220 | |||
| 221 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | ||
| 222 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | ||
| 223 | jar.write(path, progress); | ||
| 224 | }); | ||
| 225 | } | ||
| 226 | |||
| 227 | public Token getToken(int pos) { | ||
| 228 | if (this.currentSource == null) { | ||
| 229 | return null; | ||
| 230 | } | ||
| 231 | return this.currentSource.getIndex().getReferenceToken(pos); | ||
| 232 | } | ||
| 233 | |||
| 234 | @Nullable | ||
| 235 | public EntryReference<Entry<?>, Entry<?>> getReference(Token token) { | ||
| 236 | if (this.currentSource == null) { | ||
| 237 | return null; | ||
| 238 | } | ||
| 239 | return this.currentSource.getIndex().getReference(token); | ||
| 240 | } | ||
| 241 | |||
| 242 | public ReadableToken getReadableToken(Token token) { | ||
| 243 | if (this.currentSource == null) { | ||
| 244 | return null; | ||
| 245 | } | ||
| 246 | |||
| 247 | SourceIndex index = this.currentSource.getIndex(); | ||
| 248 | return new ReadableToken( | ||
| 249 | index.getLineNumber(token.start), | ||
| 250 | index.getColumnNumber(token.start), | ||
| 251 | index.getColumnNumber(token.end) | ||
| 252 | ); | ||
| 253 | } | ||
| 254 | |||
| 255 | /** | ||
| 256 | * Navigates to the declaration with respect to navigation history | ||
| 257 | * | ||
| 258 | * @param entry the entry whose declaration will be navigated to | ||
| 259 | */ | ||
| 260 | public void openDeclaration(Entry<?> entry) { | ||
| 261 | if (entry == null) { | ||
| 262 | throw new IllegalArgumentException("Entry cannot be null!"); | ||
| 263 | } | ||
| 264 | openReference(new EntryReference<>(entry, entry.getName())); | ||
| 265 | } | ||
| 266 | |||
| 267 | /** | ||
| 268 | * Navigates to the reference with respect to navigation history | ||
| 269 | * | ||
| 270 | * @param reference the reference | ||
| 271 | */ | ||
| 272 | public void openReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 273 | if (reference == null) { | ||
| 274 | throw new IllegalArgumentException("Reference cannot be null!"); | ||
| 275 | } | ||
| 276 | if (this.gui.referenceHistory == null) { | ||
| 277 | this.gui.referenceHistory = new History<>(reference); | ||
| 278 | } else { | ||
| 279 | if (!reference.equals(this.gui.referenceHistory.getCurrent())) { | ||
| 280 | this.gui.referenceHistory.push(reference); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | setReference(reference); | ||
| 284 | } | ||
| 285 | |||
| 286 | /** | ||
| 287 | * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. | ||
| 288 | * | ||
| 289 | * @param reference the reference | ||
| 290 | */ | ||
| 291 | private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 292 | // get the reference target class | ||
| 293 | ClassEntry classEntry = reference.getLocationClassEntry(); | ||
| 294 | if (!project.isRenamable(classEntry)) { | ||
| 295 | throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); | ||
| 296 | } | ||
| 297 | |||
| 298 | if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) { | ||
| 299 | // deobfuscate the class, then navigate to the reference | ||
| 300 | loadClass(classEntry, () -> showReference(reference)); | ||
| 301 | } else { | ||
| 302 | showReference(reference); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | /** | ||
| 307 | * Navigates to the reference without modifying history. Assumes the class is loaded. | ||
| 308 | * | ||
| 309 | * @param reference | ||
| 310 | */ | ||
| 311 | private void showReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 312 | Collection<Token> tokens = getTokensForReference(reference); | ||
| 313 | if (tokens.isEmpty()) { | ||
| 314 | // DEBUG | ||
| 315 | System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry())); | ||
| 316 | } else { | ||
| 317 | this.gui.showTokens(tokens); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 322 | EntryRemapper mapper = this.project.getMapper(); | ||
| 323 | |||
| 324 | SourceIndex index = this.currentSource.getIndex(); | ||
| 325 | return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) | ||
| 326 | .stream() | ||
| 327 | .flatMap(r -> index.getReferenceTokens(r).stream()) | ||
| 328 | .collect(Collectors.toList()); | ||
| 329 | } | ||
| 330 | |||
| 331 | public void openPreviousReference() { | ||
| 332 | if (hasPreviousReference()) { | ||
| 333 | setReference(gui.referenceHistory.goBack()); | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | public boolean hasPreviousReference() { | ||
| 338 | return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); | ||
| 339 | } | ||
| 340 | |||
| 341 | public void openNextReference() { | ||
| 342 | if (hasNextReference()) { | ||
| 343 | setReference(gui.referenceHistory.goForward()); | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | public boolean hasNextReference() { | ||
| 348 | return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); | ||
| 349 | } | ||
| 350 | |||
| 351 | public void navigateTo(Entry<?> entry) { | ||
| 352 | if (!project.isRenamable(entry)) { | ||
| 353 | // entry is not in the jar. Ignore it | ||
| 354 | return; | ||
| 355 | } | ||
| 356 | openDeclaration(entry); | ||
| 357 | } | ||
| 358 | |||
| 359 | public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 360 | if (!project.isRenamable(reference.getLocationClassEntry())) { | ||
| 361 | return; | ||
| 362 | } | ||
| 363 | openReference(reference); | ||
| 364 | } | ||
| 365 | |||
| 366 | private void refreshClasses() { | ||
| 367 | List<ClassEntry> obfClasses = Lists.newArrayList(); | ||
| 368 | List<ClassEntry> deobfClasses = Lists.newArrayList(); | ||
| 369 | this.addSeparatedClasses(obfClasses, deobfClasses); | ||
| 370 | this.gui.setObfClasses(obfClasses); | ||
| 371 | this.gui.setDeobfClasses(deobfClasses); | ||
| 372 | } | ||
| 373 | |||
| 374 | public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { | ||
| 375 | EntryRemapper mapper = project.getMapper(); | ||
| 376 | |||
| 377 | Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses(); | ||
| 378 | Stream<ClassEntry> visibleClasses = classes.stream() | ||
| 379 | .filter(entry -> !entry.isInnerClass()); | ||
| 380 | |||
| 381 | visibleClasses.forEach(entry -> { | ||
| 382 | ClassEntry deobfEntry = mapper.deobfuscate(entry); | ||
| 383 | |||
| 384 | List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE); | ||
| 385 | boolean obfuscated = deobfEntry.equals(entry); | ||
| 386 | |||
| 387 | if (obfuscated && !obfService.isEmpty()) { | ||
| 388 | if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { | ||
| 389 | obfuscated = false; | ||
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 393 | if (obfuscated) { | ||
| 394 | obfClasses.add(entry); | ||
| 395 | } else { | ||
| 396 | deobfClasses.add(entry); | ||
| 397 | } | ||
| 398 | }); | ||
| 399 | } | ||
| 400 | |||
| 401 | public void refreshCurrentClass() { | ||
| 402 | refreshCurrentClass(null); | ||
| 403 | } | ||
| 404 | |||
| 405 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 406 | refreshCurrentClass(reference, RefreshMode.MINIMAL); | ||
| 407 | } | ||
| 408 | |||
| 409 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) { | ||
| 410 | if (currentSource != null) { | ||
| 411 | if (reference == null) { | ||
| 412 | int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart()); | ||
| 413 | int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd()); | ||
| 414 | |||
| 415 | Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect(); | ||
| 416 | // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set | ||
| 417 | int anchorModelPos = gui.editor.getSelectionStart(); | ||
| 418 | Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); | ||
| 419 | if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) { | ||
| 420 | anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y)); | ||
| 421 | anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); | ||
| 422 | } | ||
| 423 | int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos); | ||
| 424 | Rectangle anchorViewPos_f = anchorViewPos; | ||
| 425 | int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue(); | ||
| 426 | |||
| 427 | loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> { | ||
| 428 | int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos); | ||
| 429 | Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos); | ||
| 430 | int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y); | ||
| 431 | |||
| 432 | gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd)); | ||
| 433 | // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so | ||
| 434 | // we need to wrap our change to the scroll position inside another invokeLater so it happens after | ||
| 435 | // the caret's own scrolling. | ||
| 436 | SwingUtilities.invokeLater(() -> { | ||
| 437 | gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum())); | ||
| 438 | gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum())); | ||
| 439 | }); | ||
| 440 | }), mode); | ||
| 441 | } else { | ||
| 442 | loadClass(currentSource.getEntry(), () -> showReference(reference), mode); | ||
| 443 | } | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | private void loadClass(ClassEntry classEntry, Runnable callback) { | ||
| 448 | loadClass(classEntry, callback, RefreshMode.MINIMAL); | ||
| 449 | } | ||
| 450 | |||
| 451 | private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) { | ||
| 452 | ClassEntry targetClass = classEntry.getOutermostClass(); | ||
| 453 | |||
| 454 | boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass); | ||
| 455 | if (requiresDecompile) { | ||
| 456 | currentSource = null; // Or the GUI may try to find a nonexistent token | ||
| 457 | gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling")); | ||
| 458 | } | ||
| 459 | |||
| 460 | DECOMPILER_SERVICE.submit(() -> { | ||
| 461 | try { | ||
| 462 | if (requiresDecompile || mode == RefreshMode.JAVADOCS) { | ||
| 463 | currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS); | ||
| 464 | } | ||
| 465 | |||
| 466 | remapSource(project.getMapper().getDeobfuscator()); | ||
| 467 | callback.run(); | ||
| 468 | } catch (Throwable t) { | ||
| 469 | System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); | ||
| 470 | t.printStackTrace(System.err); | ||
| 471 | } | ||
| 472 | }); | ||
| 473 | } | ||
| 474 | |||
| 475 | private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) { | ||
| 476 | try { | ||
| 477 | if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) { | ||
| 478 | uncommentedSource = decompiler.getSource(targetClass.getFullName()); | ||
| 479 | } | ||
| 480 | |||
| 481 | Source source = uncommentedSource.addJavadocs(project.getMapper()); | ||
| 482 | |||
| 483 | if (source == null) { | ||
| 484 | gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); | ||
| 485 | return DecompiledClassSource.text(targetClass, "Unable to find class"); | ||
| 486 | } | ||
| 487 | |||
| 488 | SourceIndex index = source.index(); | ||
| 489 | index.resolveReferences(project.getMapper().getObfResolver()); | ||
| 490 | |||
| 491 | return new DecompiledClassSource(targetClass, index); | ||
| 492 | } catch (Throwable t) { | ||
| 493 | StringWriter traceWriter = new StringWriter(); | ||
| 494 | t.printStackTrace(new PrintWriter(traceWriter)); | ||
| 495 | |||
| 496 | return DecompiledClassSource.text(targetClass, traceWriter.toString()); | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | private void remapSource(Translator translator) { | ||
| 501 | if (currentSource == null) { | ||
| 502 | return; | ||
| 503 | } | ||
| 504 | |||
| 505 | currentSource.remapSource(project, translator); | ||
| 506 | |||
| 507 | gui.setEditorTheme(Config.getInstance().lookAndFeel); | ||
| 508 | gui.setSource(currentSource); | ||
| 509 | } | ||
| 510 | |||
| 511 | public void modifierChange(ItemEvent event) { | ||
| 512 | if (event.getStateChange() == ItemEvent.SELECTED) { | ||
| 513 | EntryRemapper mapper = project.getMapper(); | ||
| 514 | Entry<?> entry = gui.cursorReference.entry; | ||
| 515 | AccessModifier modifier = (AccessModifier) event.getItem(); | ||
| 516 | |||
| 517 | EntryMapping mapping = mapper.getDeobfMapping(entry); | ||
| 518 | if (mapping != null) { | ||
| 519 | mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); | ||
| 520 | } else { | ||
| 521 | mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); | ||
| 522 | } | ||
| 523 | |||
| 524 | refreshCurrentClass(); | ||
| 525 | } | ||
| 526 | } | ||
| 527 | |||
| 528 | public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { | ||
| 529 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 530 | ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); | ||
| 531 | return ClassInheritanceTreeNode.findNode(rootNode, entry); | ||
| 532 | } | ||
| 533 | |||
| 534 | public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { | ||
| 535 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 536 | return this.indexTreeBuilder.buildClassImplementations(translator, entry); | ||
| 537 | } | ||
| 538 | |||
| 539 | public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { | ||
| 540 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 541 | MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry); | ||
| 542 | return MethodInheritanceTreeNode.findNode(rootNode, entry); | ||
| 543 | } | ||
| 544 | |||
| 545 | public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { | ||
| 546 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 547 | List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry); | ||
| 548 | if (rootNodes.isEmpty()) { | ||
| 549 | return null; | ||
| 550 | } | ||
| 551 | if (rootNodes.size() > 1) { | ||
| 552 | System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); | ||
| 553 | } | ||
| 554 | return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); | ||
| 555 | } | ||
| 556 | |||
| 557 | public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { | ||
| 558 | Translator deobfuscator = project.getMapper().getDeobfuscator(); | ||
| 559 | ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); | ||
| 560 | rootNode.load(project.getJarIndex(), true); | ||
| 561 | return rootNode; | ||
| 562 | } | ||
| 563 | |||
| 564 | public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { | ||
| 565 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 566 | FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); | ||
| 567 | rootNode.load(project.getJarIndex(), true); | ||
| 568 | return rootNode; | ||
| 569 | } | ||
| 570 | |||
| 571 | public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { | ||
| 572 | Translator translator = project.getMapper().getDeobfuscator(); | ||
| 573 | MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); | ||
| 574 | rootNode.load(project.getJarIndex(), true, recursive); | ||
| 575 | return rootNode; | ||
| 576 | } | ||
| 577 | |||
| 578 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { | ||
| 579 | rename(reference, newName, refreshClassTree, true); | ||
| 580 | } | ||
| 581 | |||
| 582 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) { | ||
| 583 | Entry<?> entry = reference.getNameableEntry(); | ||
| 584 | project.getMapper().mapFromObf(entry, new EntryMapping(newName)); | ||
| 585 | |||
| 586 | if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | ||
| 587 | this.gui.moveClassTree(reference, newName); | ||
| 588 | |||
| 589 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 590 | } | ||
| 591 | |||
| 592 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 593 | removeMapping(reference, true); | ||
| 594 | } | ||
| 595 | |||
| 596 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | ||
| 597 | project.getMapper().removeByObf(reference.getNameableEntry()); | ||
| 598 | |||
| 599 | if (reference.entry instanceof ClassEntry) | ||
| 600 | this.gui.moveClassTree(reference, false, true); | ||
| 601 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 602 | } | ||
| 603 | |||
| 604 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { | ||
| 605 | changeDocs(reference, updatedDocs, true); | ||
| 606 | } | ||
| 607 | |||
| 608 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) { | ||
| 609 | changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs); | ||
| 610 | |||
| 611 | refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS); | ||
| 612 | } | ||
| 613 | |||
| 614 | private void changeDoc(Entry<?> obfEntry, String newDoc) { | ||
| 615 | EntryRemapper mapper = project.getMapper(); | ||
| 616 | if (mapper.getDeobfMapping(obfEntry) == null) { | ||
| 617 | markAsDeobfuscated(obfEntry, false); // NPE | ||
| 618 | } | ||
| 619 | mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); | ||
| 620 | } | ||
| 621 | |||
| 622 | private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) { | ||
| 623 | EntryRemapper mapper = project.getMapper(); | ||
| 624 | mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); | ||
| 625 | } | ||
| 626 | |||
| 627 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 628 | markAsDeobfuscated(reference, true); | ||
| 629 | } | ||
| 630 | |||
| 631 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | ||
| 632 | EntryRemapper mapper = project.getMapper(); | ||
| 633 | Entry<?> entry = reference.getNameableEntry(); | ||
| 634 | mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); | ||
| 635 | |||
| 636 | if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | ||
| 637 | this.gui.moveClassTree(reference, true, false); | ||
| 638 | |||
| 639 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 640 | } | ||
| 641 | |||
| 642 | public void openStats(Set<StatsMember> includedMembers) { | ||
| 643 | ProgressDialog.runOffThread(gui.getFrame(), progress -> { | ||
| 644 | String data = new StatsGenerator(project).generate(progress, includedMembers); | ||
| 645 | |||
| 646 | try { | ||
| 647 | File statsFile = File.createTempFile("stats", ".html"); | ||
| 648 | |||
| 649 | try (FileWriter w = new FileWriter(statsFile)) { | ||
| 650 | w.write( | ||
| 651 | Utils.readResourceToString("/stats.html") | ||
| 652 | .replace("/*data*/", data) | ||
| 653 | ); | ||
| 654 | } | ||
| 655 | |||
| 656 | Desktop.getDesktop().open(statsFile); | ||
| 657 | } catch (IOException e) { | ||
| 658 | throw new Error(e); | ||
| 659 | } | ||
| 660 | }); | ||
| 661 | } | ||
| 662 | |||
| 663 | public void setDecompiler(DecompilerService service) { | ||
| 664 | uncommentedSource = null; | ||
| 665 | decompilerService = service; | ||
| 666 | decompiler = createDecompiler(); | ||
| 667 | refreshCurrentClass(null, RefreshMode.FULL); | ||
| 668 | } | ||
| 669 | |||
| 670 | public EnigmaClient getClient() { | ||
| 671 | return client; | ||
| 672 | } | ||
| 673 | |||
| 674 | public EnigmaServer getServer() { | ||
| 675 | return server; | ||
| 676 | } | ||
| 677 | |||
| 678 | public void createClient(String username, String ip, int port, char[] password) throws IOException { | ||
| 679 | client = new EnigmaClient(this, ip, port); | ||
| 680 | client.connect(); | ||
| 681 | client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username)); | ||
| 682 | gui.setConnectionState(ConnectionState.CONNECTED); | ||
| 683 | } | ||
| 684 | |||
| 685 | public void createServer(int port, char[] password) throws IOException { | ||
| 686 | server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port); | ||
| 687 | server.start(); | ||
| 688 | client = new EnigmaClient(this, "127.0.0.1", port); | ||
| 689 | client.connect(); | ||
| 690 | client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); | ||
| 691 | gui.setConnectionState(ConnectionState.HOSTING); | ||
| 692 | } | ||
| 693 | |||
| 694 | public synchronized void disconnectIfConnected(String reason) { | ||
| 695 | if (client == null && server == null) { | ||
| 696 | return; | ||
| 697 | } | ||
| 698 | |||
| 699 | if (client != null) { | ||
| 700 | client.disconnect(); | ||
| 701 | } | ||
| 702 | if (server != null) { | ||
| 703 | server.stop(); | ||
| 704 | } | ||
| 705 | client = null; | ||
| 706 | server = null; | ||
| 707 | SwingUtilities.invokeLater(() -> { | ||
| 708 | if (reason != null) { | ||
| 709 | JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); | ||
| 710 | } | ||
| 711 | gui.setConnectionState(ConnectionState.NOT_CONNECTED); | ||
| 712 | }); | ||
| 713 | } | ||
| 714 | |||
| 715 | public void sendPacket(Packet<ServerPacketHandler> packet) { | ||
| 716 | if (client != null) { | ||
| 717 | client.sendPacket(packet); | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | public void addMessage(Message message) { | ||
| 722 | gui.addMessage(message); | ||
| 723 | } | ||
| 724 | |||
| 725 | public void updateUserList(List<String> users) { | ||
| 726 | gui.setUserList(users); | ||
| 727 | } | ||
| 728 | |||
| 729 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java deleted file mode 100644 index c9e38cb..0000000 --- a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java +++ /dev/null | |||
| @@ -1,24 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import java.awt.Component; | ||
| 4 | |||
| 5 | import javax.swing.DefaultListCellRenderer; | ||
| 6 | import javax.swing.JList; | ||
| 7 | |||
| 8 | import cuchaz.enigma.utils.Message; | ||
| 9 | |||
| 10 | // For now, just render the translated text. | ||
| 11 | // TODO: Icons or something later? | ||
| 12 | public class MessageListCellRenderer extends DefaultListCellRenderer { | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { | ||
| 16 | super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); | ||
| 17 | Message message = (Message) value; | ||
| 18 | if (message != null) { | ||
| 19 | setText(message.translate()); | ||
| 20 | } | ||
| 21 | return this; | ||
| 22 | } | ||
| 23 | |||
| 24 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java deleted file mode 100644 index 05d90a9..0000000 --- a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java +++ /dev/null | |||
| @@ -1,42 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 15 | import cuchaz.enigma.config.Config; | ||
| 16 | |||
| 17 | import javax.swing.*; | ||
| 18 | import javax.swing.tree.TreeCellRenderer; | ||
| 19 | import java.awt.*; | ||
| 20 | |||
| 21 | class MethodTreeCellRenderer implements TreeCellRenderer { | ||
| 22 | |||
| 23 | private final TreeCellRenderer parent; | ||
| 24 | |||
| 25 | MethodTreeCellRenderer(TreeCellRenderer parent) { | ||
| 26 | this.parent = parent; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { | ||
| 31 | Component ret = parent.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); | ||
| 32 | Config config = Config.getInstance(); | ||
| 33 | if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) { | ||
| 34 | ret.setForeground(new Color(config.defaultTextColor)); | ||
| 35 | ret.setFont(ret.getFont().deriveFont(Font.PLAIN)); | ||
| 36 | } else { | ||
| 37 | ret.setForeground(new Color(config.numberColor)); | ||
| 38 | ret.setFont(ret.getFont().deriveFont(Font.ITALIC)); | ||
| 39 | } | ||
| 40 | return ret; | ||
| 41 | } | ||
| 42 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java b/src/main/java/cuchaz/enigma/gui/QuickFindAction.java deleted file mode 100644 index b7fa2eb..0000000 --- a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java +++ /dev/null | |||
| @@ -1,45 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import de.sciss.syntaxpane.SyntaxDocument; | ||
| 4 | import de.sciss.syntaxpane.actions.DefaultSyntaxAction; | ||
| 5 | |||
| 6 | import javax.swing.text.JTextComponent; | ||
| 7 | import java.awt.event.ActionEvent; | ||
| 8 | |||
| 9 | public final class QuickFindAction extends DefaultSyntaxAction { | ||
| 10 | public QuickFindAction() { | ||
| 11 | super("quick-find"); | ||
| 12 | } | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public void actionPerformed(JTextComponent target, SyntaxDocument document, int dot, ActionEvent event) { | ||
| 16 | Data data = Data.get(target); | ||
| 17 | data.showFindDialog(target); | ||
| 18 | } | ||
| 19 | |||
| 20 | private static class Data { | ||
| 21 | private static final String KEY = "enigma-find-data"; | ||
| 22 | private EnigmaQuickFindDialog findDialog; | ||
| 23 | |||
| 24 | private Data() { | ||
| 25 | } | ||
| 26 | |||
| 27 | public static Data get(JTextComponent target) { | ||
| 28 | Object o = target.getDocument().getProperty(KEY); | ||
| 29 | if (o instanceof Data) { | ||
| 30 | return (Data) o; | ||
| 31 | } | ||
| 32 | |||
| 33 | Data data = new Data(); | ||
| 34 | target.getDocument().putProperty(KEY, data); | ||
| 35 | return data; | ||
| 36 | } | ||
| 37 | |||
| 38 | public void showFindDialog(JTextComponent target) { | ||
| 39 | if (findDialog == null) { | ||
| 40 | findDialog = new EnigmaQuickFindDialog(target); | ||
| 41 | } | ||
| 42 | findDialog.showFor(target); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/src/main/java/cuchaz/enigma/gui/RefreshMode.java deleted file mode 100644 index 87cb83b..0000000 --- a/src/main/java/cuchaz/enigma/gui/RefreshMode.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | public enum RefreshMode { | ||
| 4 | MINIMAL, | ||
| 5 | JAVADOCS, | ||
| 6 | FULL | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java deleted file mode 100644 index f38f44e..0000000 --- a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java +++ /dev/null | |||
| @@ -1,64 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.Token; | ||
| 4 | |||
| 5 | import java.util.HashMap; | ||
| 6 | import java.util.Map; | ||
| 7 | |||
| 8 | public class SourceRemapper { | ||
| 9 | private final String source; | ||
| 10 | private final Iterable<Token> tokens; | ||
| 11 | |||
| 12 | public SourceRemapper(String source, Iterable<Token> tokens) { | ||
| 13 | this.source = source; | ||
| 14 | this.tokens = tokens; | ||
| 15 | } | ||
| 16 | |||
| 17 | public Result remap(Remapper remapper) { | ||
| 18 | StringBuffer remappedSource = new StringBuffer(source); | ||
| 19 | Map<Token, Token> remappedTokens = new HashMap<>(); | ||
| 20 | |||
| 21 | int accumulatedOffset = 0; | ||
| 22 | for (Token token : tokens) { | ||
| 23 | Token movedToken = token.move(accumulatedOffset); | ||
| 24 | |||
| 25 | String remappedName = remapper.remap(token, movedToken); | ||
| 26 | if (remappedName != null) { | ||
| 27 | accumulatedOffset += movedToken.getRenameOffset(remappedName); | ||
| 28 | movedToken.rename(remappedSource, remappedName); | ||
| 29 | } | ||
| 30 | |||
| 31 | if (!token.equals(movedToken)) { | ||
| 32 | remappedTokens.put(token, movedToken); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | return new Result(remappedSource.toString(), remappedTokens); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static class Result { | ||
| 40 | private final String remappedSource; | ||
| 41 | private final Map<Token, Token> remappedTokens; | ||
| 42 | |||
| 43 | Result(String remappedSource, Map<Token, Token> remappedTokens) { | ||
| 44 | this.remappedSource = remappedSource; | ||
| 45 | this.remappedTokens = remappedTokens; | ||
| 46 | } | ||
| 47 | |||
| 48 | public String getSource() { | ||
| 49 | return remappedSource; | ||
| 50 | } | ||
| 51 | |||
| 52 | public Token getRemappedToken(Token token) { | ||
| 53 | return remappedTokens.getOrDefault(token, token); | ||
| 54 | } | ||
| 55 | |||
| 56 | public boolean isEmpty() { | ||
| 57 | return remappedTokens.isEmpty(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | public interface Remapper { | ||
| 62 | String remap(Token token, Token movedToken); | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java deleted file mode 100644 index 7375111..0000000 --- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java +++ /dev/null | |||
| @@ -1,35 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.Token; | ||
| 15 | |||
| 16 | import javax.swing.*; | ||
| 17 | import java.awt.*; | ||
| 18 | |||
| 19 | public class TokenListCellRenderer implements ListCellRenderer<Token> { | ||
| 20 | |||
| 21 | private GuiController controller; | ||
| 22 | private DefaultListCellRenderer defaultRenderer; | ||
| 23 | |||
| 24 | public TokenListCellRenderer(GuiController controller) { | ||
| 25 | this.controller = controller; | ||
| 26 | this.defaultRenderer = new DefaultListCellRenderer(); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) { | ||
| 31 | JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); | ||
| 32 | label.setText(this.controller.getReadableToken(token).toString()); | ||
| 33 | return label; | ||
| 34 | } | ||
| 35 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java deleted file mode 100644 index 43b8265..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java +++ /dev/null | |||
| @@ -1,69 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.dialog; | ||
| 13 | |||
| 14 | import cuchaz.enigma.Constants; | ||
| 15 | import cuchaz.enigma.utils.I18n; | ||
| 16 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 17 | import cuchaz.enigma.utils.Utils; | ||
| 18 | |||
| 19 | import javax.swing.*; | ||
| 20 | import java.awt.*; | ||
| 21 | import java.io.IOException; | ||
| 22 | |||
| 23 | public class AboutDialog { | ||
| 24 | |||
| 25 | public static void show(JFrame parent) { | ||
| 26 | // init frame | ||
| 27 | final JFrame frame = new JFrame(String.format(I18n.translate("menu.help.about.title"), Constants.NAME)); | ||
| 28 | final Container pane = frame.getContentPane(); | ||
| 29 | pane.setLayout(new FlowLayout()); | ||
| 30 | |||
| 31 | // load the content | ||
| 32 | try { | ||
| 33 | String html = Utils.readResourceToString("/about.html"); | ||
| 34 | html = String.format(html, Constants.NAME, Constants.VERSION); | ||
| 35 | JLabel label = new JLabel(html); | ||
| 36 | label.setHorizontalAlignment(JLabel.CENTER); | ||
| 37 | pane.add(label); | ||
| 38 | } catch (IOException ex) { | ||
| 39 | throw new Error(ex); | ||
| 40 | } | ||
| 41 | |||
| 42 | // show the link | ||
| 43 | String html = "<html><a href=\"%s\">%s</a></html>"; | ||
| 44 | html = String.format(html, Constants.URL, Constants.URL); | ||
| 45 | JButton link = new JButton(html); | ||
| 46 | link.addActionListener(event -> Utils.openUrl(Constants.URL)); | ||
| 47 | link.setBorderPainted(false); | ||
| 48 | link.setOpaque(false); | ||
| 49 | link.setBackground(Color.WHITE); | ||
| 50 | link.setCursor(new Cursor(Cursor.HAND_CURSOR)); | ||
| 51 | link.setFocusable(false); | ||
| 52 | JPanel linkPanel = new JPanel(); | ||
| 53 | linkPanel.add(link); | ||
| 54 | pane.add(linkPanel); | ||
| 55 | |||
| 56 | // show ok button | ||
| 57 | JButton okButton = new JButton(I18n.translate("menu.help.about.ok")); | ||
| 58 | pane.add(okButton); | ||
| 59 | okButton.addActionListener(arg0 -> frame.dispose()); | ||
| 60 | |||
| 61 | // show the frame | ||
| 62 | pane.doLayout(); | ||
| 63 | frame.setSize(ScaleUtil.getDimension(400, 220)); | ||
| 64 | frame.setResizable(false); | ||
| 65 | frame.setLocationRelativeTo(parent); | ||
| 66 | frame.setVisible(true); | ||
| 67 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | ||
| 68 | } | ||
| 69 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java deleted file mode 100644 index 64219ab..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java +++ /dev/null | |||
| @@ -1,50 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.awt.event.KeyAdapter; | ||
| 5 | import java.awt.event.KeyEvent; | ||
| 6 | |||
| 7 | import javax.swing.JButton; | ||
| 8 | import javax.swing.JFrame; | ||
| 9 | import javax.swing.JLabel; | ||
| 10 | import javax.swing.JPanel; | ||
| 11 | |||
| 12 | import cuchaz.enigma.gui.Gui; | ||
| 13 | import cuchaz.enigma.utils.I18n; | ||
| 14 | |||
| 15 | public class ChangeDialog { | ||
| 16 | |||
| 17 | public static void show(Gui gui) { | ||
| 18 | // init frame | ||
| 19 | JFrame frame = new JFrame(I18n.translate("menu.view.change.title")); | ||
| 20 | JPanel textPanel = new JPanel(); | ||
| 21 | JPanel buttonPanel = new JPanel(); | ||
| 22 | frame.setLayout(new BorderLayout()); | ||
| 23 | frame.add(BorderLayout.NORTH, textPanel); | ||
| 24 | frame.add(BorderLayout.SOUTH, buttonPanel); | ||
| 25 | |||
| 26 | // show text | ||
| 27 | JLabel text = new JLabel((I18n.translate("menu.view.change.summary"))); | ||
| 28 | text.setHorizontalAlignment(JLabel.CENTER); | ||
| 29 | textPanel.add(text); | ||
| 30 | |||
| 31 | // show ok button | ||
| 32 | JButton okButton = new JButton(I18n.translate("menu.view.change.ok")); | ||
| 33 | buttonPanel.add(okButton); | ||
| 34 | okButton.addActionListener(event -> frame.dispose()); | ||
| 35 | okButton.addKeyListener(new KeyAdapter() { | ||
| 36 | @Override | ||
| 37 | public void keyPressed(KeyEvent e) { | ||
| 38 | if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { | ||
| 39 | frame.dispose(); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | }); | ||
| 43 | |||
| 44 | // show the frame | ||
| 45 | frame.pack(); | ||
| 46 | frame.setVisible(true); | ||
| 47 | frame.setResizable(false); | ||
| 48 | frame.setLocationRelativeTo(gui.getFrame()); | ||
| 49 | } | ||
| 50 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java deleted file mode 100644 index c5f505c..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java +++ /dev/null | |||
| @@ -1,82 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.utils.I18n; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import java.awt.Frame; | ||
| 8 | |||
| 9 | public class ConnectToServerDialog { | ||
| 10 | |||
| 11 | public static Result show(Frame parentComponent) { | ||
| 12 | JTextField usernameField = new JTextField(System.getProperty("user.name"), 20); | ||
| 13 | JPanel usernameRow = new JPanel(); | ||
| 14 | usernameRow.add(new JLabel(I18n.translate("prompt.connect.username"))); | ||
| 15 | usernameRow.add(usernameField); | ||
| 16 | JTextField ipField = new JTextField(20); | ||
| 17 | JPanel ipRow = new JPanel(); | ||
| 18 | ipRow.add(new JLabel(I18n.translate("prompt.connect.ip"))); | ||
| 19 | ipRow.add(ipField); | ||
| 20 | JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); | ||
| 21 | JPanel portRow = new JPanel(); | ||
| 22 | portRow.add(new JLabel(I18n.translate("prompt.port"))); | ||
| 23 | portRow.add(portField); | ||
| 24 | JPasswordField passwordField = new JPasswordField(20); | ||
| 25 | JPanel passwordRow = new JPanel(); | ||
| 26 | passwordRow.add(new JLabel(I18n.translate("prompt.password"))); | ||
| 27 | passwordRow.add(passwordField); | ||
| 28 | |||
| 29 | int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION); | ||
| 30 | if (response != JOptionPane.OK_OPTION) { | ||
| 31 | return null; | ||
| 32 | } | ||
| 33 | |||
| 34 | String username = usernameField.getText(); | ||
| 35 | String ip = ipField.getText(); | ||
| 36 | int port; | ||
| 37 | try { | ||
| 38 | port = Integer.parseInt(portField.getText()); | ||
| 39 | } catch (NumberFormatException e) { | ||
| 40 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | if (port < 0 || port >= 65536) { | ||
| 44 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); | ||
| 45 | return null; | ||
| 46 | } | ||
| 47 | char[] password = passwordField.getPassword(); | ||
| 48 | |||
| 49 | return new Result(username, ip, port, password); | ||
| 50 | } | ||
| 51 | |||
| 52 | public static class Result { | ||
| 53 | private final String username; | ||
| 54 | private final String ip; | ||
| 55 | private final int port; | ||
| 56 | private final char[] password; | ||
| 57 | |||
| 58 | public Result(String username, String ip, int port, char[] password) { | ||
| 59 | this.username = username; | ||
| 60 | this.ip = ip; | ||
| 61 | this.port = port; | ||
| 62 | this.password = password; | ||
| 63 | } | ||
| 64 | |||
| 65 | public String getUsername() { | ||
| 66 | return username; | ||
| 67 | } | ||
| 68 | |||
| 69 | public String getIp() { | ||
| 70 | return ip; | ||
| 71 | } | ||
| 72 | |||
| 73 | public int getPort() { | ||
| 74 | return port; | ||
| 75 | } | ||
| 76 | |||
| 77 | public char[] getPassword() { | ||
| 78 | return password; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java deleted file mode 100644 index 908b42e..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java +++ /dev/null | |||
| @@ -1,105 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.dialog; | ||
| 13 | |||
| 14 | import cuchaz.enigma.Constants; | ||
| 15 | import cuchaz.enigma.utils.I18n; | ||
| 16 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 17 | import cuchaz.enigma.utils.Utils; | ||
| 18 | |||
| 19 | import javax.swing.*; | ||
| 20 | import java.awt.*; | ||
| 21 | import java.io.PrintWriter; | ||
| 22 | import java.io.StringWriter; | ||
| 23 | import java.io.FileWriter; | ||
| 24 | import java.io.File; | ||
| 25 | import java.io.IOException; | ||
| 26 | |||
| 27 | public class CrashDialog { | ||
| 28 | |||
| 29 | private static CrashDialog instance = null; | ||
| 30 | |||
| 31 | private JFrame frame; | ||
| 32 | private JTextArea text; | ||
| 33 | |||
| 34 | private CrashDialog(JFrame parent) { | ||
| 35 | // init frame | ||
| 36 | frame = new JFrame(String.format(I18n.translate("crash.title"), Constants.NAME)); | ||
| 37 | final Container pane = frame.getContentPane(); | ||
| 38 | pane.setLayout(new BorderLayout()); | ||
| 39 | |||
| 40 | JLabel label = new JLabel(String.format(I18n.translate("crash.summary"), Constants.NAME)); | ||
| 41 | label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); | ||
| 42 | pane.add(label, BorderLayout.NORTH); | ||
| 43 | |||
| 44 | // report panel | ||
| 45 | text = new JTextArea(); | ||
| 46 | text.setTabSize(2); | ||
| 47 | pane.add(new JScrollPane(text), BorderLayout.CENTER); | ||
| 48 | |||
| 49 | // buttons panel | ||
| 50 | JPanel buttonsPanel = new JPanel(); | ||
| 51 | buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.LINE_AXIS)); | ||
| 52 | JButton exportButton = new JButton(I18n.translate("crash.export")); | ||
| 53 | exportButton.addActionListener(event -> { | ||
| 54 | JFileChooser chooser = new JFileChooser(); | ||
| 55 | chooser.setSelectedFile(new File("enigma_crash.log")); | ||
| 56 | if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { | ||
| 57 | try { | ||
| 58 | File file = chooser.getSelectedFile(); | ||
| 59 | FileWriter writer = new FileWriter(file); | ||
| 60 | writer.write(instance.text.getText()); | ||
| 61 | writer.close(); | ||
| 62 | } catch (IOException ex) { | ||
| 63 | ex.printStackTrace(); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | }); | ||
| 67 | buttonsPanel.add(exportButton); | ||
| 68 | buttonsPanel.add(Box.createHorizontalGlue()); | ||
| 69 | buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("crash.exit.warning")))); | ||
| 70 | JButton ignoreButton = new JButton(I18n.translate("crash.ignore")); | ||
| 71 | ignoreButton.addActionListener(event -> { | ||
| 72 | // close (hide) the dialog | ||
| 73 | frame.setVisible(false); | ||
| 74 | }); | ||
| 75 | buttonsPanel.add(ignoreButton); | ||
| 76 | JButton exitButton = new JButton(I18n.translate("crash.exit")); | ||
| 77 | exitButton.addActionListener(event -> { | ||
| 78 | // exit enigma | ||
| 79 | System.exit(1); | ||
| 80 | }); | ||
| 81 | buttonsPanel.add(exitButton); | ||
| 82 | pane.add(buttonsPanel, BorderLayout.SOUTH); | ||
| 83 | |||
| 84 | // show the frame | ||
| 85 | frame.setSize(ScaleUtil.getDimension(600, 400)); | ||
| 86 | frame.setLocationRelativeTo(parent); | ||
| 87 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | ||
| 88 | } | ||
| 89 | |||
| 90 | public static void init(JFrame parent) { | ||
| 91 | instance = new CrashDialog(parent); | ||
| 92 | } | ||
| 93 | |||
| 94 | public static void show(Throwable ex) { | ||
| 95 | // get the error report | ||
| 96 | StringWriter buf = new StringWriter(); | ||
| 97 | ex.printStackTrace(new PrintWriter(buf)); | ||
| 98 | String report = buf.toString(); | ||
| 99 | |||
| 100 | // show it! | ||
| 101 | instance.text.setText(report); | ||
| 102 | instance.frame.doLayout(); | ||
| 103 | instance.frame.setVisible(true); | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java deleted file mode 100644 index eea1dff..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java +++ /dev/null | |||
| @@ -1,65 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.utils.I18n; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import java.awt.*; | ||
| 8 | |||
| 9 | public class CreateServerDialog { | ||
| 10 | |||
| 11 | public static Result show(Frame parentComponent) { | ||
| 12 | JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); | ||
| 13 | JPanel portRow = new JPanel(); | ||
| 14 | portRow.add(new JLabel(I18n.translate("prompt.port"))); | ||
| 15 | portRow.add(portField); | ||
| 16 | JPasswordField passwordField = new JPasswordField(20); | ||
| 17 | JPanel passwordRow = new JPanel(); | ||
| 18 | passwordRow.add(new JLabel(I18n.translate("prompt.password"))); | ||
| 19 | passwordRow.add(passwordField); | ||
| 20 | |||
| 21 | int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{portRow, passwordRow}, I18n.translate("prompt.create_server.title"), JOptionPane.OK_CANCEL_OPTION); | ||
| 22 | if (response != JOptionPane.OK_OPTION) { | ||
| 23 | return null; | ||
| 24 | } | ||
| 25 | |||
| 26 | int port; | ||
| 27 | try { | ||
| 28 | port = Integer.parseInt(portField.getText()); | ||
| 29 | } catch (NumberFormatException e) { | ||
| 30 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); | ||
| 31 | return null; | ||
| 32 | } | ||
| 33 | if (port < 0 || port >= 65536) { | ||
| 34 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); | ||
| 35 | return null; | ||
| 36 | } | ||
| 37 | |||
| 38 | char[] password = passwordField.getPassword(); | ||
| 39 | if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { | ||
| 40 | JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.password.too_long"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | |||
| 44 | return new Result(port, password); | ||
| 45 | } | ||
| 46 | |||
| 47 | public static class Result { | ||
| 48 | private final int port; | ||
| 49 | private final char[] password; | ||
| 50 | |||
| 51 | public Result(int port, char[] password) { | ||
| 52 | this.port = port; | ||
| 53 | this.password = password; | ||
| 54 | } | ||
| 55 | |||
| 56 | public int getPort() { | ||
| 57 | return port; | ||
| 58 | } | ||
| 59 | |||
| 60 | public char[] getPassword() { | ||
| 61 | return password; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java deleted file mode 100644 index 7e41441..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java +++ /dev/null | |||
| @@ -1,159 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.dialog; | ||
| 13 | |||
| 14 | import cuchaz.enigma.utils.I18n; | ||
| 15 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 16 | import cuchaz.enigma.utils.Utils; | ||
| 17 | |||
| 18 | import javax.swing.*; | ||
| 19 | import javax.swing.text.html.HTML; | ||
| 20 | |||
| 21 | import java.awt.*; | ||
| 22 | import java.awt.event.KeyAdapter; | ||
| 23 | import java.awt.event.KeyEvent; | ||
| 24 | |||
| 25 | public class JavadocDialog { | ||
| 26 | |||
| 27 | private static JavadocDialog instance = null; | ||
| 28 | |||
| 29 | private JFrame frame; | ||
| 30 | |||
| 31 | private JavadocDialog(JFrame parent, JTextArea text, Callback callback) { | ||
| 32 | // init frame | ||
| 33 | frame = new JFrame(I18n.translate("javadocs.edit")); | ||
| 34 | final Container pane = frame.getContentPane(); | ||
| 35 | pane.setLayout(new BorderLayout()); | ||
| 36 | |||
| 37 | // editor panel | ||
| 38 | text.setTabSize(2); | ||
| 39 | pane.add(new JScrollPane(text), BorderLayout.CENTER); | ||
| 40 | text.addKeyListener(new KeyAdapter() { | ||
| 41 | @Override | ||
| 42 | public void keyPressed(KeyEvent event) { | ||
| 43 | switch (event.getKeyCode()) { | ||
| 44 | case KeyEvent.VK_ENTER: | ||
| 45 | if (event.isControlDown()) | ||
| 46 | callback.closeUi(frame, true); | ||
| 47 | break; | ||
| 48 | case KeyEvent.VK_ESCAPE: | ||
| 49 | callback.closeUi(frame, false); | ||
| 50 | break; | ||
| 51 | default: | ||
| 52 | break; | ||
| 53 | } | ||
| 54 | } | ||
| 55 | }); | ||
| 56 | |||
| 57 | // buttons panel | ||
| 58 | JPanel buttonsPanel = new JPanel(); | ||
| 59 | FlowLayout buttonsLayout = new FlowLayout(); | ||
| 60 | buttonsLayout.setAlignment(FlowLayout.RIGHT); | ||
| 61 | buttonsPanel.setLayout(buttonsLayout); | ||
| 62 | buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); | ||
| 63 | JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); | ||
| 64 | cancelButton.addActionListener(event -> { | ||
| 65 | // close (hide) the dialog | ||
| 66 | callback.closeUi(frame, false); | ||
| 67 | }); | ||
| 68 | buttonsPanel.add(cancelButton); | ||
| 69 | JButton saveButton = new JButton(I18n.translate("javadocs.save")); | ||
| 70 | saveButton.addActionListener(event -> { | ||
| 71 | // exit enigma | ||
| 72 | callback.closeUi(frame, true); | ||
| 73 | }); | ||
| 74 | buttonsPanel.add(saveButton); | ||
| 75 | pane.add(buttonsPanel, BorderLayout.SOUTH); | ||
| 76 | |||
| 77 | // tags panel | ||
| 78 | JMenuBar tagsMenu = new JMenuBar(); | ||
| 79 | |||
| 80 | // add javadoc tags | ||
| 81 | for (JavadocTag tag : JavadocTag.values()) { | ||
| 82 | JButton tagButton = new JButton(tag.getText()); | ||
| 83 | tagButton.addActionListener(action -> { | ||
| 84 | boolean textSelected = text.getSelectedText() != null; | ||
| 85 | String tagText = tag.isInline() ? "{" + tag.getText() + " }" : tag.getText() + " "; | ||
| 86 | |||
| 87 | if (textSelected) { | ||
| 88 | if (tag.isInline()) { | ||
| 89 | tagText = "{" + tag.getText() + " " + text.getSelectedText() + "}"; | ||
| 90 | } else { | ||
| 91 | tagText = tag.getText() + " " + text.getSelectedText(); | ||
| 92 | } | ||
| 93 | text.replaceSelection(tagText); | ||
| 94 | } else { | ||
| 95 | text.insert(tagText, text.getCaretPosition()); | ||
| 96 | } | ||
| 97 | |||
| 98 | if (tag.isInline()) { | ||
| 99 | text.setCaretPosition(text.getCaretPosition() - 1); | ||
| 100 | } | ||
| 101 | text.grabFocus(); | ||
| 102 | }); | ||
| 103 | tagsMenu.add(tagButton); | ||
| 104 | } | ||
| 105 | |||
| 106 | // add html tags | ||
| 107 | JComboBox<String> htmlList = new JComboBox<String>(); | ||
| 108 | htmlList.setPreferredSize(new Dimension()); | ||
| 109 | for (HTML.Tag htmlTag : HTML.getAllTags()) { | ||
| 110 | htmlList.addItem(htmlTag.toString()); | ||
| 111 | } | ||
| 112 | htmlList.addActionListener(action -> { | ||
| 113 | String tagText = "<" + htmlList.getSelectedItem().toString() + ">"; | ||
| 114 | text.insert(tagText, text.getCaretPosition()); | ||
| 115 | text.grabFocus(); | ||
| 116 | }); | ||
| 117 | tagsMenu.add(htmlList); | ||
| 118 | |||
| 119 | pane.add(tagsMenu, BorderLayout.NORTH); | ||
| 120 | |||
| 121 | // show the frame | ||
| 122 | frame.setSize(ScaleUtil.getDimension(600, 400)); | ||
| 123 | frame.setLocationRelativeTo(parent); | ||
| 124 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | ||
| 125 | } | ||
| 126 | |||
| 127 | public static void init(JFrame parent, JTextArea area, Callback callback) { | ||
| 128 | instance = new JavadocDialog(parent, area, callback); | ||
| 129 | instance.frame.doLayout(); | ||
| 130 | instance.frame.setVisible(true); | ||
| 131 | } | ||
| 132 | |||
| 133 | public interface Callback { | ||
| 134 | void closeUi(JFrame frame, boolean save); | ||
| 135 | } | ||
| 136 | |||
| 137 | private enum JavadocTag { | ||
| 138 | CODE(true), | ||
| 139 | LINK(true), | ||
| 140 | LINKPLAIN(true), | ||
| 141 | RETURN(false), | ||
| 142 | SEE(false), | ||
| 143 | THROWS(false); | ||
| 144 | |||
| 145 | private boolean inline; | ||
| 146 | |||
| 147 | private JavadocTag(boolean inline) { | ||
| 148 | this.inline = inline; | ||
| 149 | } | ||
| 150 | |||
| 151 | public String getText() { | ||
| 152 | return "@" + this.name().toLowerCase(); | ||
| 153 | } | ||
| 154 | |||
| 155 | public boolean isInline() { | ||
| 156 | return this.inline; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java deleted file mode 100644 index e33ae82..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ /dev/null | |||
| @@ -1,109 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.dialog; | ||
| 13 | |||
| 14 | import cuchaz.enigma.Constants; | ||
| 15 | import cuchaz.enigma.ProgressListener; | ||
| 16 | import cuchaz.enigma.utils.I18n; | ||
| 17 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 18 | import cuchaz.enigma.utils.Utils; | ||
| 19 | |||
| 20 | import javax.swing.*; | ||
| 21 | import java.awt.*; | ||
| 22 | import java.util.concurrent.CompletableFuture; | ||
| 23 | |||
| 24 | public class ProgressDialog implements ProgressListener, AutoCloseable { | ||
| 25 | |||
| 26 | private JFrame frame; | ||
| 27 | private JLabel labelTitle; | ||
| 28 | private JLabel labelText; | ||
| 29 | private JProgressBar progress; | ||
| 30 | |||
| 31 | public ProgressDialog(JFrame parent) { | ||
| 32 | |||
| 33 | // init frame | ||
| 34 | this.frame = new JFrame(String.format(I18n.translate("progress.operation"), Constants.NAME)); | ||
| 35 | final Container pane = this.frame.getContentPane(); | ||
| 36 | FlowLayout layout = new FlowLayout(); | ||
| 37 | layout.setAlignment(FlowLayout.LEFT); | ||
| 38 | pane.setLayout(layout); | ||
| 39 | |||
| 40 | this.labelTitle = new JLabel(); | ||
| 41 | pane.add(this.labelTitle); | ||
| 42 | |||
| 43 | // set up the progress bar | ||
| 44 | JPanel panel = new JPanel(); | ||
| 45 | pane.add(panel); | ||
| 46 | panel.setLayout(new BorderLayout()); | ||
| 47 | this.labelText = Utils.unboldLabel(new JLabel()); | ||
| 48 | this.progress = new JProgressBar(); | ||
| 49 | this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); | ||
| 50 | panel.add(this.labelText, BorderLayout.NORTH); | ||
| 51 | panel.add(this.progress, BorderLayout.CENTER); | ||
| 52 | panel.setPreferredSize(ScaleUtil.getDimension(360, 50)); | ||
| 53 | |||
| 54 | // show the frame | ||
| 55 | pane.doLayout(); | ||
| 56 | this.frame.setSize(ScaleUtil.getDimension(400, 120)); | ||
| 57 | this.frame.setResizable(false); | ||
| 58 | this.frame.setLocationRelativeTo(parent); | ||
| 59 | this.frame.setVisible(true); | ||
| 60 | this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 61 | } | ||
| 62 | |||
| 63 | public static CompletableFuture<Void> runOffThread(final JFrame parent, final ProgressRunnable runnable) { | ||
| 64 | CompletableFuture<Void> future = new CompletableFuture<>(); | ||
| 65 | new Thread(() -> | ||
| 66 | { | ||
| 67 | try (ProgressDialog progress = new ProgressDialog(parent)) { | ||
| 68 | runnable.run(progress); | ||
| 69 | future.complete(null); | ||
| 70 | } catch (Exception ex) { | ||
| 71 | future.completeExceptionally(ex); | ||
| 72 | throw new Error(ex); | ||
| 73 | } | ||
| 74 | }).start(); | ||
| 75 | return future; | ||
| 76 | } | ||
| 77 | |||
| 78 | @Override | ||
| 79 | public void close() { | ||
| 80 | this.frame.dispose(); | ||
| 81 | } | ||
| 82 | |||
| 83 | @Override | ||
| 84 | public void init(int totalWork, String title) { | ||
| 85 | this.labelTitle.setText(title); | ||
| 86 | this.progress.setMinimum(0); | ||
| 87 | this.progress.setMaximum(totalWork); | ||
| 88 | this.progress.setValue(0); | ||
| 89 | } | ||
| 90 | |||
| 91 | @Override | ||
| 92 | public void step(int numDone, String message) { | ||
| 93 | this.labelText.setText(message); | ||
| 94 | if (numDone != -1) { | ||
| 95 | this.progress.setValue(numDone); | ||
| 96 | this.progress.setIndeterminate(false); | ||
| 97 | } else { | ||
| 98 | this.progress.setIndeterminate(true); | ||
| 99 | } | ||
| 100 | |||
| 101 | // update the frame | ||
| 102 | this.frame.validate(); | ||
| 103 | this.frame.repaint(); | ||
| 104 | } | ||
| 105 | |||
| 106 | public interface ProgressRunnable { | ||
| 107 | void run(ProgressListener listener) throws Exception; | ||
| 108 | } | ||
| 109 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java deleted file mode 100644 index b283a37..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ /dev/null | |||
| @@ -1,261 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.dialog; | ||
| 13 | |||
| 14 | import java.awt.BorderLayout; | ||
| 15 | import java.awt.Color; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.Font; | ||
| 18 | import java.awt.event.*; | ||
| 19 | import java.util.Arrays; | ||
| 20 | import java.util.Collections; | ||
| 21 | import java.util.List; | ||
| 22 | |||
| 23 | import javax.swing.*; | ||
| 24 | import javax.swing.event.DocumentEvent; | ||
| 25 | import javax.swing.event.DocumentListener; | ||
| 26 | |||
| 27 | import cuchaz.enigma.gui.Gui; | ||
| 28 | import cuchaz.enigma.gui.GuiController; | ||
| 29 | import cuchaz.enigma.gui.util.AbstractListCellRenderer; | ||
| 30 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 31 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 32 | import cuchaz.enigma.utils.I18n; | ||
| 33 | import cuchaz.enigma.utils.search.SearchEntry; | ||
| 34 | import cuchaz.enigma.utils.search.SearchUtil; | ||
| 35 | |||
| 36 | public class SearchDialog { | ||
| 37 | |||
| 38 | private final JTextField searchField; | ||
| 39 | private DefaultListModel<SearchEntryImpl> classListModel; | ||
| 40 | private final JList<SearchEntryImpl> classList; | ||
| 41 | private final JDialog dialog; | ||
| 42 | |||
| 43 | private final Gui parent; | ||
| 44 | private final SearchUtil<SearchEntryImpl> su; | ||
| 45 | private SearchUtil.SearchControl currentSearch; | ||
| 46 | |||
| 47 | public SearchDialog(Gui parent) { | ||
| 48 | this.parent = parent; | ||
| 49 | |||
| 50 | su = new SearchUtil<>(); | ||
| 51 | |||
| 52 | dialog = new JDialog(parent.getFrame(), I18n.translate("menu.view.search"), true); | ||
| 53 | JPanel contentPane = new JPanel(); | ||
| 54 | contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4)); | ||
| 55 | contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4))); | ||
| 56 | |||
| 57 | searchField = new JTextField(); | ||
| 58 | searchField.getDocument().addDocumentListener(new DocumentListener() { | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public void insertUpdate(DocumentEvent e) { | ||
| 62 | updateList(); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Override | ||
| 66 | public void removeUpdate(DocumentEvent e) { | ||
| 67 | updateList(); | ||
| 68 | } | ||
| 69 | |||
| 70 | @Override | ||
| 71 | public void changedUpdate(DocumentEvent e) { | ||
| 72 | updateList(); | ||
| 73 | } | ||
| 74 | |||
| 75 | }); | ||
| 76 | searchField.addKeyListener(new KeyAdapter() { | ||
| 77 | @Override | ||
| 78 | public void keyPressed(KeyEvent e) { | ||
| 79 | if (e.getKeyCode() == KeyEvent.VK_DOWN) { | ||
| 80 | int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1; | ||
| 81 | classList.setSelectedIndex(next); | ||
| 82 | classList.ensureIndexIsVisible(next); | ||
| 83 | } else if (e.getKeyCode() == KeyEvent.VK_UP) { | ||
| 84 | int prev = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1; | ||
| 85 | classList.setSelectedIndex(prev); | ||
| 86 | classList.ensureIndexIsVisible(prev); | ||
| 87 | } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { | ||
| 88 | close(); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | }); | ||
| 92 | searchField.addActionListener(e -> openSelected()); | ||
| 93 | contentPane.add(searchField, BorderLayout.NORTH); | ||
| 94 | |||
| 95 | classListModel = new DefaultListModel<>(); | ||
| 96 | classList = new JList<>(); | ||
| 97 | classList.setModel(classListModel); | ||
| 98 | classList.setCellRenderer(new ListCellRendererImpl()); | ||
| 99 | classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
| 100 | classList.addMouseListener(new MouseAdapter() { | ||
| 101 | @Override | ||
| 102 | public void mouseClicked(MouseEvent mouseEvent) { | ||
| 103 | if (mouseEvent.getClickCount() >= 2) { | ||
| 104 | int idx = classList.locationToIndex(mouseEvent.getPoint()); | ||
| 105 | SearchEntryImpl entry = classList.getModel().getElementAt(idx); | ||
| 106 | openEntry(entry); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | }); | ||
| 110 | contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); | ||
| 111 | |||
| 112 | JPanel buttonBar = new JPanel(); | ||
| 113 | buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); | ||
| 114 | JButton open = new JButton(I18n.translate("prompt.open")); | ||
| 115 | open.addActionListener(event -> openSelected()); | ||
| 116 | buttonBar.add(open); | ||
| 117 | JButton cancel = new JButton(I18n.translate("prompt.cancel")); | ||
| 118 | cancel.addActionListener(event -> close()); | ||
| 119 | buttonBar.add(cancel); | ||
| 120 | contentPane.add(buttonBar, BorderLayout.SOUTH); | ||
| 121 | |||
| 122 | // apparently the class list doesn't update by itself when the list | ||
| 123 | // state changes and the dialog is hidden | ||
| 124 | dialog.addComponentListener(new ComponentAdapter() { | ||
| 125 | @Override | ||
| 126 | public void componentShown(ComponentEvent e) { | ||
| 127 | classList.updateUI(); | ||
| 128 | } | ||
| 129 | }); | ||
| 130 | |||
| 131 | dialog.setContentPane(contentPane); | ||
| 132 | dialog.setSize(ScaleUtil.getDimension(400, 500)); | ||
| 133 | dialog.setLocationRelativeTo(parent.getFrame()); | ||
| 134 | } | ||
| 135 | |||
| 136 | public void show() { | ||
| 137 | su.clear(); | ||
| 138 | parent.getController().project.getJarIndex().getEntryIndex().getClasses().parallelStream() | ||
| 139 | .filter(e -> !e.isInnerClass()) | ||
| 140 | .map(e -> SearchEntryImpl.from(e, parent.getController())) | ||
| 141 | .map(SearchUtil.Entry::from) | ||
| 142 | .sequential() | ||
| 143 | .forEach(su::add); | ||
| 144 | |||
| 145 | updateList(); | ||
| 146 | |||
| 147 | searchField.requestFocus(); | ||
| 148 | searchField.selectAll(); | ||
| 149 | |||
| 150 | dialog.setVisible(true); | ||
| 151 | } | ||
| 152 | |||
| 153 | private void openSelected() { | ||
| 154 | SearchEntryImpl selectedValue = classList.getSelectedValue(); | ||
| 155 | if (selectedValue != null) { | ||
| 156 | openEntry(selectedValue); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | private void openEntry(SearchEntryImpl e) { | ||
| 161 | close(); | ||
| 162 | su.hit(e); | ||
| 163 | parent.getController().navigateTo(e.obf); | ||
| 164 | if (e.deobf != null) { | ||
| 165 | parent.getDeobfPanel().deobfClasses.setSelectionClass(e.deobf); | ||
| 166 | } else { | ||
| 167 | parent.getObfPanel().obfClasses.setSelectionClass(e.obf); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | private void close() { | ||
| 172 | dialog.setVisible(false); | ||
| 173 | } | ||
| 174 | |||
| 175 | // Updates the list of class names | ||
| 176 | private void updateList() { | ||
| 177 | if (currentSearch != null) currentSearch.stop(); | ||
| 178 | |||
| 179 | DefaultListModel<SearchEntryImpl> classListModel = new DefaultListModel<>(); | ||
| 180 | this.classListModel = classListModel; | ||
| 181 | classList.setModel(classListModel); | ||
| 182 | |||
| 183 | currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> SwingUtilities.invokeLater(() -> classListModel.insertElementAt(e, idx))); | ||
| 184 | } | ||
| 185 | |||
| 186 | public void dispose() { | ||
| 187 | dialog.dispose(); | ||
| 188 | } | ||
| 189 | |||
| 190 | private static final class SearchEntryImpl implements SearchEntry { | ||
| 191 | |||
| 192 | public final ClassEntry obf; | ||
| 193 | public final ClassEntry deobf; | ||
| 194 | |||
| 195 | private SearchEntryImpl(ClassEntry obf, ClassEntry deobf) { | ||
| 196 | this.obf = obf; | ||
| 197 | this.deobf = deobf; | ||
| 198 | } | ||
| 199 | |||
| 200 | @Override | ||
| 201 | public List<String> getSearchableNames() { | ||
| 202 | if (deobf != null) { | ||
| 203 | return Arrays.asList(obf.getSimpleName(), deobf.getSimpleName()); | ||
| 204 | } else { | ||
| 205 | return Collections.singletonList(obf.getSimpleName()); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | @Override | ||
| 210 | public String getIdentifier() { | ||
| 211 | return obf.getFullName(); | ||
| 212 | } | ||
| 213 | |||
| 214 | @Override | ||
| 215 | public String toString() { | ||
| 216 | return String.format("SearchEntryImpl { obf: %s, deobf: %s }", obf, deobf); | ||
| 217 | } | ||
| 218 | |||
| 219 | public static SearchEntryImpl from(ClassEntry e, GuiController controller) { | ||
| 220 | ClassEntry deobf = controller.project.getMapper().deobfuscate(e); | ||
| 221 | if (deobf.equals(e)) deobf = null; | ||
| 222 | return new SearchEntryImpl(e, deobf); | ||
| 223 | } | ||
| 224 | |||
| 225 | } | ||
| 226 | |||
| 227 | private static final class ListCellRendererImpl extends AbstractListCellRenderer<SearchEntryImpl> { | ||
| 228 | |||
| 229 | private final JLabel mainName; | ||
| 230 | private final JLabel secondaryName; | ||
| 231 | |||
| 232 | public ListCellRendererImpl() { | ||
| 233 | this.setLayout(new BorderLayout()); | ||
| 234 | |||
| 235 | mainName = new JLabel(); | ||
| 236 | this.add(mainName, BorderLayout.WEST); | ||
| 237 | |||
| 238 | secondaryName = new JLabel(); | ||
| 239 | secondaryName.setFont(secondaryName.getFont().deriveFont(Font.ITALIC)); | ||
| 240 | secondaryName.setForeground(Color.GRAY); | ||
| 241 | this.add(secondaryName, BorderLayout.EAST); | ||
| 242 | } | ||
| 243 | |||
| 244 | @Override | ||
| 245 | public void updateUiForEntry(JList<? extends SearchEntryImpl> list, SearchEntryImpl value, int index, boolean isSelected, boolean cellHasFocus) { | ||
| 246 | if (value.deobf == null) { | ||
| 247 | mainName.setText(value.obf.getSimpleName()); | ||
| 248 | mainName.setToolTipText(value.obf.getFullName()); | ||
| 249 | secondaryName.setText(""); | ||
| 250 | secondaryName.setToolTipText(""); | ||
| 251 | } else { | ||
| 252 | mainName.setText(value.deobf.getSimpleName()); | ||
| 253 | mainName.setToolTipText(value.deobf.getFullName()); | ||
| 254 | secondaryName.setText(value.obf.getSimpleName()); | ||
| 255 | secondaryName.setToolTipText(value.obf.getFullName()); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | } | ||
| 260 | |||
| 261 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java deleted file mode 100644 index 868eba7..0000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java +++ /dev/null | |||
| @@ -1,82 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | ||
| 2 | |||
| 3 | import java.awt.BorderLayout; | ||
| 4 | import java.util.Arrays; | ||
| 5 | import java.util.Locale; | ||
| 6 | import java.util.Map; | ||
| 7 | import java.util.Set; | ||
| 8 | import java.util.stream.Collectors; | ||
| 9 | |||
| 10 | import javax.swing.JButton; | ||
| 11 | import javax.swing.JCheckBox; | ||
| 12 | import javax.swing.JFrame; | ||
| 13 | import javax.swing.JPanel; | ||
| 14 | |||
| 15 | import cuchaz.enigma.gui.Gui; | ||
| 16 | import cuchaz.enigma.gui.stats.StatsMember; | ||
| 17 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 18 | import cuchaz.enigma.utils.I18n; | ||
| 19 | |||
| 20 | public class StatsDialog { | ||
| 21 | |||
| 22 | public static void show(Gui gui) { | ||
| 23 | // init frame | ||
| 24 | JFrame frame = new JFrame(I18n.translate("menu.file.stats.title")); | ||
| 25 | JPanel checkboxesPanel = new JPanel(); | ||
| 26 | JPanel buttonPanel = new JPanel(); | ||
| 27 | frame.setLayout(new BorderLayout()); | ||
| 28 | frame.add(BorderLayout.NORTH, checkboxesPanel); | ||
| 29 | frame.add(BorderLayout.SOUTH, buttonPanel); | ||
| 30 | |||
| 31 | // show checkboxes | ||
| 32 | Map<StatsMember, JCheckBox> checkboxes = Arrays | ||
| 33 | .stream(StatsMember.values()) | ||
| 34 | .collect(Collectors.toMap(m -> m, m -> { | ||
| 35 | JCheckBox checkbox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT))); | ||
| 36 | checkboxesPanel.add(checkbox); | ||
| 37 | return checkbox; | ||
| 38 | })); | ||
| 39 | |||
| 40 | // show generate button | ||
| 41 | JButton button = new JButton(I18n.translate("menu.file.stats.generate")); | ||
| 42 | buttonPanel.add(button); | ||
| 43 | button.setEnabled(false); | ||
| 44 | button.addActionListener(action -> { | ||
| 45 | frame.dispose(); | ||
| 46 | generateStats(gui, checkboxes); | ||
| 47 | }); | ||
| 48 | |||
| 49 | // add action listener to each checkbox | ||
| 50 | checkboxes.entrySet().forEach(checkbox -> { | ||
| 51 | checkbox.getValue().addActionListener(action -> { | ||
| 52 | if (!button.isEnabled()) { | ||
| 53 | button.setEnabled(true); | ||
| 54 | } else if (checkboxes.entrySet().stream().allMatch(entry -> !entry.getValue().isSelected())) { | ||
| 55 | button.setEnabled(false); | ||
| 56 | } | ||
| 57 | }); | ||
| 58 | }); | ||
| 59 | |||
| 60 | // show the frame | ||
| 61 | frame.pack(); | ||
| 62 | frame.setVisible(true); | ||
| 63 | frame.setSize(ScaleUtil.getDimension(500, 120)); | ||
| 64 | frame.setResizable(false); | ||
| 65 | frame.setLocationRelativeTo(gui.getFrame()); | ||
| 66 | } | ||
| 67 | |||
| 68 | private static void generateStats(Gui gui, Map<StatsMember, JCheckBox> checkboxes) { | ||
| 69 | // get members from selected checkboxes | ||
| 70 | Set<StatsMember> includedMembers = checkboxes | ||
| 71 | .entrySet() | ||
| 72 | .stream() | ||
| 73 | .filter(entry -> entry.getValue().isSelected()) | ||
| 74 | .map(Map.Entry::getKey) | ||
| 75 | .collect(Collectors.toSet()); | ||
| 76 | |||
| 77 | // checks if a projet is open | ||
| 78 | if (gui.getController().project != null) { | ||
| 79 | gui.getController().openStats(includedMembers); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java deleted file mode 100644 index fb497b1..0000000 --- a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.event.MouseEvent; | ||
| 4 | |||
| 5 | import javax.swing.JTabbedPane; | ||
| 6 | |||
| 7 | public class CollapsibleTabbedPane extends JTabbedPane { | ||
| 8 | |||
| 9 | public CollapsibleTabbedPane() { | ||
| 10 | } | ||
| 11 | |||
| 12 | public CollapsibleTabbedPane(int tabPlacement) { | ||
| 13 | super(tabPlacement); | ||
| 14 | } | ||
| 15 | |||
| 16 | public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) { | ||
| 17 | super(tabPlacement, tabLayoutPolicy); | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | protected void processMouseEvent(MouseEvent e) { | ||
| 22 | int id = e.getID(); | ||
| 23 | if (id == MouseEvent.MOUSE_PRESSED) { | ||
| 24 | if (!isEnabled()) return; | ||
| 25 | int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY()); | ||
| 26 | if (tabIndex >= 0 && isEnabledAt(tabIndex)) { | ||
| 27 | if (tabIndex == getSelectedIndex()) { | ||
| 28 | if (isFocusOwner() && isRequestFocusEnabled()) { | ||
| 29 | requestFocus(); | ||
| 30 | } else { | ||
| 31 | setSelectedIndex(-1); | ||
| 32 | } | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
| 37 | super.processMouseEvent(e); | ||
| 38 | } | ||
| 39 | |||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java deleted file mode 100644 index dc2cf8f..0000000 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ /dev/null | |||
| @@ -1,386 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import cuchaz.enigma.config.Config; | ||
| 4 | import cuchaz.enigma.config.Themes; | ||
| 5 | import cuchaz.enigma.gui.Gui; | ||
| 6 | import cuchaz.enigma.gui.dialog.AboutDialog; | ||
| 7 | import cuchaz.enigma.gui.dialog.ChangeDialog; | ||
| 8 | import cuchaz.enigma.gui.dialog.ConnectToServerDialog; | ||
| 9 | import cuchaz.enigma.gui.dialog.CreateServerDialog; | ||
| 10 | import cuchaz.enigma.gui.dialog.StatsDialog; | ||
| 11 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 12 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 13 | import cuchaz.enigma.utils.I18n; | ||
| 14 | import cuchaz.enigma.utils.Pair; | ||
| 15 | |||
| 16 | import java.awt.Desktop; | ||
| 17 | import java.awt.event.InputEvent; | ||
| 18 | import java.awt.event.KeyEvent; | ||
| 19 | import java.io.File; | ||
| 20 | import java.io.IOException; | ||
| 21 | import java.net.URISyntaxException; | ||
| 22 | import java.net.URL; | ||
| 23 | import java.nio.file.Files; | ||
| 24 | import java.nio.file.Path; | ||
| 25 | import java.nio.file.Paths; | ||
| 26 | import java.util.*; | ||
| 27 | import java.util.List; | ||
| 28 | import java.util.stream.Collectors; | ||
| 29 | import java.util.stream.IntStream; | ||
| 30 | import javax.swing.*; | ||
| 31 | |||
| 32 | public class MenuBar extends JMenuBar { | ||
| 33 | |||
| 34 | public final JMenuItem closeJarMenu; | ||
| 35 | public final List<JMenuItem> openMappingsMenus; | ||
| 36 | public final JMenuItem saveMappingsMenu; | ||
| 37 | public final List<JMenuItem> saveMappingsMenus; | ||
| 38 | public final JMenuItem closeMappingsMenu; | ||
| 39 | public final JMenuItem dropMappingsMenu; | ||
| 40 | public final JMenuItem exportSourceMenu; | ||
| 41 | public final JMenuItem exportJarMenu; | ||
| 42 | public final JMenuItem connectToServerMenu; | ||
| 43 | public final JMenuItem startServerMenu; | ||
| 44 | private final Gui gui; | ||
| 45 | |||
| 46 | public MenuBar(Gui gui) { | ||
| 47 | this.gui = gui; | ||
| 48 | |||
| 49 | /* | ||
| 50 | * File menu | ||
| 51 | */ | ||
| 52 | { | ||
| 53 | JMenu menu = new JMenu(I18n.translate("menu.file")); | ||
| 54 | this.add(menu); | ||
| 55 | { | ||
| 56 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.open")); | ||
| 57 | menu.add(item); | ||
| 58 | item.addActionListener(event -> { | ||
| 59 | this.gui.jarFileChooser.setVisible(true); | ||
| 60 | String file = this.gui.jarFileChooser.getFile(); | ||
| 61 | // checks if the file name is not empty | ||
| 62 | if (file != null) { | ||
| 63 | Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file); | ||
| 64 | // checks if the file name corresponds to an existing file | ||
| 65 | if (Files.exists(path)) { | ||
| 66 | gui.getController().openJar(path); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | }); | ||
| 70 | } | ||
| 71 | { | ||
| 72 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.close")); | ||
| 73 | menu.add(item); | ||
| 74 | item.addActionListener(event -> this.gui.getController().closeJar()); | ||
| 75 | this.closeJarMenu = item; | ||
| 76 | } | ||
| 77 | menu.addSeparator(); | ||
| 78 | JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open")); | ||
| 79 | menu.add(openMenu); | ||
| 80 | { | ||
| 81 | openMappingsMenus = new ArrayList<>(); | ||
| 82 | for (MappingFormat format : MappingFormat.values()) { | ||
| 83 | if (format.getReader() != null) { | ||
| 84 | JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); | ||
| 85 | openMenu.add(item); | ||
| 86 | item.addActionListener(event -> { | ||
| 87 | if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { | ||
| 88 | File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile(); | ||
| 89 | this.gui.getController().openMappings(format, selectedFile.toPath()); | ||
| 90 | } | ||
| 91 | }); | ||
| 92 | openMappingsMenus.add(item); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | { | ||
| 97 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.save")); | ||
| 98 | menu.add(item); | ||
| 99 | item.addActionListener(event -> { | ||
| 100 | this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); | ||
| 101 | }); | ||
| 102 | item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); | ||
| 103 | this.saveMappingsMenu = item; | ||
| 104 | } | ||
| 105 | JMenu saveMenu = new JMenu(I18n.translate("menu.file.mappings.save_as")); | ||
| 106 | menu.add(saveMenu); | ||
| 107 | { | ||
| 108 | saveMappingsMenus = new ArrayList<>(); | ||
| 109 | for (MappingFormat format : MappingFormat.values()) { | ||
| 110 | if (format.getWriter() != null) { | ||
| 111 | JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); | ||
| 112 | saveMenu.add(item); | ||
| 113 | item.addActionListener(event -> { | ||
| 114 | // TODO: Use a specific file chooser for it | ||
| 115 | if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { | ||
| 116 | this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); | ||
| 117 | this.saveMappingsMenu.setEnabled(true); | ||
| 118 | } | ||
| 119 | }); | ||
| 120 | saveMappingsMenus.add(item); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | { | ||
| 125 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.close")); | ||
| 126 | menu.add(item); | ||
| 127 | item.addActionListener(event -> { | ||
| 128 | if (this.gui.getController().isDirty()) { | ||
| 129 | this.gui.showDiscardDiag((response -> { | ||
| 130 | if (response == JOptionPane.YES_OPTION) { | ||
| 131 | gui.saveMapping(); | ||
| 132 | this.gui.getController().closeMappings(); | ||
| 133 | } else if (response == JOptionPane.NO_OPTION) | ||
| 134 | this.gui.getController().closeMappings(); | ||
| 135 | return null; | ||
| 136 | }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); | ||
| 137 | } else | ||
| 138 | this.gui.getController().closeMappings(); | ||
| 139 | |||
| 140 | }); | ||
| 141 | this.closeMappingsMenu = item; | ||
| 142 | } | ||
| 143 | { | ||
| 144 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.drop")); | ||
| 145 | menu.add(item); | ||
| 146 | item.addActionListener(event -> this.gui.getController().dropMappings()); | ||
| 147 | this.dropMappingsMenu = item; | ||
| 148 | } | ||
| 149 | menu.addSeparator(); | ||
| 150 | { | ||
| 151 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.source")); | ||
| 152 | menu.add(item); | ||
| 153 | item.addActionListener(event -> { | ||
| 154 | if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { | ||
| 155 | this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath()); | ||
| 156 | } | ||
| 157 | }); | ||
| 158 | this.exportSourceMenu = item; | ||
| 159 | } | ||
| 160 | { | ||
| 161 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.jar")); | ||
| 162 | menu.add(item); | ||
| 163 | item.addActionListener(event -> { | ||
| 164 | this.gui.exportJarFileChooser.setVisible(true); | ||
| 165 | if (this.gui.exportJarFileChooser.getFile() != null) { | ||
| 166 | Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile()); | ||
| 167 | this.gui.getController().exportJar(path); | ||
| 168 | } | ||
| 169 | }); | ||
| 170 | this.exportJarMenu = item; | ||
| 171 | } | ||
| 172 | menu.addSeparator(); | ||
| 173 | { | ||
| 174 | JMenuItem stats = new JMenuItem(I18n.translate("menu.file.stats")); | ||
| 175 | menu.add(stats); | ||
| 176 | stats.addActionListener(event -> StatsDialog.show(this.gui)); | ||
| 177 | } | ||
| 178 | menu.addSeparator(); | ||
| 179 | { | ||
| 180 | JMenuItem item = new JMenuItem(I18n.translate("menu.file.exit")); | ||
| 181 | menu.add(item); | ||
| 182 | item.addActionListener(event -> this.gui.close()); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | /* | ||
| 187 | * Decompiler menu | ||
| 188 | */ | ||
| 189 | { | ||
| 190 | JMenu menu = new JMenu(I18n.translate("menu.decompiler")); | ||
| 191 | this.add(menu); | ||
| 192 | |||
| 193 | ButtonGroup decompilerGroup = new ButtonGroup(); | ||
| 194 | |||
| 195 | for (Config.Decompiler decompiler : Config.Decompiler.values()) { | ||
| 196 | JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name); | ||
| 197 | decompilerGroup.add(decompilerButton); | ||
| 198 | if (decompiler.equals(Config.getInstance().decompiler)) { | ||
| 199 | decompilerButton.setSelected(true); | ||
| 200 | } | ||
| 201 | menu.add(decompilerButton); | ||
| 202 | decompilerButton.addActionListener(event -> { | ||
| 203 | gui.getController().setDecompiler(decompiler.service); | ||
| 204 | |||
| 205 | try { | ||
| 206 | Config.getInstance().decompiler = decompiler; | ||
| 207 | Config.getInstance().saveConfig(); | ||
| 208 | } catch (IOException e) { | ||
| 209 | throw new RuntimeException(e); | ||
| 210 | } | ||
| 211 | }); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | /* | ||
| 216 | * View menu | ||
| 217 | */ | ||
| 218 | { | ||
| 219 | JMenu menu = new JMenu(I18n.translate("menu.view")); | ||
| 220 | this.add(menu); | ||
| 221 | { | ||
| 222 | JMenu themes = new JMenu(I18n.translate("menu.view.themes")); | ||
| 223 | menu.add(themes); | ||
| 224 | ButtonGroup themeGroup = new ButtonGroup(); | ||
| 225 | for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) { | ||
| 226 | JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT))); | ||
| 227 | themeGroup.add(themeButton); | ||
| 228 | if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { | ||
| 229 | themeButton.setSelected(true); | ||
| 230 | } | ||
| 231 | themes.add(themeButton); | ||
| 232 | themeButton.addActionListener(event -> Themes.setLookAndFeel(gui, lookAndFeel)); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | { | ||
| 236 | JMenu languages = new JMenu(I18n.translate("menu.view.languages")); | ||
| 237 | menu.add(languages); | ||
| 238 | ButtonGroup languageGroup = new ButtonGroup(); | ||
| 239 | for (String lang : I18n.getAvailableLanguages()) { | ||
| 240 | JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang)); | ||
| 241 | languageGroup.add(languageButton); | ||
| 242 | if (lang.equals(Config.getInstance().language)) { | ||
| 243 | languageButton.setSelected(true); | ||
| 244 | } | ||
| 245 | languages.add(languageButton); | ||
| 246 | languageButton.addActionListener(event -> { | ||
| 247 | I18n.setLanguage(lang); | ||
| 248 | ChangeDialog.show(this.gui); | ||
| 249 | }); | ||
| 250 | } | ||
| 251 | } | ||
| 252 | { | ||
| 253 | JMenu scale = new JMenu(I18n.translate("menu.view.scale")); | ||
| 254 | { | ||
| 255 | ButtonGroup scaleGroup = new ButtonGroup(); | ||
| 256 | Map<Float, JRadioButtonMenuItem> map = IntStream.of(100, 125, 150, 175, 200) | ||
| 257 | .mapToObj(scaleFactor -> { | ||
| 258 | float realScaleFactor = scaleFactor / 100f; | ||
| 259 | JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor)); | ||
| 260 | menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor)); | ||
| 261 | menuItem.addActionListener(event -> ChangeDialog.show(this.gui)); | ||
| 262 | scaleGroup.add(menuItem); | ||
| 263 | scale.add(menuItem); | ||
| 264 | return new Pair<>(realScaleFactor, menuItem); | ||
| 265 | }) | ||
| 266 | .collect(Collectors.toMap(x -> x.a, x -> x.b)); | ||
| 267 | |||
| 268 | JMenuItem customScale = new JMenuItem(I18n.translate("menu.view.scale.custom")); | ||
| 269 | customScale.addActionListener(event -> { | ||
| 270 | String answer = (String) JOptionPane.showInputDialog(gui.getFrame(), I18n.translate("menu.view.scale.custom.title"), I18n.translate("menu.view.scale.custom.title"), | ||
| 271 | JOptionPane.QUESTION_MESSAGE, null, null, Float.toString(ScaleUtil.getScaleFactor() * 100)); | ||
| 272 | if (answer == null) return; | ||
| 273 | float newScale = 1.0f; | ||
| 274 | try { | ||
| 275 | newScale = Float.parseFloat(answer) / 100f; | ||
| 276 | } catch (NumberFormatException ignored) { | ||
| 277 | } | ||
| 278 | ScaleUtil.setScaleFactor(newScale); | ||
| 279 | ChangeDialog.show(this.gui); | ||
| 280 | }); | ||
| 281 | scale.add(customScale); | ||
| 282 | ScaleUtil.addListener((newScale, _oldScale) -> { | ||
| 283 | JRadioButtonMenuItem mi = map.get(newScale); | ||
| 284 | if (mi != null) { | ||
| 285 | mi.setSelected(true); | ||
| 286 | } else { | ||
| 287 | scaleGroup.clearSelection(); | ||
| 288 | } | ||
| 289 | }); | ||
| 290 | JRadioButtonMenuItem mi = map.get(ScaleUtil.getScaleFactor()); | ||
| 291 | if (mi != null) { | ||
| 292 | mi.setSelected(true); | ||
| 293 | } | ||
| 294 | } | ||
| 295 | menu.add(scale); | ||
| 296 | } | ||
| 297 | menu.addSeparator(); | ||
| 298 | { | ||
| 299 | JMenuItem search = new JMenuItem(I18n.translate("menu.view.search")); | ||
| 300 | search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK)); | ||
| 301 | menu.add(search); | ||
| 302 | search.addActionListener(event -> { | ||
| 303 | if (this.gui.getController().project != null) { | ||
| 304 | this.gui.getSearchDialog().show(); | ||
| 305 | } | ||
| 306 | }); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | /* | ||
| 311 | * Collab menu | ||
| 312 | */ | ||
| 313 | { | ||
| 314 | JMenu menu = new JMenu(I18n.translate("menu.collab")); | ||
| 315 | this.add(menu); | ||
| 316 | { | ||
| 317 | JMenuItem item = new JMenuItem(I18n.translate("menu.collab.connect")); | ||
| 318 | menu.add(item); | ||
| 319 | item.addActionListener(event -> { | ||
| 320 | if (this.gui.getController().getClient() != null) { | ||
| 321 | this.gui.getController().disconnectIfConnected(null); | ||
| 322 | return; | ||
| 323 | } | ||
| 324 | ConnectToServerDialog.Result result = ConnectToServerDialog.show(this.gui.getFrame()); | ||
| 325 | if (result == null) { | ||
| 326 | return; | ||
| 327 | } | ||
| 328 | this.gui.getController().disconnectIfConnected(null); | ||
| 329 | try { | ||
| 330 | this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword()); | ||
| 331 | } catch (IOException e) { | ||
| 332 | JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); | ||
| 333 | this.gui.getController().disconnectIfConnected(null); | ||
| 334 | } | ||
| 335 | Arrays.fill(result.getPassword(), (char)0); | ||
| 336 | }); | ||
| 337 | this.connectToServerMenu = item; | ||
| 338 | } | ||
| 339 | { | ||
| 340 | JMenuItem item = new JMenuItem(I18n.translate("menu.collab.server.start")); | ||
| 341 | menu.add(item); | ||
| 342 | item.addActionListener(event -> { | ||
| 343 | if (this.gui.getController().getServer() != null) { | ||
| 344 | this.gui.getController().disconnectIfConnected(null); | ||
| 345 | return; | ||
| 346 | } | ||
| 347 | CreateServerDialog.Result result = CreateServerDialog.show(this.gui.getFrame()); | ||
| 348 | if (result == null) { | ||
| 349 | return; | ||
| 350 | } | ||
| 351 | this.gui.getController().disconnectIfConnected(null); | ||
| 352 | try { | ||
| 353 | this.gui.getController().createServer(result.getPort(), result.getPassword()); | ||
| 354 | } catch (IOException e) { | ||
| 355 | JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE); | ||
| 356 | this.gui.getController().disconnectIfConnected(null); | ||
| 357 | } | ||
| 358 | }); | ||
| 359 | this.startServerMenu = item; | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | /* | ||
| 364 | * Help menu | ||
| 365 | */ | ||
| 366 | { | ||
| 367 | JMenu menu = new JMenu(I18n.translate("menu.help")); | ||
| 368 | this.add(menu); | ||
| 369 | { | ||
| 370 | JMenuItem item = new JMenuItem(I18n.translate("menu.help.about")); | ||
| 371 | menu.add(item); | ||
| 372 | item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); | ||
| 373 | } | ||
| 374 | { | ||
| 375 | JMenuItem item = new JMenuItem(I18n.translate("menu.help.github")); | ||
| 376 | menu.add(item); | ||
| 377 | item.addActionListener(event -> { | ||
| 378 | try { | ||
| 379 | Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI()); | ||
| 380 | } catch (URISyntaxException | IOException ignored) { | ||
| 381 | } | ||
| 382 | }); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
| 386 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java deleted file mode 100644 index b92041c..0000000 --- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java +++ /dev/null | |||
| @@ -1,125 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.Gui; | ||
| 4 | import cuchaz.enigma.utils.I18n; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import java.awt.event.InputEvent; | ||
| 8 | import java.awt.event.KeyEvent; | ||
| 9 | |||
| 10 | public class PopupMenuBar extends JPopupMenu { | ||
| 11 | |||
| 12 | public final JMenuItem renameMenu; | ||
| 13 | public final JMenuItem editJavadocMenu; | ||
| 14 | public final JMenuItem showInheritanceMenu; | ||
| 15 | public final JMenuItem showImplementationsMenu; | ||
| 16 | public final JMenuItem showCallsMenu; | ||
| 17 | public final JMenuItem showCallsSpecificMenu; | ||
| 18 | public final JMenuItem openEntryMenu; | ||
| 19 | public final JMenuItem openPreviousMenu; | ||
| 20 | public final JMenuItem openNextMenu; | ||
| 21 | public final JMenuItem toggleMappingMenu; | ||
| 22 | |||
| 23 | public PopupMenuBar(Gui gui) { | ||
| 24 | { | ||
| 25 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename")); | ||
| 26 | menu.addActionListener(event -> gui.startRename()); | ||
| 27 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); | ||
| 28 | menu.setEnabled(false); | ||
| 29 | this.add(menu); | ||
| 30 | this.renameMenu = menu; | ||
| 31 | } | ||
| 32 | { | ||
| 33 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc")); | ||
| 34 | menu.addActionListener(event -> gui.startDocChange()); | ||
| 35 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK)); | ||
| 36 | menu.setEnabled(false); | ||
| 37 | this.add(menu); | ||
| 38 | this.editJavadocMenu = menu; | ||
| 39 | } | ||
| 40 | { | ||
| 41 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance")); | ||
| 42 | menu.addActionListener(event -> gui.showInheritance()); | ||
| 43 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK)); | ||
| 44 | menu.setEnabled(false); | ||
| 45 | this.add(menu); | ||
| 46 | this.showInheritanceMenu = menu; | ||
| 47 | } | ||
| 48 | { | ||
| 49 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations")); | ||
| 50 | menu.addActionListener(event -> gui.showImplementations()); | ||
| 51 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK)); | ||
| 52 | menu.setEnabled(false); | ||
| 53 | this.add(menu); | ||
| 54 | this.showImplementationsMenu = menu; | ||
| 55 | } | ||
| 56 | { | ||
| 57 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls")); | ||
| 58 | menu.addActionListener(event -> gui.showCalls(true)); | ||
| 59 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); | ||
| 60 | menu.setEnabled(false); | ||
| 61 | this.add(menu); | ||
| 62 | this.showCallsMenu = menu; | ||
| 63 | } | ||
| 64 | { | ||
| 65 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific")); | ||
| 66 | menu.addActionListener(event -> gui.showCalls(false)); | ||
| 67 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK)); | ||
| 68 | menu.setEnabled(false); | ||
| 69 | this.add(menu); | ||
| 70 | this.showCallsSpecificMenu = menu; | ||
| 71 | } | ||
| 72 | { | ||
| 73 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration")); | ||
| 74 | menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry)); | ||
| 75 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); | ||
| 76 | menu.setEnabled(false); | ||
| 77 | this.add(menu); | ||
| 78 | this.openEntryMenu = menu; | ||
| 79 | } | ||
| 80 | { | ||
| 81 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.back")); | ||
| 82 | menu.addActionListener(event -> gui.getController().openPreviousReference()); | ||
| 83 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK)); | ||
| 84 | menu.setEnabled(false); | ||
| 85 | this.add(menu); | ||
| 86 | this.openPreviousMenu = menu; | ||
| 87 | } | ||
| 88 | { | ||
| 89 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.forward")); | ||
| 90 | menu.addActionListener(event -> gui.getController().openNextReference()); | ||
| 91 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK)); | ||
| 92 | menu.setEnabled(false); | ||
| 93 | this.add(menu); | ||
| 94 | this.openNextMenu = menu; | ||
| 95 | } | ||
| 96 | { | ||
| 97 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated")); | ||
| 98 | menu.addActionListener(event -> gui.toggleMapping()); | ||
| 99 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); | ||
| 100 | menu.setEnabled(false); | ||
| 101 | this.add(menu); | ||
| 102 | this.toggleMappingMenu = menu; | ||
| 103 | } | ||
| 104 | { | ||
| 105 | this.add(new JSeparator()); | ||
| 106 | } | ||
| 107 | { | ||
| 108 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in")); | ||
| 109 | menu.addActionListener(event -> gui.editor.offsetEditorZoom(2)); | ||
| 110 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK)); | ||
| 111 | this.add(menu); | ||
| 112 | } | ||
| 113 | { | ||
| 114 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out")); | ||
| 115 | menu.addActionListener(event -> gui.editor.offsetEditorZoom(-2)); | ||
| 116 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); | ||
| 117 | this.add(menu); | ||
| 118 | } | ||
| 119 | { | ||
| 120 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset")); | ||
| 121 | menu.addActionListener(event -> gui.editor.resetEditorZoom()); | ||
| 122 | this.add(menu); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java deleted file mode 100644 index f5f6628..0000000 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.filechooser; | ||
| 2 | |||
| 3 | import javax.swing.*; | ||
| 4 | |||
| 5 | public class FileChooserAny extends JFileChooser { | ||
| 6 | public FileChooserAny() { | ||
| 7 | this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); | ||
| 8 | this.setAcceptAllFileFilterUsed(false); | ||
| 9 | } | ||
| 10 | } \ No newline at end of file | ||
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java deleted file mode 100644 index cea11a6..0000000 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java +++ /dev/null | |||
| @@ -1,8 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.filechooser; | ||
| 2 | |||
| 3 | import javax.swing.*; | ||
| 4 | |||
| 5 | public class FileChooserFile extends JFileChooser { | ||
| 6 | public FileChooserFile() { | ||
| 7 | } | ||
| 8 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java deleted file mode 100644 index c16e0af..0000000 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.filechooser; | ||
| 2 | |||
| 3 | import javax.swing.*; | ||
| 4 | |||
| 5 | public class FileChooserFolder extends JFileChooser { | ||
| 6 | |||
| 7 | public FileChooserFolder() { | ||
| 8 | this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); | ||
| 9 | this.setAcceptAllFileFilterUsed(false); | ||
| 10 | } | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java deleted file mode 100644 index cef6494..0000000 --- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java +++ /dev/null | |||
| @@ -1,69 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.highlight; | ||
| 13 | |||
| 14 | import cuchaz.enigma.config.Config; | ||
| 15 | |||
| 16 | import javax.swing.text.BadLocationException; | ||
| 17 | import javax.swing.text.Highlighter; | ||
| 18 | import javax.swing.text.JTextComponent; | ||
| 19 | import java.awt.*; | ||
| 20 | |||
| 21 | public class BoxHighlightPainter implements Highlighter.HighlightPainter { | ||
| 22 | private Color fillColor; | ||
| 23 | private Color borderColor; | ||
| 24 | |||
| 25 | protected BoxHighlightPainter(Color fillColor, Color borderColor) { | ||
| 26 | this.fillColor = fillColor; | ||
| 27 | this.borderColor = borderColor; | ||
| 28 | } | ||
| 29 | |||
| 30 | public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) { | ||
| 31 | return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null); | ||
| 32 | } | ||
| 33 | |||
| 34 | public static Rectangle getBounds(JTextComponent text, int start, int end) { | ||
| 35 | try { | ||
| 36 | // determine the bounds of the text | ||
| 37 | Rectangle startRect = text.modelToView(start); | ||
| 38 | Rectangle endRect = text.modelToView(end); | ||
| 39 | Rectangle bounds = startRect.union(endRect); | ||
| 40 | |||
| 41 | // adjust the box so it looks nice | ||
| 42 | bounds.x -= 2; | ||
| 43 | bounds.width += 2; | ||
| 44 | bounds.y += 1; | ||
| 45 | bounds.height -= 2; | ||
| 46 | |||
| 47 | return bounds; | ||
| 48 | } catch (BadLocationException ex) { | ||
| 49 | // don't care... just return something | ||
| 50 | return new Rectangle(0, 0, 0, 0); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { | ||
| 56 | Rectangle bounds = getBounds(text, start, end); | ||
| 57 | |||
| 58 | // fill the area | ||
| 59 | if (this.fillColor != null) { | ||
| 60 | g.setColor(this.fillColor); | ||
| 61 | g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | ||
| 62 | } | ||
| 63 | |||
| 64 | // draw a box around the area | ||
| 65 | g.setColor(this.borderColor); | ||
| 66 | g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | ||
| 67 | } | ||
| 68 | |||
| 69 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java deleted file mode 100644 index 81a70a9..0000000 --- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java +++ /dev/null | |||
| @@ -1,31 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.highlight; | ||
| 13 | |||
| 14 | import cuchaz.enigma.config.Config; | ||
| 15 | |||
| 16 | import javax.swing.text.Highlighter; | ||
| 17 | import javax.swing.text.JTextComponent; | ||
| 18 | import java.awt.*; | ||
| 19 | |||
| 20 | public class SelectionHighlightPainter implements Highlighter.HighlightPainter { | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { | ||
| 24 | // draw a thick border | ||
| 25 | Graphics2D g2d = (Graphics2D) g; | ||
| 26 | Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); | ||
| 27 | g2d.setColor(new Color(Config.getInstance().selectionHighlightColor)); | ||
| 28 | g2d.setStroke(new BasicStroke(2.0f)); | ||
| 29 | g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | ||
| 30 | } | ||
| 31 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java deleted file mode 100644 index ae23f32..0000000 --- a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.highlight; | ||
| 2 | |||
| 3 | public enum TokenHighlightType { | ||
| 4 | OBFUSCATED, | ||
| 5 | DEOBFUSCATED, | ||
| 6 | PROPOSED | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java deleted file mode 100644 index 922f8f2..0000000 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ /dev/null | |||
| @@ -1,72 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.node; | ||
| 13 | |||
| 14 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 15 | |||
| 16 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 17 | |||
| 18 | public class ClassSelectorClassNode extends DefaultMutableTreeNode { | ||
| 19 | |||
| 20 | private final ClassEntry obfEntry; | ||
| 21 | private ClassEntry classEntry; | ||
| 22 | |||
| 23 | public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) { | ||
| 24 | this.obfEntry = obfEntry; | ||
| 25 | this.classEntry = classEntry; | ||
| 26 | this.setUserObject(classEntry); | ||
| 27 | } | ||
| 28 | |||
| 29 | public ClassEntry getObfEntry() { | ||
| 30 | return obfEntry; | ||
| 31 | } | ||
| 32 | |||
| 33 | public ClassEntry getClassEntry() { | ||
| 34 | return this.classEntry; | ||
| 35 | } | ||
| 36 | |||
| 37 | @Override | ||
| 38 | public String toString() { | ||
| 39 | return this.classEntry.getSimpleName(); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public boolean equals(Object other) { | ||
| 44 | return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public int hashCode() { | ||
| 49 | return 17 + (classEntry != null ? classEntry.hashCode() : 0); | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public Object getUserObject() { | ||
| 54 | return classEntry; | ||
| 55 | } | ||
| 56 | |||
| 57 | @Override | ||
| 58 | public void setUserObject(Object userObject) { | ||
| 59 | String packageName = ""; | ||
| 60 | if (classEntry.getPackageName() != null) | ||
| 61 | packageName = classEntry.getPackageName() + "/"; | ||
| 62 | if (userObject instanceof String) | ||
| 63 | this.classEntry = new ClassEntry(packageName + userObject); | ||
| 64 | else if (userObject instanceof ClassEntry) | ||
| 65 | this.classEntry = (ClassEntry) userObject; | ||
| 66 | super.setUserObject(classEntry); | ||
| 67 | } | ||
| 68 | |||
| 69 | public boolean equals(ClassSelectorClassNode other) { | ||
| 70 | return this.classEntry.equals(other.classEntry); | ||
| 71 | } | ||
| 72 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java deleted file mode 100644 index caa985c..0000000 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java +++ /dev/null | |||
| @@ -1,58 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui.node; | ||
| 13 | |||
| 14 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 15 | |||
| 16 | public class ClassSelectorPackageNode extends DefaultMutableTreeNode { | ||
| 17 | |||
| 18 | private String packageName; | ||
| 19 | |||
| 20 | public ClassSelectorPackageNode(String packageName) { | ||
| 21 | this.packageName = packageName != null ? packageName : "(none)"; | ||
| 22 | } | ||
| 23 | |||
| 24 | public String getPackageName() { | ||
| 25 | return packageName; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public Object getUserObject() { | ||
| 30 | return packageName; | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public void setUserObject(Object userObject) { | ||
| 35 | if (userObject instanceof String) | ||
| 36 | this.packageName = (String) userObject; | ||
| 37 | super.setUserObject(userObject); | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public String toString() { | ||
| 42 | return !packageName.equals("(none)") ? this.packageName : "(none)"; | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public boolean equals(Object other) { | ||
| 47 | return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); | ||
| 48 | } | ||
| 49 | |||
| 50 | @Override | ||
| 51 | public int hashCode() { | ||
| 52 | return packageName.hashCode(); | ||
| 53 | } | ||
| 54 | |||
| 55 | public boolean equals(ClassSelectorPackageNode other) { | ||
| 56 | return other != null && this.packageName.equals(other.packageName); | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java deleted file mode 100644 index c24226b..0000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.ClassSelector; | ||
| 4 | import cuchaz.enigma.gui.Gui; | ||
| 5 | import cuchaz.enigma.utils.I18n; | ||
| 6 | |||
| 7 | import javax.swing.*; | ||
| 8 | import java.awt.*; | ||
| 9 | |||
| 10 | public class PanelDeobf extends JPanel { | ||
| 11 | |||
| 12 | public final ClassSelector deobfClasses; | ||
| 13 | private final Gui gui; | ||
| 14 | |||
| 15 | public PanelDeobf(Gui gui) { | ||
| 16 | this.gui = gui; | ||
| 17 | |||
| 18 | this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); | ||
| 19 | this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); | ||
| 20 | this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); | ||
| 21 | |||
| 22 | this.setLayout(new BorderLayout()); | ||
| 23 | this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH); | ||
| 24 | this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java deleted file mode 100644 index 8637afd..0000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ /dev/null | |||
| @@ -1,171 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | ||
| 2 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | ||
| 4 | import cuchaz.enigma.analysis.EntryReference; | ||
| 5 | import cuchaz.enigma.config.Config; | ||
| 6 | import cuchaz.enigma.gui.BrowserCaret; | ||
| 7 | import cuchaz.enigma.gui.Gui; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 11 | |||
| 12 | import javax.swing.*; | ||
| 13 | import java.awt.*; | ||
| 14 | import java.awt.event.KeyAdapter; | ||
| 15 | import java.awt.event.KeyEvent; | ||
| 16 | import java.awt.event.MouseAdapter; | ||
| 17 | import java.awt.event.MouseEvent; | ||
| 18 | |||
| 19 | public class PanelEditor extends JEditorPane { | ||
| 20 | private boolean mouseIsPressed = false; | ||
| 21 | public int fontSize = 12; | ||
| 22 | |||
| 23 | public PanelEditor(Gui gui) { | ||
| 24 | this.setEditable(false); | ||
| 25 | this.setSelectionColor(new Color(31, 46, 90)); | ||
| 26 | this.setCaret(new BrowserCaret()); | ||
| 27 | this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); | ||
| 28 | this.addCaretListener(event -> gui.onCaretMove(event.getDot(), mouseIsPressed)); | ||
| 29 | final PanelEditor self = this; | ||
| 30 | this.addMouseListener(new MouseAdapter() { | ||
| 31 | @Override | ||
| 32 | public void mousePressed(MouseEvent mouseEvent) { | ||
| 33 | mouseIsPressed = true; | ||
| 34 | } | ||
| 35 | |||
| 36 | @Override | ||
| 37 | public void mouseReleased(MouseEvent e) { | ||
| 38 | switch (e.getButton()) { | ||
| 39 | case MouseEvent.BUTTON3: // Right click | ||
| 40 | self.setCaretPosition(self.viewToModel(e.getPoint())); | ||
| 41 | break; | ||
| 42 | |||
| 43 | case 4: // Back navigation | ||
| 44 | gui.getController().openPreviousReference(); | ||
| 45 | break; | ||
| 46 | |||
| 47 | case 5: // Forward navigation | ||
| 48 | gui.getController().openNextReference(); | ||
| 49 | break; | ||
| 50 | } | ||
| 51 | mouseIsPressed = false; | ||
| 52 | } | ||
| 53 | }); | ||
| 54 | this.addKeyListener(new KeyAdapter() { | ||
| 55 | @Override | ||
| 56 | public void keyPressed(KeyEvent event) { | ||
| 57 | if (event.isControlDown()) { | ||
| 58 | gui.setShouldNavigateOnClick(false); | ||
| 59 | switch (event.getKeyCode()) { | ||
| 60 | case KeyEvent.VK_I: | ||
| 61 | gui.popupMenu.showInheritanceMenu.doClick(); | ||
| 62 | break; | ||
| 63 | |||
| 64 | case KeyEvent.VK_M: | ||
| 65 | gui.popupMenu.showImplementationsMenu.doClick(); | ||
| 66 | break; | ||
| 67 | |||
| 68 | case KeyEvent.VK_N: | ||
| 69 | gui.popupMenu.openEntryMenu.doClick(); | ||
| 70 | break; | ||
| 71 | |||
| 72 | case KeyEvent.VK_P: | ||
| 73 | gui.popupMenu.openPreviousMenu.doClick(); | ||
| 74 | break; | ||
| 75 | |||
| 76 | case KeyEvent.VK_E: | ||
| 77 | gui.popupMenu.openNextMenu.doClick(); | ||
| 78 | break; | ||
| 79 | |||
| 80 | case KeyEvent.VK_C: | ||
| 81 | if (event.isShiftDown()) { | ||
| 82 | gui.popupMenu.showCallsSpecificMenu.doClick(); | ||
| 83 | } else { | ||
| 84 | gui.popupMenu.showCallsMenu.doClick(); | ||
| 85 | } | ||
| 86 | break; | ||
| 87 | |||
| 88 | case KeyEvent.VK_O: | ||
| 89 | gui.popupMenu.toggleMappingMenu.doClick(); | ||
| 90 | break; | ||
| 91 | |||
| 92 | case KeyEvent.VK_R: | ||
| 93 | gui.popupMenu.renameMenu.doClick(); | ||
| 94 | break; | ||
| 95 | |||
| 96 | case KeyEvent.VK_D: | ||
| 97 | gui.popupMenu.editJavadocMenu.doClick(); | ||
| 98 | break; | ||
| 99 | |||
| 100 | case KeyEvent.VK_F5: | ||
| 101 | gui.getController().refreshCurrentClass(); | ||
| 102 | break; | ||
| 103 | |||
| 104 | case KeyEvent.VK_F: | ||
| 105 | // prevent navigating on click when quick find activated | ||
| 106 | break; | ||
| 107 | |||
| 108 | case KeyEvent.VK_ADD: | ||
| 109 | case KeyEvent.VK_EQUALS: | ||
| 110 | case KeyEvent.VK_PLUS: | ||
| 111 | self.offsetEditorZoom(2); | ||
| 112 | break; | ||
| 113 | case KeyEvent.VK_SUBTRACT: | ||
| 114 | case KeyEvent.VK_MINUS: | ||
| 115 | self.offsetEditorZoom(-2); | ||
| 116 | break; | ||
| 117 | |||
| 118 | default: | ||
| 119 | gui.setShouldNavigateOnClick(true); // CTRL | ||
| 120 | break; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | @Override | ||
| 126 | public void keyTyped(KeyEvent event) { | ||
| 127 | if (!gui.popupMenu.renameMenu.isEnabled()) return; | ||
| 128 | |||
| 129 | if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) { | ||
| 130 | EnigmaProject project = gui.getController().project; | ||
| 131 | EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(gui.cursorReference); | ||
| 132 | Entry<?> entry = reference.getNameableEntry(); | ||
| 133 | |||
| 134 | String name = String.valueOf(event.getKeyChar()); | ||
| 135 | if (entry instanceof ClassEntry && ((ClassEntry) entry).getParent() == null) { | ||
| 136 | String packageName = ((ClassEntry) entry).getPackageName(); | ||
| 137 | if (packageName != null) { | ||
| 138 | name = packageName + "/" + name; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | gui.popupMenu.renameMenu.doClick(); | ||
| 143 | gui.renameTextField.setText(name); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | @Override | ||
| 148 | public void keyReleased(KeyEvent event) { | ||
| 149 | gui.setShouldNavigateOnClick(event.isControlDown()); | ||
| 150 | } | ||
| 151 | }); | ||
| 152 | } | ||
| 153 | |||
| 154 | public void offsetEditorZoom(int zoomAmount) { | ||
| 155 | int newResult = this.fontSize + zoomAmount; | ||
| 156 | if (newResult > 8 && newResult < 72) { | ||
| 157 | this.fontSize = newResult; | ||
| 158 | this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | public void resetEditorZoom() { | ||
| 163 | this.fontSize = 12; | ||
| 164 | this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); | ||
| 165 | } | ||
| 166 | |||
| 167 | @Override | ||
| 168 | public Color getCaretColor() { | ||
| 169 | return new Color(Config.getInstance().caretColor); | ||
| 170 | } | ||
| 171 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java deleted file mode 100644 index de069bc..0000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java +++ /dev/null | |||
| @@ -1,32 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.Gui; | ||
| 4 | import cuchaz.enigma.utils.I18n; | ||
| 5 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 6 | import cuchaz.enigma.utils.Utils; | ||
| 7 | |||
| 8 | import javax.swing.*; | ||
| 9 | import java.awt.*; | ||
| 10 | |||
| 11 | public class PanelIdentifier extends JPanel { | ||
| 12 | |||
| 13 | private final Gui gui; | ||
| 14 | |||
| 15 | public PanelIdentifier(Gui gui) { | ||
| 16 | this.gui = gui; | ||
| 17 | |||
| 18 | this.setLayout(new GridLayout(4, 1, 0, 0)); | ||
| 19 | this.setPreferredSize(ScaleUtil.getDimension(0, 100)); | ||
| 20 | this.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); | ||
| 21 | } | ||
| 22 | |||
| 23 | public void clearReference() { | ||
| 24 | this.removeAll(); | ||
| 25 | JLabel label = new JLabel(I18n.translate("info_panel.identifier.none")); | ||
| 26 | Utils.unboldLabel(label); | ||
| 27 | label.setHorizontalAlignment(JLabel.CENTER); | ||
| 28 | this.add(label); | ||
| 29 | |||
| 30 | gui.redraw(); | ||
| 31 | } | ||
| 32 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java deleted file mode 100644 index dd7f9f9..0000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ /dev/null | |||
| @@ -1,37 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.ClassSelector; | ||
| 4 | import cuchaz.enigma.gui.Gui; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 6 | import cuchaz.enigma.utils.I18n; | ||
| 7 | |||
| 8 | import javax.swing.*; | ||
| 9 | import java.awt.*; | ||
| 10 | import java.util.Comparator; | ||
| 11 | |||
| 12 | public class PanelObf extends JPanel { | ||
| 13 | |||
| 14 | public final ClassSelector obfClasses; | ||
| 15 | private final Gui gui; | ||
| 16 | |||
| 17 | public PanelObf(Gui gui) { | ||
| 18 | this.gui = gui; | ||
| 19 | |||
| 20 | Comparator<ClassEntry> obfClassComparator = (a, b) -> { | ||
| 21 | String aname = a.getFullName(); | ||
| 22 | String bname = b.getFullName(); | ||
| 23 | if (aname.length() != bname.length()) { | ||
| 24 | return aname.length() - bname.length(); | ||
| 25 | } | ||
| 26 | return aname.compareTo(bname); | ||
| 27 | }; | ||
| 28 | |||
| 29 | this.obfClasses = new ClassSelector(gui, obfClassComparator, false); | ||
| 30 | this.obfClasses.setSelectionListener(gui.getController()::navigateTo); | ||
| 31 | this.obfClasses.setRenameSelectionListener(gui::onPanelRename); | ||
| 32 | |||
| 33 | this.setLayout(new BorderLayout()); | ||
| 34 | this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH); | ||
| 35 | this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java deleted file mode 100644 index e783530..0000000 --- a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java +++ /dev/null | |||
| @@ -1,197 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.stats; | ||
| 2 | |||
| 3 | import com.google.gson.GsonBuilder; | ||
| 4 | import cuchaz.enigma.EnigmaProject; | ||
| 5 | import cuchaz.enigma.ProgressListener; | ||
| 6 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 7 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 8 | import cuchaz.enigma.api.service.ObfuscationTestService; | ||
| 9 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 10 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 11 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 12 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 14 | import cuchaz.enigma.utils.I18n; | ||
| 15 | |||
| 16 | import java.util.*; | ||
| 17 | |||
| 18 | public class StatsGenerator { | ||
| 19 | private final EntryIndex entryIndex; | ||
| 20 | private final EntryRemapper mapper; | ||
| 21 | private final EntryResolver entryResolver; | ||
| 22 | private final List<ObfuscationTestService> obfuscationTestServices; | ||
| 23 | private final List<NameProposalService> nameProposalServices; | ||
| 24 | |||
| 25 | public StatsGenerator(EnigmaProject project) { | ||
| 26 | entryIndex = project.getJarIndex().getEntryIndex(); | ||
| 27 | mapper = project.getMapper(); | ||
| 28 | entryResolver = project.getJarIndex().getEntryResolver(); | ||
| 29 | obfuscationTestServices = project.getEnigma().getServices().get(ObfuscationTestService.TYPE); | ||
| 30 | nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE); | ||
| 31 | } | ||
| 32 | |||
| 33 | public String generate(ProgressListener progress, Set<StatsMember> includedMembers) { | ||
| 34 | includedMembers = EnumSet.copyOf(includedMembers); | ||
| 35 | int totalWork = 0; | ||
| 36 | |||
| 37 | if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { | ||
| 38 | totalWork += entryIndex.getMethods().size(); | ||
| 39 | } | ||
| 40 | |||
| 41 | if (includedMembers.contains(StatsMember.FIELDS)) { | ||
| 42 | totalWork += entryIndex.getFields().size(); | ||
| 43 | } | ||
| 44 | |||
| 45 | if (includedMembers.contains(StatsMember.CLASSES)) { | ||
| 46 | totalWork += entryIndex.getClasses().size(); | ||
| 47 | } | ||
| 48 | |||
| 49 | progress.init(totalWork, I18n.translate("progress.stats")); | ||
| 50 | |||
| 51 | Map<String, Integer> counts = new HashMap<>(); | ||
| 52 | |||
| 53 | int numDone = 0; | ||
| 54 | if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { | ||
| 55 | for (MethodEntry method : entryIndex.getMethods()) { | ||
| 56 | progress.step(numDone++, I18n.translate("type.methods")); | ||
| 57 | MethodEntry root = entryResolver | ||
| 58 | .resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT) | ||
| 59 | .stream() | ||
| 60 | .findFirst() | ||
| 61 | .orElseThrow(AssertionError::new); | ||
| 62 | |||
| 63 | if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) { | ||
| 64 | if (includedMembers.contains(StatsMember.METHODS)) { | ||
| 65 | update(counts, method); | ||
| 66 | } | ||
| 67 | |||
| 68 | if (includedMembers.contains(StatsMember.PARAMETERS)) { | ||
| 69 | int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1; | ||
| 70 | for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) { | ||
| 71 | update(counts, new LocalVariableEntry(method, index, "", true,null)); | ||
| 72 | index += argument.getSize(); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | if (includedMembers.contains(StatsMember.FIELDS)) { | ||
| 80 | for (FieldEntry field : entryIndex.getFields()) { | ||
| 81 | progress.step(numDone++, I18n.translate("type.fields")); | ||
| 82 | update(counts, field); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | if (includedMembers.contains(StatsMember.CLASSES)) { | ||
| 87 | for (ClassEntry clazz : entryIndex.getClasses()) { | ||
| 88 | progress.step(numDone++, I18n.translate("type.classes")); | ||
| 89 | update(counts, clazz); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | progress.step(-1, I18n.translate("progress.stats.data")); | ||
| 94 | |||
| 95 | Tree<Integer> tree = new Tree<>(); | ||
| 96 | |||
| 97 | for (Map.Entry<String, Integer> entry : counts.entrySet()) { | ||
| 98 | if (entry.getKey().startsWith("com.mojang")) continue; // just a few unmapped names, no point in having a subsection | ||
| 99 | tree.getNode(entry.getKey()).value = entry.getValue(); | ||
| 100 | } | ||
| 101 | |||
| 102 | tree.collapse(tree.root); | ||
| 103 | return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root); | ||
| 104 | } | ||
| 105 | |||
| 106 | private void update(Map<String, Integer> counts, Entry<?> entry) { | ||
| 107 | if (isObfuscated(entry)) { | ||
| 108 | String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.'); | ||
| 109 | counts.put(parent, counts.getOrDefault(parent, 0) + 1); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | private boolean isObfuscated(Entry<?> entry) { | ||
| 114 | String name = entry.getName(); | ||
| 115 | |||
| 116 | if (!obfuscationTestServices.isEmpty()) { | ||
| 117 | for (ObfuscationTestService service : obfuscationTestServices) { | ||
| 118 | if (service.testDeobfuscated(entry)) { | ||
| 119 | return false; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | if (!nameProposalServices.isEmpty()) { | ||
| 125 | for (NameProposalService service : nameProposalServices) { | ||
| 126 | if (service.proposeName(entry, mapper).isPresent()) { | ||
| 127 | return false; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | String mappedName = mapper.deobfuscate(entry).getName(); | ||
| 133 | if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) { | ||
| 134 | return false; | ||
| 135 | } | ||
| 136 | |||
| 137 | return true; | ||
| 138 | } | ||
| 139 | |||
| 140 | private static class Tree<T> { | ||
| 141 | public final Node<T> root; | ||
| 142 | private final Map<String, Node<T>> nodes = new HashMap<>(); | ||
| 143 | |||
| 144 | public static class Node<T> { | ||
| 145 | public String name; | ||
| 146 | public T value; | ||
| 147 | public List<Node<T>> children = new ArrayList<>(); | ||
| 148 | private final transient Map<String, Node<T>> namedChildren = new HashMap<>(); | ||
| 149 | |||
| 150 | public Node(String name, T value) { | ||
| 151 | this.name = name; | ||
| 152 | this.value = value; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | public Tree() { | ||
| 157 | root = new Node<>("", null); | ||
| 158 | } | ||
| 159 | |||
| 160 | public Node<T> getNode(String name) { | ||
| 161 | Node<T> node = nodes.get(name); | ||
| 162 | |||
| 163 | if (node == null) { | ||
| 164 | node = root; | ||
| 165 | |||
| 166 | for (String part : name.split("\\.")) { | ||
| 167 | Node<T> child = node.namedChildren.get(part); | ||
| 168 | |||
| 169 | if (child == null) { | ||
| 170 | child = new Node<>(part, null); | ||
| 171 | node.namedChildren.put(part, child); | ||
| 172 | node.children.add(child); | ||
| 173 | } | ||
| 174 | |||
| 175 | node = child; | ||
| 176 | } | ||
| 177 | |||
| 178 | nodes.put(name, node); | ||
| 179 | } | ||
| 180 | |||
| 181 | return node; | ||
| 182 | } | ||
| 183 | |||
| 184 | public void collapse(Node<T> node) { | ||
| 185 | while (node.children.size() == 1) { | ||
| 186 | Node<T> child = node.children.get(0); | ||
| 187 | node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name; | ||
| 188 | node.children = child.children; | ||
| 189 | node.value = child.value; | ||
| 190 | } | ||
| 191 | |||
| 192 | for (Node<T> child : node.children) { | ||
| 193 | collapse(child); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java b/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java deleted file mode 100644 index 70b4f40..0000000 --- a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java +++ /dev/null | |||
| @@ -1,8 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.stats; | ||
| 2 | |||
| 3 | public enum StatsMember { | ||
| 4 | METHODS, | ||
| 5 | FIELDS, | ||
| 6 | PARAMETERS, | ||
| 7 | CLASSES | ||
| 8 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java deleted file mode 100644 index 612e3e9..0000000 --- a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java +++ /dev/null | |||
| @@ -1,77 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.util; | ||
| 2 | |||
| 3 | import java.awt.Component; | ||
| 4 | import java.awt.event.MouseEvent; | ||
| 5 | |||
| 6 | import javax.swing.*; | ||
| 7 | import javax.swing.border.Border; | ||
| 8 | |||
| 9 | public abstract class AbstractListCellRenderer<E> extends JPanel implements ListCellRenderer<E> { | ||
| 10 | |||
| 11 | private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); | ||
| 12 | |||
| 13 | private Border noFocusBorder; | ||
| 14 | |||
| 15 | public AbstractListCellRenderer() { | ||
| 16 | setBorder(getNoFocusBorder()); | ||
| 17 | } | ||
| 18 | |||
| 19 | protected Border getNoFocusBorder() { | ||
| 20 | if (noFocusBorder == null) { | ||
| 21 | Border border = UIManager.getLookAndFeel().getDefaults().getBorder("List.List.cellNoFocusBorder"); | ||
| 22 | noFocusBorder = border != null ? border : NO_FOCUS_BORDER; | ||
| 23 | } | ||
| 24 | return noFocusBorder; | ||
| 25 | } | ||
| 26 | |||
| 27 | protected Border getBorder(boolean isSelected, boolean cellHasFocus) { | ||
| 28 | Border b = null; | ||
| 29 | if (cellHasFocus) { | ||
| 30 | UIDefaults defaults = UIManager.getLookAndFeel().getDefaults(); | ||
| 31 | if (isSelected) { | ||
| 32 | b = defaults.getBorder("List.focusSelectedCellHighlightBorder"); | ||
| 33 | } | ||
| 34 | if (b == null) { | ||
| 35 | b = defaults.getBorder("List.focusCellHighlightBorder"); | ||
| 36 | } | ||
| 37 | } else { | ||
| 38 | b = getNoFocusBorder(); | ||
| 39 | } | ||
| 40 | return b; | ||
| 41 | } | ||
| 42 | |||
| 43 | public abstract void updateUiForEntry(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus); | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus) { | ||
| 47 | updateUiForEntry(list, value, index, isSelected, cellHasFocus); | ||
| 48 | |||
| 49 | if (isSelected) { | ||
| 50 | setBackground(list.getSelectionBackground()); | ||
| 51 | setForeground(list.getSelectionForeground()); | ||
| 52 | } else { | ||
| 53 | setBackground(list.getBackground()); | ||
| 54 | setForeground(list.getForeground()); | ||
| 55 | } | ||
| 56 | |||
| 57 | setEnabled(list.isEnabled()); | ||
| 58 | setFont(list.getFont()); | ||
| 59 | |||
| 60 | setBorder(getBorder(isSelected, cellHasFocus)); | ||
| 61 | |||
| 62 | // This isn't the width of the cell, but it's close enough for where it's needed (getComponentAt in getToolTipText) | ||
| 63 | setSize(list.getWidth(), getPreferredSize().height); | ||
| 64 | |||
| 65 | return this; | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public String getToolTipText(MouseEvent event) { | ||
| 70 | Component c = getComponentAt(event.getPoint()); | ||
| 71 | if (c instanceof JComponent) { | ||
| 72 | return ((JComponent) c).getToolTipText(); | ||
| 73 | } | ||
| 74 | return getToolTipText(); | ||
| 75 | } | ||
| 76 | |||
| 77 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/util/History.java b/src/main/java/cuchaz/enigma/gui/util/History.java deleted file mode 100644 index 94f3105..0000000 --- a/src/main/java/cuchaz/enigma/gui/util/History.java +++ /dev/null | |||
| @@ -1,49 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.util; | ||
| 2 | |||
| 3 | import com.google.common.collect.Queues; | ||
| 4 | |||
| 5 | import java.util.Deque; | ||
| 6 | |||
| 7 | public class History<T> { | ||
| 8 | private final Deque<T> previous = Queues.newArrayDeque(); | ||
| 9 | private final Deque<T> next = Queues.newArrayDeque(); | ||
| 10 | private T current; | ||
| 11 | |||
| 12 | public History(T initial) { | ||
| 13 | current = initial; | ||
| 14 | } | ||
| 15 | |||
| 16 | public T getCurrent() { | ||
| 17 | return current; | ||
| 18 | } | ||
| 19 | |||
| 20 | public void push(T value) { | ||
| 21 | previous.addLast(current); | ||
| 22 | current = value; | ||
| 23 | next.clear(); | ||
| 24 | } | ||
| 25 | |||
| 26 | public void replace(T value) { | ||
| 27 | current = value; | ||
| 28 | } | ||
| 29 | |||
| 30 | public boolean canGoBack() { | ||
| 31 | return !previous.isEmpty(); | ||
| 32 | } | ||
| 33 | |||
| 34 | public T goBack() { | ||
| 35 | next.addFirst(current); | ||
| 36 | current = previous.removeLast(); | ||
| 37 | return current; | ||
| 38 | } | ||
| 39 | |||
| 40 | public boolean canGoForward() { | ||
| 41 | return !next.isEmpty(); | ||
| 42 | } | ||
| 43 | |||
| 44 | public T goForward() { | ||
| 45 | previous.addLast(current); | ||
| 46 | current = next.removeFirst(); | ||
| 47 | return current; | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java b/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java deleted file mode 100644 index d045c6d..0000000 --- a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java +++ /dev/null | |||
| @@ -1,8 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.util; | ||
| 2 | |||
| 3 | @FunctionalInterface | ||
| 4 | public interface ScaleChangeListener { | ||
| 5 | |||
| 6 | void onScaleChanged(float scale, float oldScale); | ||
| 7 | |||
| 8 | } | ||
diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java deleted file mode 100644 index 9f722e9..0000000 --- a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java +++ /dev/null | |||
| @@ -1,110 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.util; | ||
| 2 | |||
| 3 | import java.awt.Dimension; | ||
| 4 | import java.awt.Font; | ||
| 5 | import java.io.IOException; | ||
| 6 | import java.lang.reflect.Field; | ||
| 7 | import java.util.ArrayList; | ||
| 8 | import java.util.List; | ||
| 9 | |||
| 10 | import javax.swing.BorderFactory; | ||
| 11 | import javax.swing.UIManager; | ||
| 12 | import javax.swing.border.Border; | ||
| 13 | |||
| 14 | import com.github.swingdpi.UiDefaultsScaler; | ||
| 15 | import com.github.swingdpi.plaf.BasicTweaker; | ||
| 16 | import com.github.swingdpi.plaf.MetalTweaker; | ||
| 17 | import com.github.swingdpi.plaf.NimbusTweaker; | ||
| 18 | import com.github.swingdpi.plaf.WindowsTweaker; | ||
| 19 | import cuchaz.enigma.config.Config; | ||
| 20 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 21 | |||
| 22 | public class ScaleUtil { | ||
| 23 | |||
| 24 | private static List<ScaleChangeListener> listeners = new ArrayList<>(); | ||
| 25 | |||
| 26 | public static float getScaleFactor() { | ||
| 27 | return Config.getInstance().scaleFactor; | ||
| 28 | } | ||
| 29 | |||
| 30 | public static void setScaleFactor(float scaleFactor) { | ||
| 31 | float oldScale = getScaleFactor(); | ||
| 32 | float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f); | ||
| 33 | Config.getInstance().scaleFactor = clamped; | ||
| 34 | try { | ||
| 35 | Config.getInstance().saveConfig(); | ||
| 36 | } catch (IOException e) { | ||
| 37 | e.printStackTrace(); | ||
| 38 | } | ||
| 39 | listeners.forEach(l -> l.onScaleChanged(clamped, oldScale)); | ||
| 40 | } | ||
| 41 | |||
| 42 | public static void addListener(ScaleChangeListener listener) { | ||
| 43 | listeners.add(listener); | ||
| 44 | } | ||
| 45 | |||
| 46 | public static void removeListener(ScaleChangeListener listener) { | ||
| 47 | listeners.remove(listener); | ||
| 48 | } | ||
| 49 | |||
| 50 | public static Dimension getDimension(int width, int height) { | ||
| 51 | return new Dimension(scale(width), scale(height)); | ||
| 52 | } | ||
| 53 | |||
| 54 | public static Font getFont(String fontName, int plain, int fontSize) { | ||
| 55 | return scaleFont(new Font(fontName, plain, fontSize)); | ||
| 56 | } | ||
| 57 | |||
| 58 | public static Font scaleFont(Font font) { | ||
| 59 | return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font); | ||
| 60 | } | ||
| 61 | |||
| 62 | public static float scale(float f) { | ||
| 63 | return f * getScaleFactor(); | ||
| 64 | } | ||
| 65 | |||
| 66 | public static float invert(float f) { | ||
| 67 | return f / getScaleFactor(); | ||
| 68 | } | ||
| 69 | |||
| 70 | public static int scale(int i) { | ||
| 71 | return (int) (i * getScaleFactor()); | ||
| 72 | } | ||
| 73 | |||
| 74 | public static Border createEmptyBorder(int top, int left, int bottom, int right) { | ||
| 75 | return BorderFactory.createEmptyBorder(scale(top), scale(left), scale(bottom), scale(right)); | ||
| 76 | } | ||
| 77 | |||
| 78 | public static int invert(int i) { | ||
| 79 | return (int) (i / getScaleFactor()); | ||
| 80 | } | ||
| 81 | |||
| 82 | public static void applyScaling() { | ||
| 83 | float scale = getScaleFactor(); | ||
| 84 | UiDefaultsScaler.updateAndApplyGlobalScaling((int) (100 * scale), true); | ||
| 85 | try { | ||
| 86 | Field defaultFontField = DefaultSyntaxKit.class.getDeclaredField("DEFAULT_FONT"); | ||
| 87 | defaultFontField.setAccessible(true); | ||
| 88 | Font font = (Font) defaultFontField.get(null); | ||
| 89 | font = font.deriveFont(12 * scale); | ||
| 90 | defaultFontField.set(null, font); | ||
| 91 | } catch (NoSuchFieldException | IllegalAccessException e) { | ||
| 92 | e.printStackTrace(); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | private static BasicTweaker createTweakerForCurrentLook(float dpiScaling) { | ||
| 97 | String testString = UIManager.getLookAndFeel().getName().toLowerCase(); | ||
| 98 | if (testString.contains("windows")) { | ||
| 99 | return new WindowsTweaker(dpiScaling, testString.contains("classic")); | ||
| 100 | } | ||
| 101 | if (testString.contains("metal")) { | ||
| 102 | return new MetalTweaker(dpiScaling); | ||
| 103 | } | ||
| 104 | if (testString.contains("nimbus")) { | ||
| 105 | return new NimbusTweaker(dpiScaling); | ||
| 106 | } | ||
| 107 | return new BasicTweaker(dpiScaling); | ||
| 108 | } | ||
| 109 | |||
| 110 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java deleted file mode 100644 index 2cfe823..0000000 --- a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java +++ /dev/null | |||
| @@ -1,164 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import com.google.common.io.MoreFiles; | ||
| 4 | import cuchaz.enigma.*; | ||
| 5 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 7 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | import joptsimple.OptionParser; | ||
| 10 | import joptsimple.OptionSet; | ||
| 11 | import joptsimple.OptionSpec; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.PrintWriter; | ||
| 15 | import java.nio.file.Files; | ||
| 16 | import java.nio.file.Path; | ||
| 17 | import java.nio.file.Paths; | ||
| 18 | import java.util.concurrent.BlockingQueue; | ||
| 19 | import java.util.concurrent.Executors; | ||
| 20 | import java.util.concurrent.LinkedBlockingDeque; | ||
| 21 | import java.util.concurrent.TimeUnit; | ||
| 22 | |||
| 23 | public class DedicatedEnigmaServer extends EnigmaServer { | ||
| 24 | |||
| 25 | private final EnigmaProfile profile; | ||
| 26 | private final MappingFormat mappingFormat; | ||
| 27 | private final Path mappingsFile; | ||
| 28 | private final PrintWriter log; | ||
| 29 | private BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>(); | ||
| 30 | |||
| 31 | public DedicatedEnigmaServer( | ||
| 32 | byte[] jarChecksum, | ||
| 33 | char[] password, | ||
| 34 | EnigmaProfile profile, | ||
| 35 | MappingFormat mappingFormat, | ||
| 36 | Path mappingsFile, | ||
| 37 | PrintWriter log, | ||
| 38 | EntryRemapper mappings, | ||
| 39 | int port | ||
| 40 | ) { | ||
| 41 | super(jarChecksum, password, mappings, port); | ||
| 42 | this.profile = profile; | ||
| 43 | this.mappingFormat = mappingFormat; | ||
| 44 | this.mappingsFile = mappingsFile; | ||
| 45 | this.log = log; | ||
| 46 | } | ||
| 47 | |||
| 48 | @Override | ||
| 49 | protected void runOnThread(Runnable task) { | ||
| 50 | tasks.add(task); | ||
| 51 | } | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public void log(String message) { | ||
| 55 | super.log(message); | ||
| 56 | log.println(message); | ||
| 57 | } | ||
| 58 | |||
| 59 | public static void main(String[] args) { | ||
| 60 | OptionParser parser = new OptionParser(); | ||
| 61 | |||
| 62 | OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup") | ||
| 63 | .withRequiredArg() | ||
| 64 | .required() | ||
| 65 | .withValuesConvertedBy(Main.PathConverter.INSTANCE); | ||
| 66 | |||
| 67 | OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup") | ||
| 68 | .withRequiredArg() | ||
| 69 | .required() | ||
| 70 | .withValuesConvertedBy(Main.PathConverter.INSTANCE); | ||
| 71 | |||
| 72 | OptionSpec<Path> profileOpt = parser.accepts("profile", "Profile json to apply at startup") | ||
| 73 | .withRequiredArg() | ||
| 74 | .withValuesConvertedBy(Main.PathConverter.INSTANCE); | ||
| 75 | |||
| 76 | OptionSpec<Integer> portOpt = parser.accepts("port", "Port to run the server on") | ||
| 77 | .withOptionalArg() | ||
| 78 | .ofType(Integer.class) | ||
| 79 | .defaultsTo(EnigmaServer.DEFAULT_PORT); | ||
| 80 | |||
| 81 | OptionSpec<String> passwordOpt = parser.accepts("password", "The password to join the server") | ||
| 82 | .withRequiredArg() | ||
| 83 | .defaultsTo(""); | ||
| 84 | |||
| 85 | OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to") | ||
| 86 | .withRequiredArg() | ||
| 87 | .withValuesConvertedBy(Main.PathConverter.INSTANCE) | ||
| 88 | .defaultsTo(Paths.get("log.txt")); | ||
| 89 | |||
| 90 | OptionSet parsedArgs = parser.parse(args); | ||
| 91 | Path jar = parsedArgs.valueOf(jarOpt); | ||
| 92 | Path mappingsFile = parsedArgs.valueOf(mappingsOpt); | ||
| 93 | Path profileFile = parsedArgs.valueOf(profileOpt); | ||
| 94 | int port = parsedArgs.valueOf(portOpt); | ||
| 95 | char[] password = parsedArgs.valueOf(passwordOpt).toCharArray(); | ||
| 96 | if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { | ||
| 97 | System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters"); | ||
| 98 | System.exit(1); | ||
| 99 | } | ||
| 100 | Path logFile = parsedArgs.valueOf(logFileOpt); | ||
| 101 | |||
| 102 | System.out.println("Starting Enigma server"); | ||
| 103 | DedicatedEnigmaServer server; | ||
| 104 | try { | ||
| 105 | byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); | ||
| 106 | |||
| 107 | EnigmaProfile profile = EnigmaProfile.read(profileFile); | ||
| 108 | Enigma enigma = Enigma.builder().setProfile(profile).build(); | ||
| 109 | System.out.println("Indexing Jar..."); | ||
| 110 | EnigmaProject project = enigma.openJar(jar, ProgressListener.none()); | ||
| 111 | |||
| 112 | MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; | ||
| 113 | EntryRemapper mappings; | ||
| 114 | if (!Files.exists(mappingsFile)) { | ||
| 115 | mappings = EntryRemapper.empty(project.getJarIndex()); | ||
| 116 | } else { | ||
| 117 | System.out.println("Reading mappings..."); | ||
| 118 | if (Files.isDirectory(mappingsFile)) { | ||
| 119 | mappingFormat = MappingFormat.ENIGMA_DIRECTORY; | ||
| 120 | } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) { | ||
| 121 | mappingFormat = MappingFormat.ENIGMA_ZIP; | ||
| 122 | } else { | ||
| 123 | mappingFormat = MappingFormat.ENIGMA_FILE; | ||
| 124 | } | ||
| 125 | mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters())); | ||
| 126 | } | ||
| 127 | |||
| 128 | PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile)); | ||
| 129 | |||
| 130 | server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port); | ||
| 131 | server.start(); | ||
| 132 | System.out.println("Server started"); | ||
| 133 | } catch (IOException | MappingParseException e) { | ||
| 134 | System.err.println("Error starting server!"); | ||
| 135 | e.printStackTrace(); | ||
| 136 | System.exit(1); | ||
| 137 | return; | ||
| 138 | } | ||
| 139 | |||
| 140 | // noinspection RedundantSuppression | ||
| 141 | // noinspection Convert2MethodRef - javac 8 bug | ||
| 142 | Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES); | ||
| 143 | Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings)); | ||
| 144 | |||
| 145 | while (true) { | ||
| 146 | try { | ||
| 147 | server.tasks.take().run(); | ||
| 148 | } catch (InterruptedException e) { | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | @Override | ||
| 155 | public synchronized void stop() { | ||
| 156 | super.stop(); | ||
| 157 | System.exit(0); | ||
| 158 | } | ||
| 159 | |||
| 160 | private void saveMappings() { | ||
| 161 | mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()); | ||
| 162 | log.flush(); | ||
| 163 | } | ||
| 164 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/src/main/java/cuchaz/enigma/network/EnigmaClient.java deleted file mode 100644 index bfa53d7..0000000 --- a/src/main/java/cuchaz/enigma/network/EnigmaClient.java +++ /dev/null | |||
| @@ -1,85 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.packet.LoginC2SPacket; | ||
| 5 | import cuchaz.enigma.network.packet.Packet; | ||
| 6 | import cuchaz.enigma.network.packet.PacketRegistry; | ||
| 7 | |||
| 8 | import javax.swing.SwingUtilities; | ||
| 9 | import java.io.DataInput; | ||
| 10 | import java.io.DataInputStream; | ||
| 11 | import java.io.DataOutput; | ||
| 12 | import java.io.DataOutputStream; | ||
| 13 | import java.io.EOFException; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.net.Socket; | ||
| 16 | import java.net.SocketException; | ||
| 17 | |||
| 18 | public class EnigmaClient { | ||
| 19 | |||
| 20 | private final GuiController controller; | ||
| 21 | |||
| 22 | private final String ip; | ||
| 23 | private final int port; | ||
| 24 | private Socket socket; | ||
| 25 | private DataOutput output; | ||
| 26 | |||
| 27 | public EnigmaClient(GuiController controller, String ip, int port) { | ||
| 28 | this.controller = controller; | ||
| 29 | this.ip = ip; | ||
| 30 | this.port = port; | ||
| 31 | } | ||
| 32 | |||
| 33 | public void connect() throws IOException { | ||
| 34 | socket = new Socket(ip, port); | ||
| 35 | output = new DataOutputStream(socket.getOutputStream()); | ||
| 36 | Thread thread = new Thread(() -> { | ||
| 37 | try { | ||
| 38 | DataInput input = new DataInputStream(socket.getInputStream()); | ||
| 39 | while (true) { | ||
| 40 | int packetId; | ||
| 41 | try { | ||
| 42 | packetId = input.readUnsignedByte(); | ||
| 43 | } catch (EOFException | SocketException e) { | ||
| 44 | break; | ||
| 45 | } | ||
| 46 | Packet<GuiController> packet = PacketRegistry.createS2CPacket(packetId); | ||
| 47 | if (packet == null) { | ||
| 48 | throw new IOException("Received invalid packet id " + packetId); | ||
| 49 | } | ||
| 50 | packet.read(input); | ||
| 51 | SwingUtilities.invokeLater(() -> packet.handle(controller)); | ||
| 52 | } | ||
| 53 | } catch (IOException e) { | ||
| 54 | controller.disconnectIfConnected(e.toString()); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | controller.disconnectIfConnected("Disconnected"); | ||
| 58 | }); | ||
| 59 | thread.setName("Client I/O thread"); | ||
| 60 | thread.setDaemon(true); | ||
| 61 | thread.start(); | ||
| 62 | } | ||
| 63 | |||
| 64 | public synchronized void disconnect() { | ||
| 65 | if (socket != null && !socket.isClosed()) { | ||
| 66 | try { | ||
| 67 | socket.close(); | ||
| 68 | } catch (IOException e1) { | ||
| 69 | System.err.println("Failed to close socket"); | ||
| 70 | e1.printStackTrace(); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | |||
| 76 | public void sendPacket(Packet<ServerPacketHandler> packet) { | ||
| 77 | try { | ||
| 78 | output.writeByte(PacketRegistry.getC2SId(packet)); | ||
| 79 | packet.write(output); | ||
| 80 | } catch (IOException e) { | ||
| 81 | controller.disconnectIfConnected(e.toString()); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/src/main/java/cuchaz/enigma/network/EnigmaServer.java deleted file mode 100644 index b0e15a3..0000000 --- a/src/main/java/cuchaz/enigma/network/EnigmaServer.java +++ /dev/null | |||
| @@ -1,292 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.packet.KickS2CPacket; | ||
| 5 | import cuchaz.enigma.network.packet.MessageS2CPacket; | ||
| 6 | import cuchaz.enigma.network.packet.Packet; | ||
| 7 | import cuchaz.enigma.network.packet.PacketRegistry; | ||
| 8 | import cuchaz.enigma.network.packet.RemoveMappingS2CPacket; | ||
| 9 | import cuchaz.enigma.network.packet.RenameS2CPacket; | ||
| 10 | import cuchaz.enigma.network.packet.UserListS2CPacket; | ||
| 11 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 12 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 14 | import cuchaz.enigma.utils.Message; | ||
| 15 | |||
| 16 | import java.io.DataInput; | ||
| 17 | import java.io.DataInputStream; | ||
| 18 | import java.io.DataOutput; | ||
| 19 | import java.io.DataOutputStream; | ||
| 20 | import java.io.EOFException; | ||
| 21 | import java.io.IOException; | ||
| 22 | import java.net.ServerSocket; | ||
| 23 | import java.net.Socket; | ||
| 24 | import java.net.SocketException; | ||
| 25 | import java.util.ArrayList; | ||
| 26 | import java.util.Collections; | ||
| 27 | import java.util.HashMap; | ||
| 28 | import java.util.HashSet; | ||
| 29 | import java.util.List; | ||
| 30 | import java.util.Map; | ||
| 31 | import java.util.Set; | ||
| 32 | import java.util.concurrent.CopyOnWriteArrayList; | ||
| 33 | |||
| 34 | public abstract class EnigmaServer { | ||
| 35 | |||
| 36 | // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347 | ||
| 37 | public static final int DEFAULT_PORT = 34712; | ||
| 38 | public static final int PROTOCOL_VERSION = 0; | ||
| 39 | public static final String OWNER_USERNAME = "Owner"; | ||
| 40 | public static final int CHECKSUM_SIZE = 20; | ||
| 41 | public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet | ||
| 42 | |||
| 43 | private final int port; | ||
| 44 | private ServerSocket socket; | ||
| 45 | private List<Socket> clients = new CopyOnWriteArrayList<>(); | ||
| 46 | private Map<Socket, String> usernames = new HashMap<>(); | ||
| 47 | private Set<Socket> unapprovedClients = new HashSet<>(); | ||
| 48 | |||
| 49 | private final byte[] jarChecksum; | ||
| 50 | private final char[] password; | ||
| 51 | |||
| 52 | public static final int DUMMY_SYNC_ID = 0; | ||
| 53 | private final EntryRemapper mappings; | ||
| 54 | private Map<Entry<?>, Integer> syncIds = new HashMap<>(); | ||
| 55 | private Map<Integer, Entry<?>> inverseSyncIds = new HashMap<>(); | ||
| 56 | private Map<Integer, Set<Socket>> clientsNeedingConfirmation = new HashMap<>(); | ||
| 57 | private int nextSyncId = DUMMY_SYNC_ID + 1; | ||
| 58 | |||
| 59 | private static int nextIoId = 0; | ||
| 60 | |||
| 61 | public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { | ||
| 62 | this.jarChecksum = jarChecksum; | ||
| 63 | this.password = password; | ||
| 64 | this.mappings = mappings; | ||
| 65 | this.port = port; | ||
| 66 | } | ||
| 67 | |||
| 68 | public void start() throws IOException { | ||
| 69 | socket = new ServerSocket(port); | ||
| 70 | log("Server started on " + socket.getInetAddress() + ":" + port); | ||
| 71 | Thread thread = new Thread(() -> { | ||
| 72 | try { | ||
| 73 | while (!socket.isClosed()) { | ||
| 74 | acceptClient(); | ||
| 75 | } | ||
| 76 | } catch (SocketException e) { | ||
| 77 | System.out.println("Server closed"); | ||
| 78 | } catch (IOException e) { | ||
| 79 | e.printStackTrace(); | ||
| 80 | } | ||
| 81 | }); | ||
| 82 | thread.setName("Server client listener"); | ||
| 83 | thread.setDaemon(true); | ||
| 84 | thread.start(); | ||
| 85 | } | ||
| 86 | |||
| 87 | private void acceptClient() throws IOException { | ||
| 88 | Socket client = socket.accept(); | ||
| 89 | clients.add(client); | ||
| 90 | Thread thread = new Thread(() -> { | ||
| 91 | try { | ||
| 92 | DataInput input = new DataInputStream(client.getInputStream()); | ||
| 93 | while (true) { | ||
| 94 | int packetId; | ||
| 95 | try { | ||
| 96 | packetId = input.readUnsignedByte(); | ||
| 97 | } catch (EOFException | SocketException e) { | ||
| 98 | break; | ||
| 99 | } | ||
| 100 | Packet<ServerPacketHandler> packet = PacketRegistry.createC2SPacket(packetId); | ||
| 101 | if (packet == null) { | ||
| 102 | throw new IOException("Received invalid packet id " + packetId); | ||
| 103 | } | ||
| 104 | packet.read(input); | ||
| 105 | runOnThread(() -> packet.handle(new ServerPacketHandler(client, this))); | ||
| 106 | } | ||
| 107 | } catch (IOException e) { | ||
| 108 | kick(client, e.toString()); | ||
| 109 | e.printStackTrace(); | ||
| 110 | return; | ||
| 111 | } | ||
| 112 | kick(client, "disconnect.disconnected"); | ||
| 113 | }); | ||
| 114 | thread.setName("Server I/O thread #" + (nextIoId++)); | ||
| 115 | thread.setDaemon(true); | ||
| 116 | thread.start(); | ||
| 117 | } | ||
| 118 | |||
| 119 | public void stop() { | ||
| 120 | runOnThread(() -> { | ||
| 121 | if (socket != null && !socket.isClosed()) { | ||
| 122 | for (Socket client : clients) { | ||
| 123 | kick(client, "disconnect.server_closed"); | ||
| 124 | } | ||
| 125 | try { | ||
| 126 | socket.close(); | ||
| 127 | } catch (IOException e) { | ||
| 128 | System.err.println("Failed to close server socket"); | ||
| 129 | e.printStackTrace(); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | }); | ||
| 133 | } | ||
| 134 | |||
| 135 | public void kick(Socket client, String reason) { | ||
| 136 | if (!clients.remove(client)) return; | ||
| 137 | |||
| 138 | sendPacket(client, new KickS2CPacket(reason)); | ||
| 139 | |||
| 140 | clientsNeedingConfirmation.values().removeIf(list -> { | ||
| 141 | list.remove(client); | ||
| 142 | return list.isEmpty(); | ||
| 143 | }); | ||
| 144 | String username = usernames.remove(client); | ||
| 145 | try { | ||
| 146 | client.close(); | ||
| 147 | } catch (IOException e) { | ||
| 148 | System.err.println("Failed to close server client socket"); | ||
| 149 | e.printStackTrace(); | ||
| 150 | } | ||
| 151 | |||
| 152 | if (username != null) { | ||
| 153 | System.out.println("Kicked " + username + " because " + reason); | ||
| 154 | sendMessage(Message.disconnect(username)); | ||
| 155 | } | ||
| 156 | sendUsernamePacket(); | ||
| 157 | } | ||
| 158 | |||
| 159 | public boolean isUsernameTaken(String username) { | ||
| 160 | return usernames.containsValue(username); | ||
| 161 | } | ||
| 162 | |||
| 163 | public void setUsername(Socket client, String username) { | ||
| 164 | usernames.put(client, username); | ||
| 165 | sendUsernamePacket(); | ||
| 166 | } | ||
| 167 | |||
| 168 | private void sendUsernamePacket() { | ||
| 169 | List<String> usernames = new ArrayList<>(this.usernames.values()); | ||
| 170 | Collections.sort(usernames); | ||
| 171 | sendToAll(new UserListS2CPacket(usernames)); | ||
| 172 | } | ||
| 173 | |||
| 174 | public String getUsername(Socket client) { | ||
| 175 | return usernames.get(client); | ||
| 176 | } | ||
| 177 | |||
| 178 | public void sendPacket(Socket client, Packet<GuiController> packet) { | ||
| 179 | if (!client.isClosed()) { | ||
| 180 | int packetId = PacketRegistry.getS2CId(packet); | ||
| 181 | try { | ||
| 182 | DataOutput output = new DataOutputStream(client.getOutputStream()); | ||
| 183 | output.writeByte(packetId); | ||
| 184 | packet.write(output); | ||
| 185 | } catch (IOException e) { | ||
| 186 | if (!(packet instanceof KickS2CPacket)) { | ||
| 187 | kick(client, e.toString()); | ||
| 188 | e.printStackTrace(); | ||
| 189 | } | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | public void sendToAll(Packet<GuiController> packet) { | ||
| 195 | for (Socket client : clients) { | ||
| 196 | sendPacket(client, packet); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | public void sendToAllExcept(Socket excluded, Packet<GuiController> packet) { | ||
| 201 | for (Socket client : clients) { | ||
| 202 | if (client != excluded) { | ||
| 203 | sendPacket(client, packet); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | public boolean canModifyEntry(Socket client, Entry<?> entry) { | ||
| 209 | if (unapprovedClients.contains(client)) { | ||
| 210 | return false; | ||
| 211 | } | ||
| 212 | |||
| 213 | Integer syncId = syncIds.get(entry); | ||
| 214 | if (syncId == null) { | ||
| 215 | return true; | ||
| 216 | } | ||
| 217 | Set<Socket> clients = clientsNeedingConfirmation.get(syncId); | ||
| 218 | return clients == null || !clients.contains(client); | ||
| 219 | } | ||
| 220 | |||
| 221 | public int lockEntry(Socket exception, Entry<?> entry) { | ||
| 222 | int syncId = nextSyncId; | ||
| 223 | nextSyncId++; | ||
| 224 | // sync id is sent as an unsigned short, can't have more than 65536 | ||
| 225 | if (nextSyncId == 65536) { | ||
| 226 | nextSyncId = DUMMY_SYNC_ID + 1; | ||
| 227 | } | ||
| 228 | Integer oldSyncId = syncIds.get(entry); | ||
| 229 | if (oldSyncId != null) { | ||
| 230 | clientsNeedingConfirmation.remove(oldSyncId); | ||
| 231 | } | ||
| 232 | syncIds.put(entry, syncId); | ||
| 233 | inverseSyncIds.put(syncId, entry); | ||
| 234 | Set<Socket> clients = new HashSet<>(this.clients); | ||
| 235 | clients.remove(exception); | ||
| 236 | clientsNeedingConfirmation.put(syncId, clients); | ||
| 237 | return syncId; | ||
| 238 | } | ||
| 239 | |||
| 240 | public void confirmChange(Socket client, int syncId) { | ||
| 241 | if (usernames.containsKey(client)) { | ||
| 242 | unapprovedClients.remove(client); | ||
| 243 | } | ||
| 244 | |||
| 245 | Set<Socket> clients = clientsNeedingConfirmation.get(syncId); | ||
| 246 | if (clients != null) { | ||
| 247 | clients.remove(client); | ||
| 248 | if (clients.isEmpty()) { | ||
| 249 | clientsNeedingConfirmation.remove(syncId); | ||
| 250 | syncIds.remove(inverseSyncIds.remove(syncId)); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) { | ||
| 256 | EntryMapping oldMapping = mappings.getDeobfMapping(entry); | ||
| 257 | String oldName = oldMapping == null ? null : oldMapping.getTargetName(); | ||
| 258 | if (oldName == null) { | ||
| 259 | sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); | ||
| 260 | } else { | ||
| 261 | sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | protected abstract void runOnThread(Runnable task); | ||
| 266 | |||
| 267 | public void log(String message) { | ||
| 268 | System.out.println(message); | ||
| 269 | } | ||
| 270 | |||
| 271 | protected boolean isRunning() { | ||
| 272 | return !socket.isClosed(); | ||
| 273 | } | ||
| 274 | |||
| 275 | public byte[] getJarChecksum() { | ||
| 276 | return jarChecksum; | ||
| 277 | } | ||
| 278 | |||
| 279 | public char[] getPassword() { | ||
| 280 | return password; | ||
| 281 | } | ||
| 282 | |||
| 283 | public EntryRemapper getMappings() { | ||
| 284 | return mappings; | ||
| 285 | } | ||
| 286 | |||
| 287 | public void sendMessage(Message message) { | ||
| 288 | log(String.format("[MSG] %s", message.translate())); | ||
| 289 | sendToAll(new MessageS2CPacket(message)); | ||
| 290 | } | ||
| 291 | |||
| 292 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java deleted file mode 100644 index 21c6825..0000000 --- a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 4 | |||
| 5 | import javax.swing.*; | ||
| 6 | |||
| 7 | public class IntegratedEnigmaServer extends EnigmaServer { | ||
| 8 | public IntegratedEnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { | ||
| 9 | super(jarChecksum, password, mappings, port); | ||
| 10 | } | ||
| 11 | |||
| 12 | @Override | ||
| 13 | protected void runOnThread(Runnable task) { | ||
| 14 | SwingUtilities.invokeLater(task); | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java deleted file mode 100644 index 8618553..0000000 --- a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java +++ /dev/null | |||
| @@ -1,22 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network; | ||
| 2 | |||
| 3 | import java.net.Socket; | ||
| 4 | |||
| 5 | public class ServerPacketHandler { | ||
| 6 | |||
| 7 | private final Socket client; | ||
| 8 | private final EnigmaServer server; | ||
| 9 | |||
| 10 | public ServerPacketHandler(Socket client, EnigmaServer server) { | ||
| 11 | this.client = client; | ||
| 12 | this.server = server; | ||
| 13 | } | ||
| 14 | |||
| 15 | public Socket getClient() { | ||
| 16 | return client; | ||
| 17 | } | ||
| 18 | |||
| 19 | public EnigmaServer getServer() { | ||
| 20 | return server; | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java deleted file mode 100644 index 4d5d86f..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java +++ /dev/null | |||
| @@ -1,59 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.utils.Message; | ||
| 8 | import cuchaz.enigma.utils.Utils; | ||
| 9 | |||
| 10 | import java.io.DataInput; | ||
| 11 | import java.io.DataOutput; | ||
| 12 | import java.io.IOException; | ||
| 13 | |||
| 14 | public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> { | ||
| 15 | private Entry<?> entry; | ||
| 16 | private String newDocs; | ||
| 17 | |||
| 18 | ChangeDocsC2SPacket() { | ||
| 19 | } | ||
| 20 | |||
| 21 | public ChangeDocsC2SPacket(Entry<?> entry, String newDocs) { | ||
| 22 | this.entry = entry; | ||
| 23 | this.newDocs = newDocs; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void read(DataInput input) throws IOException { | ||
| 28 | this.entry = PacketHelper.readEntry(input); | ||
| 29 | this.newDocs = PacketHelper.readString(input); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void write(DataOutput output) throws IOException { | ||
| 34 | PacketHelper.writeEntry(output, entry); | ||
| 35 | PacketHelper.writeString(output, newDocs); | ||
| 36 | } | ||
| 37 | |||
| 38 | @Override | ||
| 39 | public void handle(ServerPacketHandler handler) { | ||
| 40 | EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry); | ||
| 41 | |||
| 42 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 43 | if (!valid) { | ||
| 44 | String oldDocs = mapping == null ? null : mapping.getJavadoc(); | ||
| 45 | handler.getServer().sendPacket(handler.getClient(), new ChangeDocsS2CPacket(EnigmaServer.DUMMY_SYNC_ID, entry, oldDocs == null ? "" : oldDocs)); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | if (mapping == null) { | ||
| 50 | mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()); | ||
| 51 | } | ||
| 52 | handler.getServer().getMappings().mapFromObf(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs)); | ||
| 53 | |||
| 54 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 55 | handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs)); | ||
| 56 | handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry)); | ||
| 57 | } | ||
| 58 | |||
| 59 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java deleted file mode 100644 index bf5b7cb..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class ChangeDocsS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | private String newDocs; | ||
| 15 | |||
| 16 | ChangeDocsS2CPacket() { | ||
| 17 | } | ||
| 18 | |||
| 19 | public ChangeDocsS2CPacket(int syncId, Entry<?> entry, String newDocs) { | ||
| 20 | this.syncId = syncId; | ||
| 21 | this.entry = entry; | ||
| 22 | this.newDocs = newDocs; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void read(DataInput input) throws IOException { | ||
| 27 | this.syncId = input.readUnsignedShort(); | ||
| 28 | this.entry = PacketHelper.readEntry(input); | ||
| 29 | this.newDocs = PacketHelper.readString(input); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void write(DataOutput output) throws IOException { | ||
| 34 | output.writeShort(syncId); | ||
| 35 | PacketHelper.writeEntry(output, entry); | ||
| 36 | PacketHelper.writeString(output, newDocs); | ||
| 37 | } | ||
| 38 | |||
| 39 | @Override | ||
| 40 | public void handle(GuiController controller) { | ||
| 41 | controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false); | ||
| 42 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java deleted file mode 100644 index 78ef964..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | |||
| 5 | import java.io.DataInput; | ||
| 6 | import java.io.DataOutput; | ||
| 7 | import java.io.IOException; | ||
| 8 | |||
| 9 | public class ConfirmChangeC2SPacket implements Packet<ServerPacketHandler> { | ||
| 10 | private int syncId; | ||
| 11 | |||
| 12 | ConfirmChangeC2SPacket() { | ||
| 13 | } | ||
| 14 | |||
| 15 | public ConfirmChangeC2SPacket(int syncId) { | ||
| 16 | this.syncId = syncId; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public void read(DataInput input) throws IOException { | ||
| 21 | this.syncId = input.readUnsignedShort(); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void write(DataOutput output) throws IOException { | ||
| 26 | output.writeShort(syncId); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void handle(ServerPacketHandler handler) { | ||
| 31 | handler.getServer().confirmChange(handler.getClient(), syncId); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java deleted file mode 100644 index bd007d3..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | |||
| 5 | import java.io.DataInput; | ||
| 6 | import java.io.DataOutput; | ||
| 7 | import java.io.IOException; | ||
| 8 | |||
| 9 | public class KickS2CPacket implements Packet<GuiController> { | ||
| 10 | private String reason; | ||
| 11 | |||
| 12 | KickS2CPacket() { | ||
| 13 | } | ||
| 14 | |||
| 15 | public KickS2CPacket(String reason) { | ||
| 16 | this.reason = reason; | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public void read(DataInput input) throws IOException { | ||
| 21 | this.reason = PacketHelper.readString(input); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void write(DataOutput output) throws IOException { | ||
| 26 | PacketHelper.writeString(output, reason); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void handle(GuiController controller) { | ||
| 31 | controller.disconnectIfConnected(reason); | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java deleted file mode 100644 index 722cbbf..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java +++ /dev/null | |||
| @@ -1,75 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.EnigmaServer; | ||
| 4 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 5 | import cuchaz.enigma.utils.Message; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | import java.util.Arrays; | ||
| 11 | |||
| 12 | public class LoginC2SPacket implements Packet<ServerPacketHandler> { | ||
| 13 | private byte[] jarChecksum; | ||
| 14 | private char[] password; | ||
| 15 | private String username; | ||
| 16 | |||
| 17 | LoginC2SPacket() { | ||
| 18 | } | ||
| 19 | |||
| 20 | public LoginC2SPacket(byte[] jarChecksum, char[] password, String username) { | ||
| 21 | this.jarChecksum = jarChecksum; | ||
| 22 | this.password = password; | ||
| 23 | this.username = username; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void read(DataInput input) throws IOException { | ||
| 28 | if (input.readUnsignedShort() != EnigmaServer.PROTOCOL_VERSION) { | ||
| 29 | throw new IOException("Mismatching protocol"); | ||
| 30 | } | ||
| 31 | this.jarChecksum = new byte[EnigmaServer.CHECKSUM_SIZE]; | ||
| 32 | input.readFully(jarChecksum); | ||
| 33 | this.password = new char[input.readUnsignedByte()]; | ||
| 34 | for (int i = 0; i < password.length; i++) { | ||
| 35 | password[i] = input.readChar(); | ||
| 36 | } | ||
| 37 | this.username = PacketHelper.readString(input); | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public void write(DataOutput output) throws IOException { | ||
| 42 | output.writeShort(EnigmaServer.PROTOCOL_VERSION); | ||
| 43 | output.write(jarChecksum); | ||
| 44 | output.writeByte(password.length); | ||
| 45 | for (char c : password) { | ||
| 46 | output.writeChar(c); | ||
| 47 | } | ||
| 48 | PacketHelper.writeString(output, username); | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public void handle(ServerPacketHandler handler) { | ||
| 53 | boolean usernameTaken = handler.getServer().isUsernameTaken(username); | ||
| 54 | handler.getServer().setUsername(handler.getClient(), username); | ||
| 55 | handler.getServer().log(username + " logged in with IP " + handler.getClient().getInetAddress().toString() + ":" + handler.getClient().getPort()); | ||
| 56 | |||
| 57 | if (!Arrays.equals(password, handler.getServer().getPassword())) { | ||
| 58 | handler.getServer().kick(handler.getClient(), "disconnect.wrong_password"); | ||
| 59 | return; | ||
| 60 | } | ||
| 61 | |||
| 62 | if (usernameTaken) { | ||
| 63 | handler.getServer().kick(handler.getClient(), "disconnect.username_taken"); | ||
| 64 | return; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (!Arrays.equals(jarChecksum, handler.getServer().getJarChecksum())) { | ||
| 68 | handler.getServer().kick(handler.getClient(), "disconnect.wrong_jar"); | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | |||
| 72 | handler.getServer().sendPacket(handler.getClient(), new SyncMappingsS2CPacket(handler.getServer().getMappings().getObfToDeobf())); | ||
| 73 | handler.getServer().sendMessage(Message.connect(username)); | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java deleted file mode 100644 index 98d20d9..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java +++ /dev/null | |||
| @@ -1,48 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.utils.Message; | ||
| 7 | |||
| 8 | import java.io.DataInput; | ||
| 9 | import java.io.DataOutput; | ||
| 10 | import java.io.IOException; | ||
| 11 | |||
| 12 | public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> { | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | MarkDeobfuscatedC2SPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public MarkDeobfuscatedC2SPacket(Entry<?> entry) { | ||
| 19 | this.entry = entry; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void read(DataInput input) throws IOException { | ||
| 24 | this.entry = PacketHelper.readEntry(input); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void write(DataOutput output) throws IOException { | ||
| 29 | PacketHelper.writeEntry(output, entry); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void handle(ServerPacketHandler handler) { | ||
| 34 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 35 | if (!valid) { | ||
| 36 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); | ||
| 37 | return; | ||
| 38 | } | ||
| 39 | |||
| 40 | handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); | ||
| 41 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated"); | ||
| 42 | |||
| 43 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 44 | handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry)); | ||
| 45 | handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry)); | ||
| 46 | |||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java deleted file mode 100644 index b7d6eda..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class MarkDeobfuscatedS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | MarkDeobfuscatedS2CPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public MarkDeobfuscatedS2CPacket(int syncId, Entry<?> entry) { | ||
| 19 | this.syncId = syncId; | ||
| 20 | this.entry = entry; | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public void read(DataInput input) throws IOException { | ||
| 25 | this.syncId = input.readUnsignedShort(); | ||
| 26 | this.entry = PacketHelper.readEntry(input); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void write(DataOutput output) throws IOException { | ||
| 31 | output.writeShort(syncId); | ||
| 32 | PacketHelper.writeEntry(output, entry); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void handle(GuiController controller) { | ||
| 37 | controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false); | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java deleted file mode 100644 index b8e0f14..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 7 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 8 | import cuchaz.enigma.utils.Message; | ||
| 9 | |||
| 10 | public class MessageC2SPacket implements Packet<ServerPacketHandler> { | ||
| 11 | |||
| 12 | private String message; | ||
| 13 | |||
| 14 | MessageC2SPacket() { | ||
| 15 | } | ||
| 16 | |||
| 17 | public MessageC2SPacket(String message) { | ||
| 18 | this.message = message; | ||
| 19 | } | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public void read(DataInput input) throws IOException { | ||
| 23 | message = PacketHelper.readString(input); | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void write(DataOutput output) throws IOException { | ||
| 28 | PacketHelper.writeString(output, message); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public void handle(ServerPacketHandler handler) { | ||
| 33 | String message = this.message.trim(); | ||
| 34 | if (!message.isEmpty()) { | ||
| 35 | handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message)); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java deleted file mode 100644 index edeaae0..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java +++ /dev/null | |||
| @@ -1,36 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 7 | import cuchaz.enigma.gui.GuiController; | ||
| 8 | import cuchaz.enigma.utils.Message; | ||
| 9 | |||
| 10 | public class MessageS2CPacket implements Packet<GuiController> { | ||
| 11 | |||
| 12 | private Message message; | ||
| 13 | |||
| 14 | MessageS2CPacket() { | ||
| 15 | } | ||
| 16 | |||
| 17 | public MessageS2CPacket(Message message) { | ||
| 18 | this.message = message; | ||
| 19 | } | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public void read(DataInput input) throws IOException { | ||
| 23 | message = Message.read(input); | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void write(DataOutput output) throws IOException { | ||
| 28 | message.write(output); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public void handle(GuiController handler) { | ||
| 33 | handler.addMessage(message); | ||
| 34 | } | ||
| 35 | |||
| 36 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/Packet.java b/src/main/java/cuchaz/enigma/network/packet/Packet.java deleted file mode 100644 index 2f16dfb..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/Packet.java +++ /dev/null | |||
| @@ -1,15 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 7 | public interface Packet<H> { | ||
| 8 | |||
| 9 | void read(DataInput input) throws IOException; | ||
| 10 | |||
| 11 | void write(DataOutput output) throws IOException; | ||
| 12 | |||
| 13 | void handle(H handler); | ||
| 14 | |||
| 15 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java deleted file mode 100644 index 464606e..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java +++ /dev/null | |||
| @@ -1,135 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 4 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 10 | |||
| 11 | import java.io.DataInput; | ||
| 12 | import java.io.DataOutput; | ||
| 13 | import java.io.IOException; | ||
| 14 | import java.nio.charset.StandardCharsets; | ||
| 15 | |||
| 16 | public class PacketHelper { | ||
| 17 | |||
| 18 | private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; | ||
| 19 | private static final int MAX_STRING_LENGTH = 65535; | ||
| 20 | |||
| 21 | public static Entry<?> readEntry(DataInput input) throws IOException { | ||
| 22 | return readEntry(input, null, true); | ||
| 23 | } | ||
| 24 | |||
| 25 | public static Entry<?> readEntry(DataInput input, Entry<?> parent, boolean includeParent) throws IOException { | ||
| 26 | int type = input.readUnsignedByte(); | ||
| 27 | |||
| 28 | if (includeParent && input.readBoolean()) { | ||
| 29 | parent = readEntry(input, null, true); | ||
| 30 | } | ||
| 31 | |||
| 32 | String name = readString(input); | ||
| 33 | |||
| 34 | String javadocs = null; | ||
| 35 | if (input.readBoolean()) { | ||
| 36 | javadocs = readString(input); | ||
| 37 | } | ||
| 38 | |||
| 39 | switch (type) { | ||
| 40 | case ENTRY_CLASS: { | ||
| 41 | if (parent != null && !(parent instanceof ClassEntry)) { | ||
| 42 | throw new IOException("Class requires class parent"); | ||
| 43 | } | ||
| 44 | return new ClassEntry((ClassEntry) parent, name, javadocs); | ||
| 45 | } | ||
| 46 | case ENTRY_FIELD: { | ||
| 47 | if (!(parent instanceof ClassEntry)) { | ||
| 48 | throw new IOException("Field requires class parent"); | ||
| 49 | } | ||
| 50 | TypeDescriptor desc = new TypeDescriptor(readString(input)); | ||
| 51 | return new FieldEntry((ClassEntry) parent, name, desc, javadocs); | ||
| 52 | } | ||
| 53 | case ENTRY_METHOD: { | ||
| 54 | if (!(parent instanceof ClassEntry)) { | ||
| 55 | throw new IOException("Method requires class parent"); | ||
| 56 | } | ||
| 57 | MethodDescriptor desc = new MethodDescriptor(readString(input)); | ||
| 58 | return new MethodEntry((ClassEntry) parent, name, desc, javadocs); | ||
| 59 | } | ||
| 60 | case ENTRY_LOCAL_VAR: { | ||
| 61 | if (!(parent instanceof MethodEntry)) { | ||
| 62 | throw new IOException("Local variable requires method parent"); | ||
| 63 | } | ||
| 64 | int index = input.readUnsignedShort(); | ||
| 65 | boolean parameter = input.readBoolean(); | ||
| 66 | return new LocalVariableEntry((MethodEntry) parent, index, name, parameter, javadocs); | ||
| 67 | } | ||
| 68 | default: throw new IOException("Received unknown entry type " + type); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | public static void writeEntry(DataOutput output, Entry<?> entry) throws IOException { | ||
| 73 | writeEntry(output, entry, true); | ||
| 74 | } | ||
| 75 | |||
| 76 | public static void writeEntry(DataOutput output, Entry<?> entry, boolean includeParent) throws IOException { | ||
| 77 | // type | ||
| 78 | if (entry instanceof ClassEntry) { | ||
| 79 | output.writeByte(ENTRY_CLASS); | ||
| 80 | } else if (entry instanceof FieldEntry) { | ||
| 81 | output.writeByte(ENTRY_FIELD); | ||
| 82 | } else if (entry instanceof MethodEntry) { | ||
| 83 | output.writeByte(ENTRY_METHOD); | ||
| 84 | } else if (entry instanceof LocalVariableEntry) { | ||
| 85 | output.writeByte(ENTRY_LOCAL_VAR); | ||
| 86 | } else { | ||
| 87 | throw new IOException("Don't know how to serialize entry of type " + entry.getClass().getSimpleName()); | ||
| 88 | } | ||
| 89 | |||
| 90 | // parent | ||
| 91 | if (includeParent) { | ||
| 92 | output.writeBoolean(entry.getParent() != null); | ||
| 93 | if (entry.getParent() != null) { | ||
| 94 | writeEntry(output, entry.getParent(), true); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | // name | ||
| 99 | writeString(output, entry.getName()); | ||
| 100 | |||
| 101 | // javadocs | ||
| 102 | output.writeBoolean(entry.getJavadocs() != null); | ||
| 103 | if (entry.getJavadocs() != null) { | ||
| 104 | writeString(output, entry.getJavadocs()); | ||
| 105 | } | ||
| 106 | |||
| 107 | // type-specific stuff | ||
| 108 | if (entry instanceof FieldEntry) { | ||
| 109 | writeString(output, ((FieldEntry) entry).getDesc().toString()); | ||
| 110 | } else if (entry instanceof MethodEntry) { | ||
| 111 | writeString(output, ((MethodEntry) entry).getDesc().toString()); | ||
| 112 | } else if (entry instanceof LocalVariableEntry) { | ||
| 113 | LocalVariableEntry localVar = (LocalVariableEntry) entry; | ||
| 114 | output.writeShort(localVar.getIndex()); | ||
| 115 | output.writeBoolean(localVar.isArgument()); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | public static String readString(DataInput input) throws IOException { | ||
| 120 | int length = input.readUnsignedShort(); | ||
| 121 | byte[] bytes = new byte[length]; | ||
| 122 | input.readFully(bytes); | ||
| 123 | return new String(bytes, StandardCharsets.UTF_8); | ||
| 124 | } | ||
| 125 | |||
| 126 | public static void writeString(DataOutput output, String str) throws IOException { | ||
| 127 | byte[] bytes = str.getBytes(StandardCharsets.UTF_8); | ||
| 128 | if (bytes.length > MAX_STRING_LENGTH) { | ||
| 129 | throw new IOException("String too long, was " + bytes.length + " bytes, max " + MAX_STRING_LENGTH + " allowed"); | ||
| 130 | } | ||
| 131 | output.writeShort(bytes.length); | ||
| 132 | output.write(bytes); | ||
| 133 | } | ||
| 134 | |||
| 135 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java deleted file mode 100644 index ba5d9de..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java +++ /dev/null | |||
| @@ -1,64 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 5 | |||
| 6 | import java.util.HashMap; | ||
| 7 | import java.util.Map; | ||
| 8 | import java.util.function.Supplier; | ||
| 9 | |||
| 10 | public class PacketRegistry { | ||
| 11 | |||
| 12 | private static final Map<Class<? extends Packet<ServerPacketHandler>>, Integer> c2sPacketIds = new HashMap<>(); | ||
| 13 | private static final Map<Integer, Supplier<? extends Packet<ServerPacketHandler>>> c2sPacketCreators = new HashMap<>(); | ||
| 14 | private static final Map<Class<? extends Packet<GuiController>>, Integer> s2cPacketIds = new HashMap<>(); | ||
| 15 | private static final Map<Integer, Supplier<? extends Packet<GuiController>>> s2cPacketCreators = new HashMap<>(); | ||
| 16 | |||
| 17 | private static <T extends Packet<ServerPacketHandler>> void registerC2S(int id, Class<T> clazz, Supplier<T> creator) { | ||
| 18 | c2sPacketIds.put(clazz, id); | ||
| 19 | c2sPacketCreators.put(id, creator); | ||
| 20 | } | ||
| 21 | |||
| 22 | private static <T extends Packet<GuiController>> void registerS2C(int id, Class<T> clazz, Supplier<T> creator) { | ||
| 23 | s2cPacketIds.put(clazz, id); | ||
| 24 | s2cPacketCreators.put(id, creator); | ||
| 25 | } | ||
| 26 | |||
| 27 | static { | ||
| 28 | registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::new); | ||
| 29 | registerC2S(1, ConfirmChangeC2SPacket.class, ConfirmChangeC2SPacket::new); | ||
| 30 | registerC2S(2, RenameC2SPacket.class, RenameC2SPacket::new); | ||
| 31 | registerC2S(3, RemoveMappingC2SPacket.class, RemoveMappingC2SPacket::new); | ||
| 32 | registerC2S(4, ChangeDocsC2SPacket.class, ChangeDocsC2SPacket::new); | ||
| 33 | registerC2S(5, MarkDeobfuscatedC2SPacket.class, MarkDeobfuscatedC2SPacket::new); | ||
| 34 | registerC2S(6, MessageC2SPacket.class, MessageC2SPacket::new); | ||
| 35 | |||
| 36 | registerS2C(0, KickS2CPacket.class, KickS2CPacket::new); | ||
| 37 | registerS2C(1, SyncMappingsS2CPacket.class, SyncMappingsS2CPacket::new); | ||
| 38 | registerS2C(2, RenameS2CPacket.class, RenameS2CPacket::new); | ||
| 39 | registerS2C(3, RemoveMappingS2CPacket.class, RemoveMappingS2CPacket::new); | ||
| 40 | registerS2C(4, ChangeDocsS2CPacket.class, ChangeDocsS2CPacket::new); | ||
| 41 | registerS2C(5, MarkDeobfuscatedS2CPacket.class, MarkDeobfuscatedS2CPacket::new); | ||
| 42 | registerS2C(6, MessageS2CPacket.class, MessageS2CPacket::new); | ||
| 43 | registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new); | ||
| 44 | } | ||
| 45 | |||
| 46 | public static int getC2SId(Packet<ServerPacketHandler> packet) { | ||
| 47 | return c2sPacketIds.get(packet.getClass()); | ||
| 48 | } | ||
| 49 | |||
| 50 | public static Packet<ServerPacketHandler> createC2SPacket(int id) { | ||
| 51 | Supplier<? extends Packet<ServerPacketHandler>> creator = c2sPacketCreators.get(id); | ||
| 52 | return creator == null ? null : creator.get(); | ||
| 53 | } | ||
| 54 | |||
| 55 | public static int getS2CId(Packet<GuiController> packet) { | ||
| 56 | return s2cPacketIds.get(packet.getClass()); | ||
| 57 | } | ||
| 58 | |||
| 59 | public static Packet<GuiController> createS2CPacket(int id) { | ||
| 60 | Supplier<? extends Packet<GuiController>> creator = s2cPacketCreators.get(id); | ||
| 61 | return creator == null ? null : creator.get(); | ||
| 62 | } | ||
| 63 | |||
| 64 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java deleted file mode 100644 index a3f3d91..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java +++ /dev/null | |||
| @@ -1,55 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.utils.Message; | ||
| 7 | |||
| 8 | import java.io.DataInput; | ||
| 9 | import java.io.DataOutput; | ||
| 10 | import java.io.IOException; | ||
| 11 | |||
| 12 | public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> { | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | RemoveMappingC2SPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public RemoveMappingC2SPacket(Entry<?> entry) { | ||
| 19 | this.entry = entry; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void read(DataInput input) throws IOException { | ||
| 24 | this.entry = PacketHelper.readEntry(input); | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void write(DataOutput output) throws IOException { | ||
| 29 | PacketHelper.writeEntry(output, entry); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void handle(ServerPacketHandler handler) { | ||
| 34 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 35 | |||
| 36 | if (valid) { | ||
| 37 | try { | ||
| 38 | handler.getServer().getMappings().removeByObf(entry); | ||
| 39 | } catch (IllegalNameException e) { | ||
| 40 | valid = false; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | if (!valid) { | ||
| 45 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " removed the mapping for " + entry); | ||
| 50 | |||
| 51 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 52 | handler.getServer().sendToAllExcept(handler.getClient(), new RemoveMappingS2CPacket(syncId, entry)); | ||
| 53 | handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), entry)); | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java deleted file mode 100644 index 7bb1b00..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class RemoveMappingS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | |||
| 15 | RemoveMappingS2CPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public RemoveMappingS2CPacket(int syncId, Entry<?> entry) { | ||
| 19 | this.syncId = syncId; | ||
| 20 | this.entry = entry; | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public void read(DataInput input) throws IOException { | ||
| 25 | this.syncId = input.readUnsignedShort(); | ||
| 26 | this.entry = PacketHelper.readEntry(input); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void write(DataOutput output) throws IOException { | ||
| 31 | output.writeShort(syncId); | ||
| 32 | PacketHelper.writeEntry(output, entry); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void handle(GuiController controller) { | ||
| 37 | controller.removeMapping(new EntryReference<>(entry, entry.getName()), false); | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java deleted file mode 100644 index 03e95d6..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java +++ /dev/null | |||
| @@ -1,64 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 7 | import cuchaz.enigma.utils.Message; | ||
| 8 | |||
| 9 | import java.io.DataInput; | ||
| 10 | import java.io.DataOutput; | ||
| 11 | import java.io.IOException; | ||
| 12 | |||
| 13 | public class RenameC2SPacket implements Packet<ServerPacketHandler> { | ||
| 14 | private Entry<?> entry; | ||
| 15 | private String newName; | ||
| 16 | private boolean refreshClassTree; | ||
| 17 | |||
| 18 | RenameC2SPacket() { | ||
| 19 | } | ||
| 20 | |||
| 21 | public RenameC2SPacket(Entry<?> entry, String newName, boolean refreshClassTree) { | ||
| 22 | this.entry = entry; | ||
| 23 | this.newName = newName; | ||
| 24 | this.refreshClassTree = refreshClassTree; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void read(DataInput input) throws IOException { | ||
| 29 | this.entry = PacketHelper.readEntry(input); | ||
| 30 | this.newName = PacketHelper.readString(input); | ||
| 31 | this.refreshClassTree = input.readBoolean(); | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public void write(DataOutput output) throws IOException { | ||
| 36 | PacketHelper.writeEntry(output, entry); | ||
| 37 | PacketHelper.writeString(output, newName); | ||
| 38 | output.writeBoolean(refreshClassTree); | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public void handle(ServerPacketHandler handler) { | ||
| 43 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | ||
| 44 | |||
| 45 | if (valid) { | ||
| 46 | try { | ||
| 47 | handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName)); | ||
| 48 | } catch (IllegalNameException e) { | ||
| 49 | valid = false; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | if (!valid) { | ||
| 54 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, refreshClassTree); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | |||
| 58 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " renamed " + entry + " to " + newName); | ||
| 59 | |||
| 60 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | ||
| 61 | handler.getServer().sendToAllExcept(handler.getClient(), new RenameS2CPacket(syncId, entry, newName, refreshClassTree)); | ||
| 62 | handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), entry, newName)); | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java deleted file mode 100644 index 058f0e5..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java +++ /dev/null | |||
| @@ -1,48 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.GuiController; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | ||
| 8 | import java.io.DataOutput; | ||
| 9 | import java.io.IOException; | ||
| 10 | |||
| 11 | public class RenameS2CPacket implements Packet<GuiController> { | ||
| 12 | private int syncId; | ||
| 13 | private Entry<?> entry; | ||
| 14 | private String newName; | ||
| 15 | private boolean refreshClassTree; | ||
| 16 | |||
| 17 | RenameS2CPacket() { | ||
| 18 | } | ||
| 19 | |||
| 20 | public RenameS2CPacket(int syncId, Entry<?> entry, String newName, boolean refreshClassTree) { | ||
| 21 | this.syncId = syncId; | ||
| 22 | this.entry = entry; | ||
| 23 | this.newName = newName; | ||
| 24 | this.refreshClassTree = refreshClassTree; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void read(DataInput input) throws IOException { | ||
| 29 | this.syncId = input.readUnsignedShort(); | ||
| 30 | this.entry = PacketHelper.readEntry(input); | ||
| 31 | this.newName = PacketHelper.readString(input); | ||
| 32 | this.refreshClassTree = input.readBoolean(); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public void write(DataOutput output) throws IOException { | ||
| 37 | output.writeShort(syncId); | ||
| 38 | PacketHelper.writeEntry(output, entry); | ||
| 39 | PacketHelper.writeString(output, newName); | ||
| 40 | output.writeBoolean(refreshClassTree); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public void handle(GuiController controller) { | ||
| 45 | controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false); | ||
| 46 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java deleted file mode 100644 index e6378d1..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java +++ /dev/null | |||
| @@ -1,88 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.GuiController; | ||
| 4 | import cuchaz.enigma.network.EnigmaServer; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | |||
| 11 | import java.io.DataInput; | ||
| 12 | import java.io.DataOutput; | ||
| 13 | import java.io.IOException; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.stream.Collectors; | ||
| 17 | |||
| 18 | public class SyncMappingsS2CPacket implements Packet<GuiController> { | ||
| 19 | private EntryTree<EntryMapping> mappings; | ||
| 20 | |||
| 21 | SyncMappingsS2CPacket() { | ||
| 22 | } | ||
| 23 | |||
| 24 | public SyncMappingsS2CPacket(EntryTree<EntryMapping> mappings) { | ||
| 25 | this.mappings = mappings; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void read(DataInput input) throws IOException { | ||
| 30 | mappings = new HashEntryTree<>(); | ||
| 31 | int size = input.readInt(); | ||
| 32 | for (int i = 0; i < size; i++) { | ||
| 33 | readEntryTreeNode(input, null); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | private void readEntryTreeNode(DataInput input, Entry<?> parent) throws IOException { | ||
| 38 | Entry<?> entry = PacketHelper.readEntry(input, parent, false); | ||
| 39 | EntryMapping mapping = null; | ||
| 40 | if (input.readBoolean()) { | ||
| 41 | String name = input.readUTF(); | ||
| 42 | if (input.readBoolean()) { | ||
| 43 | String javadoc = input.readUTF(); | ||
| 44 | mapping = new EntryMapping(name, javadoc); | ||
| 45 | } else { | ||
| 46 | mapping = new EntryMapping(name); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | mappings.insert(entry, mapping); | ||
| 50 | int size = input.readUnsignedShort(); | ||
| 51 | for (int i = 0; i < size; i++) { | ||
| 52 | readEntryTreeNode(input, entry); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public void write(DataOutput output) throws IOException { | ||
| 58 | List<EntryTreeNode<EntryMapping>> roots = mappings.getRootNodes().collect(Collectors.toList()); | ||
| 59 | output.writeInt(roots.size()); | ||
| 60 | for (EntryTreeNode<EntryMapping> node : roots) { | ||
| 61 | writeEntryTreeNode(output, node); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException { | ||
| 66 | PacketHelper.writeEntry(output, node.getEntry(), false); | ||
| 67 | EntryMapping value = node.getValue(); | ||
| 68 | output.writeBoolean(value != null); | ||
| 69 | if (value != null) { | ||
| 70 | PacketHelper.writeString(output, value.getTargetName()); | ||
| 71 | output.writeBoolean(value.getJavadoc() != null); | ||
| 72 | if (value.getJavadoc() != null) { | ||
| 73 | PacketHelper.writeString(output, value.getJavadoc()); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | Collection<? extends EntryTreeNode<EntryMapping>> children = node.getChildNodes(); | ||
| 77 | output.writeShort(children.size()); | ||
| 78 | for (EntryTreeNode<EntryMapping> child : children) { | ||
| 79 | writeEntryTreeNode(output, child); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | @Override | ||
| 84 | public void handle(GuiController controller) { | ||
| 85 | controller.openMappings(mappings); | ||
| 86 | controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID)); | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java deleted file mode 100644 index 8904848..0000000 --- a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | import java.util.ArrayList; | ||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | import cuchaz.enigma.gui.GuiController; | ||
| 10 | |||
| 11 | public class UserListS2CPacket implements Packet<GuiController> { | ||
| 12 | |||
| 13 | private List<String> users; | ||
| 14 | |||
| 15 | UserListS2CPacket() { | ||
| 16 | } | ||
| 17 | |||
| 18 | public UserListS2CPacket(List<String> users) { | ||
| 19 | this.users = users; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public void read(DataInput input) throws IOException { | ||
| 24 | int len = input.readUnsignedShort(); | ||
| 25 | users = new ArrayList<>(len); | ||
| 26 | for (int i = 0; i < len; i++) { | ||
| 27 | users.add(input.readUTF()); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public void write(DataOutput output) throws IOException { | ||
| 33 | output.writeShort(users.size()); | ||
| 34 | for (String user : users) { | ||
| 35 | PacketHelper.writeString(output, user); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | @Override | ||
| 40 | public void handle(GuiController handler) { | ||
| 41 | handler.updateUserList(users); | ||
| 42 | } | ||
| 43 | |||
| 44 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/Decompiler.java b/src/main/java/cuchaz/enigma/source/Decompiler.java deleted file mode 100644 index c9666d5..0000000 --- a/src/main/java/cuchaz/enigma/source/Decompiler.java +++ /dev/null | |||
| @@ -1,5 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | public interface Decompiler { | ||
| 4 | Source getSource(String className); | ||
| 5 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/DecompilerService.java b/src/main/java/cuchaz/enigma/source/DecompilerService.java deleted file mode 100644 index 377ccbc..0000000 --- a/src/main/java/cuchaz/enigma/source/DecompilerService.java +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ClassProvider; | ||
| 4 | import cuchaz.enigma.api.service.EnigmaService; | ||
| 5 | import cuchaz.enigma.api.service.EnigmaServiceType; | ||
| 6 | |||
| 7 | public interface DecompilerService extends EnigmaService { | ||
| 8 | EnigmaServiceType<DecompilerService> TYPE = EnigmaServiceType.create("decompiler"); | ||
| 9 | |||
| 10 | Decompiler create(ClassProvider classProvider, SourceSettings settings); | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/Decompilers.java b/src/main/java/cuchaz/enigma/source/Decompilers.java deleted file mode 100644 index 7d154a6..0000000 --- a/src/main/java/cuchaz/enigma/source/Decompilers.java +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import cuchaz.enigma.source.cfr.CfrDecompiler; | ||
| 4 | import cuchaz.enigma.source.procyon.ProcyonDecompiler; | ||
| 5 | |||
| 6 | public class Decompilers { | ||
| 7 | public static final DecompilerService PROCYON = ProcyonDecompiler::new; | ||
| 8 | public static final DecompilerService CFR = CfrDecompiler::new; | ||
| 9 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/Source.java b/src/main/java/cuchaz/enigma/source/Source.java deleted file mode 100644 index 43c4de0..0000000 --- a/src/main/java/cuchaz/enigma/source/Source.java +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 4 | |||
| 5 | public interface Source { | ||
| 6 | String asString(); | ||
| 7 | |||
| 8 | Source addJavadocs(EntryRemapper remapper); | ||
| 9 | |||
| 10 | SourceIndex index(); | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/SourceIndex.java b/src/main/java/cuchaz/enigma/source/SourceIndex.java deleted file mode 100644 index 6a335ec..0000000 --- a/src/main/java/cuchaz/enigma/source/SourceIndex.java +++ /dev/null | |||
| @@ -1,174 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import com.google.common.collect.HashMultimap; | ||
| 4 | import com.google.common.collect.Lists; | ||
| 5 | import com.google.common.collect.Maps; | ||
| 6 | import com.google.common.collect.Multimap; | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.analysis.Token; | ||
| 9 | import cuchaz.enigma.gui.SourceRemapper; | ||
| 10 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 11 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 13 | |||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.Map; | ||
| 17 | import java.util.TreeMap; | ||
| 18 | import java.util.stream.Collectors; | ||
| 19 | |||
| 20 | public class SourceIndex { | ||
| 21 | private String source; | ||
| 22 | private List<Integer> lineOffsets; | ||
| 23 | private final TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference; | ||
| 24 | private final Multimap<EntryReference<Entry<?>, Entry<?>>, Token> referenceToTokens; | ||
| 25 | private final Map<Entry<?>, Token> declarationToToken; | ||
| 26 | |||
| 27 | public SourceIndex() { | ||
| 28 | tokenToReference = new TreeMap<>(); | ||
| 29 | referenceToTokens = HashMultimap.create(); | ||
| 30 | declarationToToken = Maps.newHashMap(); | ||
| 31 | } | ||
| 32 | |||
| 33 | public SourceIndex(String source) { | ||
| 34 | this(); | ||
| 35 | setSource(source); | ||
| 36 | } | ||
| 37 | |||
| 38 | public void setSource(String source) { | ||
| 39 | this.source = source; | ||
| 40 | lineOffsets = Lists.newArrayList(); | ||
| 41 | lineOffsets.add(0); | ||
| 42 | |||
| 43 | for (int i = 0; i < this.source.length(); i++) { | ||
| 44 | if (this.source.charAt(i) == '\n') { | ||
| 45 | lineOffsets.add(i + 1); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | public String getSource() { | ||
| 51 | return source; | ||
| 52 | } | ||
| 53 | |||
| 54 | public int getLineNumber(int position) { | ||
| 55 | int line = 0; | ||
| 56 | |||
| 57 | for (int offset : lineOffsets) { | ||
| 58 | if (offset > position) { | ||
| 59 | break; | ||
| 60 | } | ||
| 61 | |||
| 62 | line++; | ||
| 63 | } | ||
| 64 | |||
| 65 | return line; | ||
| 66 | } | ||
| 67 | |||
| 68 | public int getColumnNumber(int position) { | ||
| 69 | return position - lineOffsets.get(getLineNumber(position) - 1) + 1; | ||
| 70 | } | ||
| 71 | |||
| 72 | public int getPosition(int line, int column) { | ||
| 73 | return lineOffsets.get(line - 1) + column - 1; | ||
| 74 | } | ||
| 75 | |||
| 76 | public Iterable<Entry<?>> declarations() { | ||
| 77 | return declarationToToken.keySet(); | ||
| 78 | } | ||
| 79 | |||
| 80 | public Iterable<Token> declarationTokens() { | ||
| 81 | return declarationToToken.values(); | ||
| 82 | } | ||
| 83 | |||
| 84 | public Token getDeclarationToken(Entry<?> entry) { | ||
| 85 | return declarationToToken.get(entry); | ||
| 86 | } | ||
| 87 | |||
| 88 | public void addDeclaration(Token token, Entry<?> deobfEntry) { | ||
| 89 | if (token != null) { | ||
| 90 | EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text); | ||
| 91 | tokenToReference.put(token, reference); | ||
| 92 | referenceToTokens.put(reference, token); | ||
| 93 | declarationToToken.put(deobfEntry, token); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | public Iterable<EntryReference<Entry<?>, Entry<?>>> references() { | ||
| 98 | return referenceToTokens.keySet(); | ||
| 99 | } | ||
| 100 | |||
| 101 | public EntryReference<Entry<?>, Entry<?>> getReference(Token token) { | ||
| 102 | if (token == null) { | ||
| 103 | return null; | ||
| 104 | } | ||
| 105 | |||
| 106 | return tokenToReference.get(token); | ||
| 107 | } | ||
| 108 | |||
| 109 | public Iterable<Token> referenceTokens() { | ||
| 110 | return tokenToReference.keySet(); | ||
| 111 | } | ||
| 112 | |||
| 113 | public Token getReferenceToken(int pos) { | ||
| 114 | Token token = tokenToReference.floorKey(new Token(pos, pos, null)); | ||
| 115 | |||
| 116 | if (token != null && token.contains(pos)) { | ||
| 117 | return token; | ||
| 118 | } | ||
| 119 | |||
| 120 | return null; | ||
| 121 | } | ||
| 122 | |||
| 123 | public Collection<Token> getReferenceTokens(EntryReference<Entry<?>, Entry<?>> deobfReference) { | ||
| 124 | return referenceToTokens.get(deobfReference); | ||
| 125 | } | ||
| 126 | |||
| 127 | public void addReference(Token token, Entry<?> deobfEntry, Entry<?> deobfContext) { | ||
| 128 | if (token != null) { | ||
| 129 | EntryReference<Entry<?>, Entry<?>> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); | ||
| 130 | tokenToReference.put(token, deobfReference); | ||
| 131 | referenceToTokens.put(deobfReference, token); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | public void resolveReferences(EntryResolver resolver) { | ||
| 136 | // resolve all the classes in the source references | ||
| 137 | for (Token token : Lists.newArrayList(referenceToTokens.values())) { | ||
| 138 | EntryReference<Entry<?>, Entry<?>> reference = tokenToReference.get(token); | ||
| 139 | EntryReference<Entry<?>, Entry<?>> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); | ||
| 140 | |||
| 141 | // replace the reference | ||
| 142 | tokenToReference.replace(token, resolvedReference); | ||
| 143 | |||
| 144 | Collection<Token> tokens = referenceToTokens.removeAll(reference); | ||
| 145 | referenceToTokens.putAll(resolvedReference, tokens); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | public SourceIndex remapTo(SourceRemapper.Result result) { | ||
| 150 | SourceIndex remapped = new SourceIndex(result.getSource()); | ||
| 151 | |||
| 152 | for (Map.Entry<Entry<?>, Token> entry : declarationToToken.entrySet()) { | ||
| 153 | remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); | ||
| 154 | } | ||
| 155 | |||
| 156 | for (Map.Entry<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> entry : referenceToTokens.asMap().entrySet()) { | ||
| 157 | EntryReference<Entry<?>, Entry<?>> reference = entry.getKey(); | ||
| 158 | Collection<Token> oldTokens = entry.getValue(); | ||
| 159 | |||
| 160 | Collection<Token> newTokens = oldTokens | ||
| 161 | .stream() | ||
| 162 | .map(result::getRemappedToken) | ||
| 163 | .collect(Collectors.toList()); | ||
| 164 | |||
| 165 | remapped.referenceToTokens.putAll(reference, newTokens); | ||
| 166 | } | ||
| 167 | |||
| 168 | for (Map.Entry<Token, EntryReference<Entry<?>, Entry<?>>> entry : tokenToReference.entrySet()) { | ||
| 169 | remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); | ||
| 170 | } | ||
| 171 | |||
| 172 | return remapped; | ||
| 173 | } | ||
| 174 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/SourceSettings.java b/src/main/java/cuchaz/enigma/source/SourceSettings.java deleted file mode 100644 index f6c68e9..0000000 --- a/src/main/java/cuchaz/enigma/source/SourceSettings.java +++ /dev/null | |||
| @@ -1,11 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | public class SourceSettings { | ||
| 4 | public final boolean removeImports; | ||
| 5 | public final boolean removeVariableFinal; | ||
| 6 | |||
| 7 | public SourceSettings(boolean removeImports, boolean removeVariableFinal) { | ||
| 8 | this.removeImports = removeImports; | ||
| 9 | this.removeVariableFinal = removeVariableFinal; | ||
| 10 | } | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java deleted file mode 100644 index 9e37f16..0000000 --- a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java +++ /dev/null | |||
| @@ -1,108 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | ||
| 2 | |||
| 3 | import com.google.common.io.ByteStreams; | ||
| 4 | import cuchaz.enigma.ClassProvider; | ||
| 5 | import cuchaz.enigma.source.Decompiler; | ||
| 6 | import cuchaz.enigma.source.Source; | ||
| 7 | import cuchaz.enigma.source.SourceSettings; | ||
| 8 | import org.benf.cfr.reader.apiunreleased.ClassFileSource2; | ||
| 9 | import org.benf.cfr.reader.apiunreleased.JarContent; | ||
| 10 | import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; | ||
| 11 | import org.benf.cfr.reader.entities.ClassFile; | ||
| 12 | import org.benf.cfr.reader.mapping.MappingFactory; | ||
| 13 | import org.benf.cfr.reader.mapping.ObfuscationMapping; | ||
| 14 | import org.benf.cfr.reader.relationship.MemberNameResolver; | ||
| 15 | import org.benf.cfr.reader.state.DCCommonState; | ||
| 16 | import org.benf.cfr.reader.state.TypeUsageCollectingDumper; | ||
| 17 | import org.benf.cfr.reader.util.AnalysisType; | ||
| 18 | import org.benf.cfr.reader.util.CannotLoadClassException; | ||
| 19 | import org.benf.cfr.reader.util.collections.ListFactory; | ||
| 20 | import org.benf.cfr.reader.util.getopt.Options; | ||
| 21 | import org.benf.cfr.reader.util.getopt.OptionsImpl; | ||
| 22 | import org.objectweb.asm.ClassWriter; | ||
| 23 | import org.objectweb.asm.tree.ClassNode; | ||
| 24 | |||
| 25 | import java.io.IOException; | ||
| 26 | import java.io.InputStream; | ||
| 27 | import java.util.Collection; | ||
| 28 | import java.util.HashMap; | ||
| 29 | import java.util.Map; | ||
| 30 | |||
| 31 | |||
| 32 | public class CfrDecompiler implements Decompiler { | ||
| 33 | private final DCCommonState state; | ||
| 34 | |||
| 35 | public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { | ||
| 36 | Map<String, String> options = new HashMap<>(); | ||
| 37 | |||
| 38 | state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() { | ||
| 39 | @Override | ||
| 40 | public JarContent addJarContent(String s, AnalysisType analysisType) { | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | |||
| 44 | @Override | ||
| 45 | public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { | ||
| 46 | |||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | public Collection<String> addJar(String jarPath) { | ||
| 51 | return null; | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public String getPossiblyRenamedPath(String path) { | ||
| 56 | return path; | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public Pair<byte[], String> getClassFileContent(String path) { | ||
| 61 | ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); | ||
| 62 | |||
| 63 | if (node == null) { | ||
| 64 | try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { | ||
| 65 | if (classResource != null) { | ||
| 66 | return new Pair<>(ByteStreams.toByteArray(classResource), path); | ||
| 67 | } | ||
| 68 | } catch (IOException ignored) {} | ||
| 69 | |||
| 70 | return null; | ||
| 71 | } | ||
| 72 | |||
| 73 | ClassWriter cw = new ClassWriter(0); | ||
| 74 | node.accept(cw); | ||
| 75 | return new Pair<>(cw.toByteArray(), path); | ||
| 76 | } | ||
| 77 | }); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Override | ||
| 81 | public Source getSource(String className) { | ||
| 82 | DCCommonState state = this.state; | ||
| 83 | Options options = state.getOptions(); | ||
| 84 | |||
| 85 | ObfuscationMapping mapping = MappingFactory.get(options, state); | ||
| 86 | state = new DCCommonState(state, mapping); | ||
| 87 | ClassFile tree = state.getClassFileMaybePath(className); | ||
| 88 | |||
| 89 | state.configureWith(tree); | ||
| 90 | |||
| 91 | // To make sure we're analysing the cached version | ||
| 92 | try { | ||
| 93 | tree = state.getClassFile(tree.getClassType()); | ||
| 94 | } catch (CannotLoadClassException ignored) {} | ||
| 95 | |||
| 96 | if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) { | ||
| 97 | tree.loadInnerClasses(state); | ||
| 98 | } | ||
| 99 | |||
| 100 | if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) { | ||
| 101 | MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes())); | ||
| 102 | } | ||
| 103 | |||
| 104 | TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); | ||
| 105 | tree.analyseTop(state, typeUsageCollector); | ||
| 106 | return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation()); | ||
| 107 | } | ||
| 108 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java deleted file mode 100644 index d4f2da6..0000000 --- a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | ||
| 2 | |||
| 3 | import cuchaz.enigma.source.Source; | ||
| 4 | import cuchaz.enigma.source.SourceIndex; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 6 | import org.benf.cfr.reader.entities.ClassFile; | ||
| 7 | import org.benf.cfr.reader.state.DCCommonState; | ||
| 8 | import org.benf.cfr.reader.state.TypeUsageInformation; | ||
| 9 | |||
| 10 | public class CfrSource implements Source { | ||
| 11 | private final ClassFile tree; | ||
| 12 | private final SourceIndex index; | ||
| 13 | private final String string; | ||
| 14 | |||
| 15 | public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) { | ||
| 16 | this.tree = tree; | ||
| 17 | |||
| 18 | EnigmaDumper dumper = new EnigmaDumper(typeUsages); | ||
| 19 | tree.dump(state.getObfuscationMapping().wrap(dumper)); | ||
| 20 | index = dumper.getIndex(); | ||
| 21 | string = dumper.getString(); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public String asString() { | ||
| 26 | return string; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public Source addJavadocs(EntryRemapper remapper) { | ||
| 31 | return this; // TODO | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public SourceIndex index() { | ||
| 36 | return index; | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java deleted file mode 100644 index 09e0a9b..0000000 --- a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java +++ /dev/null | |||
| @@ -1,433 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.Token; | ||
| 4 | import cuchaz.enigma.source.SourceIndex; | ||
| 5 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 6 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 8 | import org.benf.cfr.reader.bytecode.analysis.types.*; | ||
| 9 | import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable; | ||
| 10 | import org.benf.cfr.reader.entities.Field; | ||
| 11 | import org.benf.cfr.reader.entities.Method; | ||
| 12 | import org.benf.cfr.reader.mapping.NullMapping; | ||
| 13 | import org.benf.cfr.reader.mapping.ObfuscationMapping; | ||
| 14 | import org.benf.cfr.reader.state.TypeUsageInformation; | ||
| 15 | import org.benf.cfr.reader.util.collections.SetFactory; | ||
| 16 | import org.benf.cfr.reader.util.output.DelegatingDumper; | ||
| 17 | import org.benf.cfr.reader.util.output.Dumpable; | ||
| 18 | import org.benf.cfr.reader.util.output.Dumper; | ||
| 19 | import org.benf.cfr.reader.util.output.TypeContext; | ||
| 20 | |||
| 21 | import java.util.Set; | ||
| 22 | import java.util.stream.Collectors; | ||
| 23 | |||
| 24 | public class EnigmaDumper implements Dumper { | ||
| 25 | private int outputCount = 0; | ||
| 26 | private int indent; | ||
| 27 | private boolean atStart = true; | ||
| 28 | private boolean pendingCR = false; | ||
| 29 | private final StringBuilder sb = new StringBuilder(); | ||
| 30 | private final TypeUsageInformation typeUsageInformation; | ||
| 31 | private final Set<JavaTypeInstance> emitted = SetFactory.newSet(); | ||
| 32 | private final SourceIndex index = new SourceIndex(); | ||
| 33 | private int position; | ||
| 34 | |||
| 35 | |||
| 36 | public EnigmaDumper(TypeUsageInformation typeUsageInformation) { | ||
| 37 | this.typeUsageInformation = typeUsageInformation; | ||
| 38 | } | ||
| 39 | |||
| 40 | private void append(String s) { | ||
| 41 | sb.append(s); | ||
| 42 | position += s.length(); | ||
| 43 | } | ||
| 44 | |||
| 45 | private String getDesc(JavaTypeInstance type) { | ||
| 46 | if (!type.isUsableType() && type != RawJavaType.VOID) { | ||
| 47 | throw new IllegalArgumentException(type.toString()); | ||
| 48 | } | ||
| 49 | |||
| 50 | if (type instanceof JavaGenericBaseInstance) { | ||
| 51 | return getDesc(type.getDeGenerifiedType()); | ||
| 52 | } | ||
| 53 | |||
| 54 | if (type instanceof JavaRefTypeInstance) { | ||
| 55 | return "L" + type.getRawName().replace('.', '/') + ";"; | ||
| 56 | } | ||
| 57 | |||
| 58 | if (type instanceof JavaArrayTypeInstance) { | ||
| 59 | return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection()); | ||
| 60 | } | ||
| 61 | |||
| 62 | if (type instanceof RawJavaType) { | ||
| 63 | switch ((RawJavaType) type) { | ||
| 64 | case BOOLEAN: | ||
| 65 | return "Z"; | ||
| 66 | case BYTE: | ||
| 67 | return "B"; | ||
| 68 | case CHAR: | ||
| 69 | return "C"; | ||
| 70 | case SHORT: | ||
| 71 | return "S"; | ||
| 72 | case INT: | ||
| 73 | return "I"; | ||
| 74 | case LONG: | ||
| 75 | return "J"; | ||
| 76 | case FLOAT: | ||
| 77 | return "F"; | ||
| 78 | case DOUBLE: | ||
| 79 | return "D"; | ||
| 80 | case VOID: | ||
| 81 | return "V"; | ||
| 82 | default: | ||
| 83 | throw new AssertionError(); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | throw new AssertionError(); | ||
| 88 | } | ||
| 89 | |||
| 90 | private MethodEntry getMethodEntry(MethodPrototype method) { | ||
| 91 | if (method == null || method.getClassType() == null) { | ||
| 92 | return null; | ||
| 93 | } | ||
| 94 | |||
| 95 | MethodDescriptor desc = new MethodDescriptor( | ||
| 96 | method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()), | ||
| 97 | new TypeDescriptor(method.getName().equals("<init>") || method.getName().equals("<clinit>") ? "V" : getDesc(method.getReturnType())) | ||
| 98 | ); | ||
| 99 | |||
| 100 | return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc); | ||
| 101 | } | ||
| 102 | |||
| 103 | private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) { | ||
| 104 | int variableIndex = method.isInstanceMethod() ? 1 : 0; | ||
| 105 | for (int i = 0; i < parameterIndex; i++) { | ||
| 106 | variableIndex += method.getArgs().get(i).getStackType().getComputationCategory(); | ||
| 107 | } | ||
| 108 | |||
| 109 | return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null); | ||
| 110 | } | ||
| 111 | |||
| 112 | private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) { | ||
| 113 | return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type))); | ||
| 114 | } | ||
| 115 | |||
| 116 | private ClassEntry getClassEntry(JavaTypeInstance type) { | ||
| 117 | return new ClassEntry(type.getRawName().replace('.', '/')); | ||
| 118 | } | ||
| 119 | |||
| 120 | @Override | ||
| 121 | public Dumper beginBlockComment(boolean inline) { | ||
| 122 | print("/*").newln(); | ||
| 123 | return this; | ||
| 124 | } | ||
| 125 | |||
| 126 | @Override | ||
| 127 | public Dumper endBlockComment() { | ||
| 128 | print(" */").newln(); | ||
| 129 | return this; | ||
| 130 | } | ||
| 131 | |||
| 132 | @Override | ||
| 133 | public Dumper label(String s, boolean inline) { | ||
| 134 | processPendingCR(); | ||
| 135 | append(s); | ||
| 136 | append(":"); | ||
| 137 | return this; | ||
| 138 | } | ||
| 139 | |||
| 140 | @Override | ||
| 141 | public Dumper comment(String s) { | ||
| 142 | append("// "); | ||
| 143 | append(s); | ||
| 144 | append("\n"); | ||
| 145 | return this; | ||
| 146 | } | ||
| 147 | |||
| 148 | @Override | ||
| 149 | public void enqueuePendingCarriageReturn() { | ||
| 150 | pendingCR = true; | ||
| 151 | } | ||
| 152 | |||
| 153 | @Override | ||
| 154 | public Dumper removePendingCarriageReturn() { | ||
| 155 | pendingCR = false; | ||
| 156 | return this; | ||
| 157 | } | ||
| 158 | |||
| 159 | private void processPendingCR() { | ||
| 160 | if (pendingCR) { | ||
| 161 | append("\n"); | ||
| 162 | atStart = true; | ||
| 163 | pendingCR = false; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | @Override | ||
| 168 | public Dumper identifier(String s, Object ref, boolean defines) { | ||
| 169 | return print(s); | ||
| 170 | } | ||
| 171 | |||
| 172 | @Override | ||
| 173 | public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) { | ||
| 174 | doIndent(); | ||
| 175 | Token token = new Token(position, position + name.length(), name); | ||
| 176 | Entry<?> entry = getMethodEntry(method); | ||
| 177 | |||
| 178 | if (entry != null) { | ||
| 179 | if (defines) { | ||
| 180 | index.addDeclaration(token, entry); | ||
| 181 | } else { | ||
| 182 | index.addReference(token, entry, null); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | return identifier(name, null, defines); | ||
| 187 | } | ||
| 188 | |||
| 189 | @Override | ||
| 190 | public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) { | ||
| 191 | doIndent(); | ||
| 192 | Token token = new Token(position, position + name.length(), name); | ||
| 193 | Entry<?> entry = getParameterEntry(method, index, name); | ||
| 194 | |||
| 195 | if (entry != null) { | ||
| 196 | if (defines) { | ||
| 197 | this.index.addDeclaration(token, entry); | ||
| 198 | } else { | ||
| 199 | this.index.addReference(token, entry, null); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | return identifier(name, null, defines); | ||
| 204 | } | ||
| 205 | |||
| 206 | @Override | ||
| 207 | public Dumper variableName(String name, NamedVariable variable, boolean defines) { | ||
| 208 | return identifier(name, null, defines); | ||
| 209 | } | ||
| 210 | |||
| 211 | @Override | ||
| 212 | public Dumper packageName(JavaRefTypeInstance t) { | ||
| 213 | String s = t.getPackageName(); | ||
| 214 | |||
| 215 | if (!s.isEmpty()) { | ||
| 216 | keyword("package ").print(s).endCodeln().newln(); | ||
| 217 | } | ||
| 218 | |||
| 219 | return this; | ||
| 220 | } | ||
| 221 | |||
| 222 | @Override | ||
| 223 | public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) { | ||
| 224 | doIndent(); | ||
| 225 | Token token = new Token(position, position + name.length(), name); | ||
| 226 | Entry<?> entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance()); | ||
| 227 | |||
| 228 | if (entry != null) { | ||
| 229 | if (defines) { | ||
| 230 | index.addDeclaration(token, entry); | ||
| 231 | } else { | ||
| 232 | index.addReference(token, entry, null); | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | identifier(name, null, defines); | ||
| 237 | return this; | ||
| 238 | } | ||
| 239 | |||
| 240 | @Override | ||
| 241 | public Dumper print(String s) { | ||
| 242 | processPendingCR(); | ||
| 243 | doIndent(); | ||
| 244 | append(s); | ||
| 245 | atStart = s.endsWith("\n"); | ||
| 246 | outputCount++; | ||
| 247 | return this; | ||
| 248 | } | ||
| 249 | |||
| 250 | @Override | ||
| 251 | public Dumper print(char c) { | ||
| 252 | return print(String.valueOf(c)); | ||
| 253 | } | ||
| 254 | |||
| 255 | @Override | ||
| 256 | public Dumper newln() { | ||
| 257 | append("\n"); | ||
| 258 | atStart = true; | ||
| 259 | outputCount++; | ||
| 260 | return this; | ||
| 261 | } | ||
| 262 | |||
| 263 | @Override | ||
| 264 | public Dumper endCodeln() { | ||
| 265 | append(";\n"); | ||
| 266 | atStart = true; | ||
| 267 | outputCount++; | ||
| 268 | return this; | ||
| 269 | } | ||
| 270 | |||
| 271 | @Override | ||
| 272 | public Dumper keyword(String s) { | ||
| 273 | print(s); | ||
| 274 | return this; | ||
| 275 | } | ||
| 276 | |||
| 277 | @Override | ||
| 278 | public Dumper operator(String s) { | ||
| 279 | print(s); | ||
| 280 | return this; | ||
| 281 | } | ||
| 282 | |||
| 283 | @Override | ||
| 284 | public Dumper separator(String s) { | ||
| 285 | print(s); | ||
| 286 | return this; | ||
| 287 | } | ||
| 288 | |||
| 289 | @Override | ||
| 290 | public Dumper literal(String s, Object o) { | ||
| 291 | print(s); | ||
| 292 | return this; | ||
| 293 | } | ||
| 294 | |||
| 295 | private void doIndent() { | ||
| 296 | if (!atStart) return; | ||
| 297 | String indents = " "; | ||
| 298 | |||
| 299 | for (int x = 0; x < indent; ++x) { | ||
| 300 | append(indents); | ||
| 301 | } | ||
| 302 | |||
| 303 | atStart = false; | ||
| 304 | } | ||
| 305 | |||
| 306 | @Override | ||
| 307 | public void indent(int diff) { | ||
| 308 | indent += diff; | ||
| 309 | } | ||
| 310 | |||
| 311 | @Override | ||
| 312 | public Dumper dump(Dumpable d) { | ||
| 313 | if (d == null) { | ||
| 314 | keyword("null"); | ||
| 315 | return this; | ||
| 316 | } | ||
| 317 | |||
| 318 | d.dump(this); | ||
| 319 | return this; | ||
| 320 | } | ||
| 321 | |||
| 322 | @Override | ||
| 323 | public TypeUsageInformation getTypeUsageInformation() { | ||
| 324 | return typeUsageInformation; | ||
| 325 | } | ||
| 326 | |||
| 327 | @Override | ||
| 328 | public ObfuscationMapping getObfuscationMapping() { | ||
| 329 | return NullMapping.INSTANCE; | ||
| 330 | } | ||
| 331 | |||
| 332 | @Override | ||
| 333 | public String toString() { | ||
| 334 | return sb.toString(); | ||
| 335 | } | ||
| 336 | |||
| 337 | @Override | ||
| 338 | public void addSummaryError(Method method, String s) {} | ||
| 339 | |||
| 340 | @Override | ||
| 341 | public void close() { | ||
| 342 | } | ||
| 343 | |||
| 344 | @Override | ||
| 345 | public boolean canEmitClass(JavaTypeInstance type) { | ||
| 346 | return emitted.add(type); | ||
| 347 | } | ||
| 348 | |||
| 349 | @Override | ||
| 350 | public int getOutputCount() { | ||
| 351 | return outputCount; | ||
| 352 | } | ||
| 353 | |||
| 354 | @Override | ||
| 355 | public Dumper dump(JavaTypeInstance type) { | ||
| 356 | return dump(type, TypeContext.None, false); | ||
| 357 | } | ||
| 358 | |||
| 359 | @Override | ||
| 360 | public Dumper dump(JavaTypeInstance type, boolean defines) { | ||
| 361 | return dump(type, TypeContext.None, false); | ||
| 362 | } | ||
| 363 | |||
| 364 | @Override | ||
| 365 | public Dumper dump(JavaTypeInstance type, TypeContext context) { | ||
| 366 | return dump(type, context, false); | ||
| 367 | } | ||
| 368 | |||
| 369 | private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) { | ||
| 370 | doIndent(); | ||
| 371 | if (type instanceof JavaRefTypeInstance) { | ||
| 372 | int start = position; | ||
| 373 | type.dumpInto(this, typeUsageInformation, TypeContext.None); | ||
| 374 | int end = position; | ||
| 375 | Token token = new Token(start, end, sb.toString().substring(start, end)); | ||
| 376 | |||
| 377 | if (defines) { | ||
| 378 | index.addDeclaration(token, getClassEntry(type)); | ||
| 379 | } else { | ||
| 380 | index.addReference(token, getClassEntry(type), null); | ||
| 381 | } | ||
| 382 | |||
| 383 | return this; | ||
| 384 | } | ||
| 385 | |||
| 386 | type.dumpInto(this, typeUsageInformation, context); | ||
| 387 | return this; | ||
| 388 | } | ||
| 389 | |||
| 390 | @Override | ||
| 391 | public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { | ||
| 392 | return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation); | ||
| 393 | } | ||
| 394 | |||
| 395 | public SourceIndex getIndex() { | ||
| 396 | index.setSource(getString()); | ||
| 397 | return index; | ||
| 398 | } | ||
| 399 | |||
| 400 | public String getString() { | ||
| 401 | return sb.toString(); | ||
| 402 | } | ||
| 403 | |||
| 404 | public static class WithTypeUsageInformationDumper extends DelegatingDumper { | ||
| 405 | private final TypeUsageInformation typeUsageInformation; | ||
| 406 | |||
| 407 | WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) { | ||
| 408 | super(delegate); | ||
| 409 | this.typeUsageInformation = typeUsageInformation; | ||
| 410 | } | ||
| 411 | |||
| 412 | @Override | ||
| 413 | public TypeUsageInformation getTypeUsageInformation() { | ||
| 414 | return typeUsageInformation; | ||
| 415 | } | ||
| 416 | |||
| 417 | @Override | ||
| 418 | public Dumper dump(JavaTypeInstance javaTypeInstance) { | ||
| 419 | return dump(javaTypeInstance, TypeContext.None); | ||
| 420 | } | ||
| 421 | |||
| 422 | @Override | ||
| 423 | public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) { | ||
| 424 | javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext); | ||
| 425 | return this; | ||
| 426 | } | ||
| 427 | |||
| 428 | @Override | ||
| 429 | public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { | ||
| 430 | return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation); | ||
| 431 | } | ||
| 432 | } | ||
| 433 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java deleted file mode 100644 index 2fae61a..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java +++ /dev/null | |||
| @@ -1,49 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 4 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 5 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 6 | import com.strobel.assembler.metadata.TypeReference; | ||
| 7 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 8 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 9 | import cuchaz.enigma.translation.representation.Signature; | ||
| 10 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.FieldDefEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 15 | |||
| 16 | public class EntryParser { | ||
| 17 | public static FieldDefEntry parse(FieldDefinition definition) { | ||
| 18 | ClassEntry owner = parse(definition.getDeclaringType()); | ||
| 19 | TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); | ||
| 20 | Signature signature = Signature.createTypedSignature(definition.getSignature()); | ||
| 21 | AccessFlags access = new AccessFlags(definition.getModifiers()); | ||
| 22 | return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); | ||
| 23 | } | ||
| 24 | |||
| 25 | public static ClassDefEntry parse(TypeDefinition def) { | ||
| 26 | String name = def.getInternalName(); | ||
| 27 | Signature signature = Signature.createSignature(def.getSignature()); | ||
| 28 | AccessFlags access = new AccessFlags(def.getModifiers()); | ||
| 29 | ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null; | ||
| 30 | ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new); | ||
| 31 | return new ClassDefEntry(name, signature, access, superClass, interfaces); | ||
| 32 | } | ||
| 33 | |||
| 34 | public static ClassEntry parse(TypeReference typeReference) { | ||
| 35 | return new ClassEntry(typeReference.getInternalName()); | ||
| 36 | } | ||
| 37 | |||
| 38 | public static MethodDefEntry parse(MethodDefinition definition) { | ||
| 39 | ClassEntry classEntry = parse(definition.getDeclaringType()); | ||
| 40 | MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); | ||
| 41 | Signature signature = Signature.createSignature(definition.getSignature()); | ||
| 42 | AccessFlags access = new AccessFlags(definition.getModifiers()); | ||
| 43 | return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); | ||
| 44 | } | ||
| 45 | |||
| 46 | public static TypeDescriptor parseTypeDescriptor(TypeReference type) { | ||
| 47 | return new TypeDescriptor(type.getErasedSignature()); | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java deleted file mode 100644 index 37bc0c8..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java +++ /dev/null | |||
| @@ -1,81 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 4 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 5 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 6 | import com.strobel.assembler.metadata.TypeReference; | ||
| 7 | import com.strobel.decompiler.DecompilerContext; | ||
| 8 | import com.strobel.decompiler.DecompilerSettings; | ||
| 9 | import com.strobel.decompiler.languages.java.BraceStyle; | ||
| 10 | import com.strobel.decompiler.languages.java.JavaFormattingOptions; | ||
| 11 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 12 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 13 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | ||
| 14 | import cuchaz.enigma.ClassProvider; | ||
| 15 | import cuchaz.enigma.api.EnigmaPluginContext; | ||
| 16 | import cuchaz.enigma.source.Source; | ||
| 17 | import cuchaz.enigma.source.Decompiler; | ||
| 18 | import cuchaz.enigma.source.SourceSettings; | ||
| 19 | import cuchaz.enigma.source.procyon.transformers.*; | ||
| 20 | import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; | ||
| 21 | import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; | ||
| 22 | import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; | ||
| 23 | import cuchaz.enigma.utils.Utils; | ||
| 24 | |||
| 25 | public class ProcyonDecompiler implements Decompiler { | ||
| 26 | private final SourceSettings settings; | ||
| 27 | private final DecompilerSettings decompilerSettings; | ||
| 28 | private final MetadataSystem metadataSystem; | ||
| 29 | |||
| 30 | public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { | ||
| 31 | ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); | ||
| 32 | |||
| 33 | metadataSystem = new NoRetryMetadataSystem(typeLoader); | ||
| 34 | metadataSystem.setEagerMethodLoadingEnabled(true); | ||
| 35 | |||
| 36 | decompilerSettings = DecompilerSettings.javaDefaults(); | ||
| 37 | decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); | ||
| 38 | decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); | ||
| 39 | decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); | ||
| 40 | decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); | ||
| 41 | decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); | ||
| 42 | decompilerSettings.setTypeLoader(typeLoader); | ||
| 43 | |||
| 44 | JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions(); | ||
| 45 | formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; | ||
| 46 | formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; | ||
| 47 | formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; | ||
| 48 | |||
| 49 | this.settings = settings; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public Source getSource(String className) { | ||
| 54 | TypeReference type = metadataSystem.lookupType(className); | ||
| 55 | if (type == null) { | ||
| 56 | throw new Error(String.format("Unable to find desc: %s", className)); | ||
| 57 | } | ||
| 58 | |||
| 59 | TypeDefinition resolvedType = type.resolve(); | ||
| 60 | |||
| 61 | DecompilerContext context = new DecompilerContext(); | ||
| 62 | context.setCurrentType(resolvedType); | ||
| 63 | context.setSettings(decompilerSettings); | ||
| 64 | |||
| 65 | AstBuilder builder = new AstBuilder(context); | ||
| 66 | builder.addType(resolvedType); | ||
| 67 | builder.runTransformations(null); | ||
| 68 | CompilationUnit source = builder.getCompilationUnit(); | ||
| 69 | |||
| 70 | new ObfuscatedEnumSwitchRewriterTransform(context).run(source); | ||
| 71 | new VarargsFixer(context).run(source); | ||
| 72 | new RemoveObjectCasts(context).run(source); | ||
| 73 | new Java8Generics().run(source); | ||
| 74 | new InvalidIdentifierFix().run(source); | ||
| 75 | if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source); | ||
| 76 | if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source); | ||
| 77 | source.acceptVisitor(new InsertParenthesesVisitor(), null); | ||
| 78 | |||
| 79 | return new ProcyonSource(source, decompilerSettings); | ||
| 80 | } | ||
| 81 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java deleted file mode 100644 index 53c8c70..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java +++ /dev/null | |||
| @@ -1,49 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.DecompilerSettings; | ||
| 4 | import com.strobel.decompiler.PlainTextOutput; | ||
| 5 | import com.strobel.decompiler.languages.java.JavaOutputVisitor; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 7 | import cuchaz.enigma.source.Source; | ||
| 8 | import cuchaz.enigma.source.SourceIndex; | ||
| 9 | import cuchaz.enigma.source.procyon.index.SourceIndexVisitor; | ||
| 10 | import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform; | ||
| 11 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 12 | |||
| 13 | import java.io.StringWriter; | ||
| 14 | |||
| 15 | public class ProcyonSource implements Source { | ||
| 16 | private final DecompilerSettings settings; | ||
| 17 | private final CompilationUnit tree; | ||
| 18 | private String string; | ||
| 19 | |||
| 20 | public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) { | ||
| 21 | this.settings = settings; | ||
| 22 | this.tree = tree; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public SourceIndex index() { | ||
| 27 | SourceIndex index = new SourceIndex(asString()); | ||
| 28 | tree.acceptVisitor(new SourceIndexVisitor(), index); | ||
| 29 | return index; | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public String asString() { | ||
| 34 | if (string == null) { | ||
| 35 | StringWriter writer = new StringWriter(); | ||
| 36 | tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); | ||
| 37 | string = writer.toString(); | ||
| 38 | } | ||
| 39 | |||
| 40 | return string; | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public Source addJavadocs(EntryRemapper remapper) { | ||
| 45 | CompilationUnit remappedTree = (CompilationUnit) tree.clone(); | ||
| 46 | new AddJavadocsAstTransform(remapper).run(remappedTree); | ||
| 47 | return new ProcyonSource(remappedTree, settings); | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java deleted file mode 100644 index f6eeb15..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java +++ /dev/null | |||
| @@ -1,95 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.source.procyon.index; | ||
| 13 | |||
| 14 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 15 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 16 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 17 | import com.strobel.assembler.metadata.TypeReference; | ||
| 18 | import com.strobel.decompiler.languages.TextLocation; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 20 | import cuchaz.enigma.source.SourceIndex; | ||
| 21 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 23 | |||
| 24 | public class SourceIndexClassVisitor extends SourceIndexVisitor { | ||
| 25 | private ClassDefEntry classEntry; | ||
| 26 | |||
| 27 | public SourceIndexClassVisitor(ClassDefEntry classEntry) { | ||
| 28 | this.classEntry = classEntry; | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { | ||
| 33 | // is this this class, or a subtype? | ||
| 34 | TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); | ||
| 35 | ClassDefEntry classEntry = EntryParser.parse(def); | ||
| 36 | if (!classEntry.equals(this.classEntry)) { | ||
| 37 | // it's a subtype, recurse | ||
| 38 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); | ||
| 39 | return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); | ||
| 40 | } | ||
| 41 | |||
| 42 | return visitChildren(node, index); | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 47 | TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); | ||
| 48 | if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { | ||
| 49 | ClassEntry classEntry = new ClassEntry(ref.getInternalName()); | ||
| 50 | index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry); | ||
| 51 | } | ||
| 52 | |||
| 53 | return visitChildren(node, index); | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { | ||
| 58 | MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); | ||
| 59 | MethodDefEntry methodEntry = EntryParser.parse(def); | ||
| 60 | AstNode tokenNode = node.getNameToken(); | ||
| 61 | if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) { | ||
| 62 | // for static initializers, check elsewhere for the token node | ||
| 63 | tokenNode = node.getModifiers().firstOrNullObject(); | ||
| 64 | } | ||
| 65 | index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry); | ||
| 66 | return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); | ||
| 67 | } | ||
| 68 | |||
| 69 | @Override | ||
| 70 | public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { | ||
| 71 | MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); | ||
| 72 | MethodDefEntry methodEntry = EntryParser.parse(def); | ||
| 73 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry); | ||
| 74 | return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); | ||
| 75 | } | ||
| 76 | |||
| 77 | @Override | ||
| 78 | public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { | ||
| 79 | FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); | ||
| 80 | FieldDefEntry fieldEntry = EntryParser.parse(def); | ||
| 81 | assert (node.getVariables().size() == 1); | ||
| 82 | VariableInitializer variable = node.getVariables().firstOrNullObject(); | ||
| 83 | index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry); | ||
| 84 | return visitChildren(node, index); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { | ||
| 89 | // treat enum declarations as field declarations | ||
| 90 | FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); | ||
| 91 | FieldDefEntry fieldEntry = EntryParser.parse(def); | ||
| 92 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry); | ||
| 93 | return visitChildren(node, index); | ||
| 94 | } | ||
| 95 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java deleted file mode 100644 index 0e8bc51..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java +++ /dev/null | |||
| @@ -1,218 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.source.procyon.index; | ||
| 13 | |||
| 14 | import com.google.common.collect.HashMultimap; | ||
| 15 | import com.google.common.collect.Multimap; | ||
| 16 | import com.strobel.assembler.metadata.*; | ||
| 17 | import com.strobel.decompiler.ast.Variable; | ||
| 18 | import com.strobel.decompiler.languages.TextLocation; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 20 | import cuchaz.enigma.source.SourceIndex; | ||
| 21 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 22 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 23 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 24 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 25 | |||
| 26 | import java.lang.Error; | ||
| 27 | import java.util.HashMap; | ||
| 28 | import java.util.Map; | ||
| 29 | |||
| 30 | public class SourceIndexMethodVisitor extends SourceIndexVisitor { | ||
| 31 | private final MethodDefEntry methodEntry; | ||
| 32 | |||
| 33 | private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create(); | ||
| 34 | private Map<String, Entry<?>> identifierEntryCache = new HashMap<>(); | ||
| 35 | |||
| 36 | public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { | ||
| 37 | this.methodEntry = methodEntry; | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { | ||
| 42 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 43 | |||
| 44 | // get the behavior entry | ||
| 45 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 46 | MethodEntry methodEntry = null; | ||
| 47 | if (ref instanceof MethodReference) { | ||
| 48 | methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); | ||
| 49 | } | ||
| 50 | if (methodEntry != null) { | ||
| 51 | // get the node for the token | ||
| 52 | AstNode tokenNode = null; | ||
| 53 | if (node.getTarget() instanceof MemberReferenceExpression) { | ||
| 54 | tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); | ||
| 55 | } else if (node.getTarget() instanceof SuperReferenceExpression) { | ||
| 56 | tokenNode = node.getTarget(); | ||
| 57 | } else if (node.getTarget() instanceof ThisReferenceExpression) { | ||
| 58 | tokenNode = node.getTarget(); | ||
| 59 | } | ||
| 60 | if (tokenNode != null) { | ||
| 61 | index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | // Check for identifier | ||
| 66 | node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) | ||
| 67 | .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); | ||
| 68 | return visitChildren(node, index); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { | ||
| 73 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 74 | if (ref instanceof FieldReference) { | ||
| 75 | // make sure this is actually a field | ||
| 76 | String erasedSignature = ref.getErasedSignature(); | ||
| 77 | if (erasedSignature.indexOf('(') >= 0) { | ||
| 78 | throw new Error("Expected a field here! got " + ref); | ||
| 79 | } | ||
| 80 | |||
| 81 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 82 | FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); | ||
| 83 | index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry); | ||
| 84 | } | ||
| 85 | |||
| 86 | return visitChildren(node, index); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 91 | TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); | ||
| 92 | if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { | ||
| 93 | ClassEntry classEntry = new ClassEntry(ref.getInternalName()); | ||
| 94 | index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry); | ||
| 95 | } | ||
| 96 | |||
| 97 | return visitChildren(node, index); | ||
| 98 | } | ||
| 99 | |||
| 100 | @Override | ||
| 101 | public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { | ||
| 102 | ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); | ||
| 103 | int parameterIndex = def.getSlot(); | ||
| 104 | |||
| 105 | if (parameterIndex >= 0) { | ||
| 106 | MethodDefEntry ownerMethod = methodEntry; | ||
| 107 | if (def.getMethod() instanceof MethodDefinition) { | ||
| 108 | ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod()); | ||
| 109 | } | ||
| 110 | |||
| 111 | TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType()); | ||
| 112 | LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); | ||
| 113 | Identifier identifier = node.getNameToken(); | ||
| 114 | // cache the argument entry and the identifier | ||
| 115 | identifierEntryCache.put(identifier.getName(), localVariableEntry); | ||
| 116 | index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); | ||
| 117 | } | ||
| 118 | |||
| 119 | return visitChildren(node, index); | ||
| 120 | } | ||
| 121 | |||
| 122 | @Override | ||
| 123 | public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { | ||
| 124 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 125 | if (ref != null) { | ||
| 126 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 127 | FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); | ||
| 128 | index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry); | ||
| 129 | } else | ||
| 130 | this.checkIdentifier(node, index); | ||
| 131 | return visitChildren(node, index); | ||
| 132 | } | ||
| 133 | |||
| 134 | private void checkIdentifier(IdentifierExpression node, SourceIndex index) { | ||
| 135 | if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! | ||
| 136 | index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier())); | ||
| 137 | else | ||
| 138 | unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! | ||
| 139 | } | ||
| 140 | |||
| 141 | private void addDeclarationToUnmatched(String key, SourceIndex index) { | ||
| 142 | Entry<?> entry = identifierEntryCache.get(key); | ||
| 143 | |||
| 144 | // This cannot happened in theory | ||
| 145 | if (entry == null) | ||
| 146 | return; | ||
| 147 | for (Identifier identifier : unmatchedIdentifier.get(key)) | ||
| 148 | index.addDeclaration(TokenFactory.createToken(index, identifier), entry); | ||
| 149 | unmatchedIdentifier.removeAll(key); | ||
| 150 | } | ||
| 151 | |||
| 152 | @Override | ||
| 153 | public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { | ||
| 154 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 155 | if (ref != null && node.getType() instanceof SimpleType) { | ||
| 156 | SimpleType simpleTypeNode = (SimpleType) node.getType(); | ||
| 157 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 158 | MethodEntry constructorEntry = new MethodEntry(classEntry, "<init>", new MethodDescriptor(ref.getErasedSignature())); | ||
| 159 | index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry); | ||
| 160 | } | ||
| 161 | |||
| 162 | return visitChildren(node, index); | ||
| 163 | } | ||
| 164 | |||
| 165 | @Override | ||
| 166 | public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { | ||
| 167 | AstNodeCollection<VariableInitializer> variables = node.getVariables(); | ||
| 168 | |||
| 169 | // Single assignation | ||
| 170 | if (variables.size() == 1) { | ||
| 171 | VariableInitializer initializer = variables.firstOrNullObject(); | ||
| 172 | if (initializer != null && node.getType() instanceof SimpleType) { | ||
| 173 | Identifier identifier = initializer.getNameToken(); | ||
| 174 | Variable variable = initializer.getUserData(Keys.VARIABLE); | ||
| 175 | if (variable != null) { | ||
| 176 | VariableDefinition originalVariable = variable.getOriginalVariable(); | ||
| 177 | if (originalVariable != null) { | ||
| 178 | int variableIndex = originalVariable.getSlot(); | ||
| 179 | if (variableIndex >= 0) { | ||
| 180 | MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod()); | ||
| 181 | TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType()); | ||
| 182 | LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); | ||
| 183 | identifierEntryCache.put(identifier.getName(), localVariableEntry); | ||
| 184 | addDeclarationToUnmatched(identifier.getName(), index); | ||
| 185 | index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | return visitChildren(node, index); | ||
| 192 | } | ||
| 193 | |||
| 194 | @Override | ||
| 195 | public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { | ||
| 196 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 197 | |||
| 198 | if (ref instanceof MethodReference) { | ||
| 199 | // get the behavior entry | ||
| 200 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 201 | MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); | ||
| 202 | |||
| 203 | // get the node for the token | ||
| 204 | AstNode methodNameToken = node.getMethodNameToken(); | ||
| 205 | AstNode targetToken = node.getTarget(); | ||
| 206 | |||
| 207 | if (methodNameToken != null) { | ||
| 208 | index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry); | ||
| 209 | } | ||
| 210 | |||
| 211 | if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { | ||
| 212 | index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | return visitChildren(node, index); | ||
| 217 | } | ||
| 218 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java deleted file mode 100644 index dad505f..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.source.procyon.index; | ||
| 13 | |||
| 14 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 19 | import cuchaz.enigma.source.SourceIndex; | ||
| 20 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 22 | |||
| 23 | public class SourceIndexVisitor extends DepthFirstAstVisitor<SourceIndex, Void> { | ||
| 24 | @Override | ||
| 25 | public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { | ||
| 26 | TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); | ||
| 27 | ClassDefEntry classEntry = EntryParser.parse(def); | ||
| 28 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); | ||
| 29 | |||
| 30 | return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | protected Void visitChildren(AstNode node, SourceIndex index) { | ||
| 35 | for (final AstNode child : node.getChildren()) { | ||
| 36 | child.acceptVisitor(this, index); | ||
| 37 | } | ||
| 38 | return null; | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java deleted file mode 100644 index 62e7c10..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java +++ /dev/null | |||
| @@ -1,46 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.index; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.Region; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 8 | import cuchaz.enigma.analysis.Token; | ||
| 9 | import cuchaz.enigma.source.SourceIndex; | ||
| 10 | |||
| 11 | import java.util.regex.Pattern; | ||
| 12 | |||
| 13 | public class TokenFactory { | ||
| 14 | private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); | ||
| 15 | |||
| 16 | public static Token createToken(SourceIndex index, AstNode node) { | ||
| 17 | String name = node instanceof Identifier ? ((Identifier) node).getName() : ""; | ||
| 18 | Region region = node.getRegion(); | ||
| 19 | |||
| 20 | if (region.getBeginLine() == 0) { | ||
| 21 | System.err.println("Got bad region from Procyon for node " + node); | ||
| 22 | return null; | ||
| 23 | } | ||
| 24 | |||
| 25 | int start = index.getPosition(region.getBeginLine(), region.getBeginColumn()); | ||
| 26 | int end = index.getPosition(region.getEndLine(), region.getEndColumn()); | ||
| 27 | String text = index.getSource().substring(start, end); | ||
| 28 | Token token = new Token(start, end, text); | ||
| 29 | |||
| 30 | boolean isAnonymousInner = | ||
| 31 | node instanceof Identifier && | ||
| 32 | name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && | ||
| 33 | name.lastIndexOf('$') >= 0 && | ||
| 34 | !ANONYMOUS_INNER.matcher(name).matches(); | ||
| 35 | |||
| 36 | if (isAnonymousInner) { | ||
| 37 | TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; | ||
| 38 | if (type != null) { | ||
| 39 | name = type.getName(); | ||
| 40 | token.end = token.start + name.length(); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | return token; | ||
| 45 | } | ||
| 46 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java deleted file mode 100644 index 70fc8c6..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java +++ /dev/null | |||
| @@ -1,134 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.google.common.base.Function; | ||
| 4 | import com.google.common.base.Strings; | ||
| 5 | import com.strobel.assembler.metadata.ParameterDefinition; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 8 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 9 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 10 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 11 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 13 | |||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Collections; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Objects; | ||
| 18 | import java.util.stream.Stream; | ||
| 19 | |||
| 20 | public final class AddJavadocsAstTransform implements IAstTransform { | ||
| 21 | |||
| 22 | private final EntryRemapper remapper; | ||
| 23 | |||
| 24 | public AddJavadocsAstTransform(EntryRemapper remapper) { | ||
| 25 | this.remapper = remapper; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void run(AstNode compilationUnit) { | ||
| 30 | compilationUnit.acceptVisitor(new Visitor(remapper), null); | ||
| 31 | } | ||
| 32 | |||
| 33 | static class Visitor extends DepthFirstAstVisitor<Void, Void> { | ||
| 34 | |||
| 35 | private final EntryRemapper remapper; | ||
| 36 | |||
| 37 | Visitor(EntryRemapper remapper) { | ||
| 38 | this.remapper = remapper; | ||
| 39 | } | ||
| 40 | |||
| 41 | private <T extends AstNode> void addDoc(T node, Function<T, Entry<?>> retriever) { | ||
| 42 | final Comment[] comments = getComments(node, retriever); | ||
| 43 | if (comments != null) { | ||
| 44 | node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) { | ||
| 49 | final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); | ||
| 50 | final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); | ||
| 51 | return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, | ||
| 52 | CommentType.Documentation)).toArray(Comment[]::new); | ||
| 53 | } | ||
| 54 | |||
| 55 | private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) { | ||
| 56 | final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); | ||
| 57 | final Comment[] ret = getComments(node, retriever); | ||
| 58 | if (ret != null) { | ||
| 59 | final String paramPrefix = "@param " + mapping.getTargetName() + " "; | ||
| 60 | final String indent = Strings.repeat(" ", paramPrefix.length()); | ||
| 61 | ret[0].setContent(paramPrefix + ret[0].getContent()); | ||
| 62 | for (int i = 1; i < ret.length; i++) { | ||
| 63 | ret[i].setContent(indent + ret[i].getContent()); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | return ret; | ||
| 67 | } | ||
| 68 | |||
| 69 | private void visitMethod(AstNode node) { | ||
| 70 | final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION)); | ||
| 71 | final Comment[] baseComments = getComments(node, $ -> methodDefEntry); | ||
| 72 | List<Comment> comments = new ArrayList<>(); | ||
| 73 | if (baseComments != null) | ||
| 74 | Collections.addAll(comments, baseComments); | ||
| 75 | |||
| 76 | for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { | ||
| 77 | ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); | ||
| 78 | final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), | ||
| 79 | true, | ||
| 80 | EntryParser.parseTypeDescriptor(def.getParameterType()), null)); | ||
| 81 | if (paramComments != null) | ||
| 82 | Collections.addAll(comments, paramComments); | ||
| 83 | } | ||
| 84 | |||
| 85 | if (!comments.isEmpty()) { | ||
| 86 | if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { | ||
| 87 | comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); | ||
| 88 | } | ||
| 89 | final AstNode oldFirst = node.getFirstChild(); | ||
| 90 | for (Comment comment : comments) { | ||
| 91 | node.insertChildBefore(oldFirst, comment, Roles.COMMENT); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | @Override | ||
| 97 | protected Void visitChildren(AstNode node, Void data) { | ||
| 98 | for (final AstNode child : node.getChildren()) { | ||
| 99 | child.acceptVisitor(this, data); | ||
| 100 | } | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | |||
| 104 | @Override | ||
| 105 | public Void visitMethodDeclaration(MethodDeclaration node, Void data) { | ||
| 106 | visitMethod(node); | ||
| 107 | return super.visitMethodDeclaration(node, data); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Override | ||
| 111 | public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { | ||
| 112 | visitMethod(node); | ||
| 113 | return super.visitConstructorDeclaration(node, data); | ||
| 114 | } | ||
| 115 | |||
| 116 | @Override | ||
| 117 | public Void visitFieldDeclaration(FieldDeclaration node, Void data) { | ||
| 118 | addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); | ||
| 119 | return super.visitFieldDeclaration(node, data); | ||
| 120 | } | ||
| 121 | |||
| 122 | @Override | ||
| 123 | public Void visitTypeDeclaration(TypeDeclaration node, Void data) { | ||
| 124 | addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION))); | ||
| 125 | return super.visitTypeDeclaration(node, data); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { | ||
| 130 | addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); | ||
| 131 | return super.visitEnumValueDeclaration(node, data); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java deleted file mode 100644 index 39e599d..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.ImportDeclaration; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.PackageDeclaration; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 8 | |||
| 9 | public final class DropImportAstTransform implements IAstTransform { | ||
| 10 | public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); | ||
| 11 | |||
| 12 | private DropImportAstTransform() { | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public void run(AstNode compilationUnit) { | ||
| 17 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 18 | } | ||
| 19 | |||
| 20 | static class Visitor extends DepthFirstAstVisitor<Void, Void> { | ||
| 21 | @Override | ||
| 22 | public Void visitPackageDeclaration(PackageDeclaration node, Void data) { | ||
| 23 | node.remove(); | ||
| 24 | return null; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public Void visitImportDeclaration(ImportDeclaration node, Void data) { | ||
| 29 | node.remove(); | ||
| 30 | return null; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java deleted file mode 100644 index b8c087b..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java +++ /dev/null | |||
| @@ -1,37 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 5 | |||
| 6 | import javax.lang.model.element.Modifier; | ||
| 7 | |||
| 8 | public final class DropVarModifiersAstTransform implements IAstTransform { | ||
| 9 | public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); | ||
| 10 | |||
| 11 | private DropVarModifiersAstTransform() { | ||
| 12 | } | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public void run(AstNode compilationUnit) { | ||
| 16 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 17 | } | ||
| 18 | |||
| 19 | static class Visitor extends DepthFirstAstVisitor<Void, Void> { | ||
| 20 | @Override | ||
| 21 | public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { | ||
| 22 | for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { | ||
| 23 | if (modifierToken.getModifier() == Modifier.FINAL) { | ||
| 24 | modifierToken.remove(); | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | return null; | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { | ||
| 33 | node.removeModifier(Modifier.FINAL); | ||
| 34 | return null; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java deleted file mode 100644 index 34d95fa..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java +++ /dev/null | |||
| @@ -1,29 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Created by Thiakil on 13/07/2018. | ||
| 10 | */ | ||
| 11 | public class InvalidIdentifierFix implements IAstTransform { | ||
| 12 | @Override | ||
| 13 | public void run(AstNode compilationUnit) { | ||
| 14 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 15 | } | ||
| 16 | |||
| 17 | class Visitor extends DepthFirstAstVisitor<Void,Void>{ | ||
| 18 | @Override | ||
| 19 | public Void visitIdentifier(Identifier node, Void data) { | ||
| 20 | super.visitIdentifier(node, data); | ||
| 21 | if (node.getName().equals("do") || node.getName().equals("if")){ | ||
| 22 | Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation()); | ||
| 23 | newIdentifier.copyUserDataFrom(node); | ||
| 24 | node.replaceWith(newIdentifier); | ||
| 25 | } | ||
| 26 | return null; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java deleted file mode 100644 index 8accfc7..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java +++ /dev/null | |||
| @@ -1,107 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.BuiltinTypes; | ||
| 4 | import com.strobel.assembler.metadata.CommonTypeReferences; | ||
| 5 | import com.strobel.assembler.metadata.Flags; | ||
| 6 | import com.strobel.assembler.metadata.IGenericInstance; | ||
| 7 | import com.strobel.assembler.metadata.IMemberDefinition; | ||
| 8 | import com.strobel.assembler.metadata.JvmType; | ||
| 9 | import com.strobel.assembler.metadata.MemberReference; | ||
| 10 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 11 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 12 | import com.strobel.assembler.metadata.TypeReference; | ||
| 13 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 14 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AstNodeCollection; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.AstType; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.ComposedType; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.Expression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.Roles; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.WildcardType; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Created by Thiakil on 12/07/2018. | ||
| 33 | */ | ||
| 34 | public class Java8Generics implements IAstTransform { | ||
| 35 | |||
| 36 | @Override | ||
| 37 | public void run(AstNode compilationUnit) { | ||
| 38 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 39 | } | ||
| 40 | |||
| 41 | static class Visitor extends DepthFirstAstVisitor<Void,Void>{ | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public Void visitInvocationExpression(InvocationExpression node, Void data) { | ||
| 45 | super.visitInvocationExpression(node, data); | ||
| 46 | if (node.getTarget() instanceof MemberReferenceExpression){ | ||
| 47 | MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget(); | ||
| 48 | if (referenceExpression.getTypeArguments().stream().map(t->{ | ||
| 49 | TypeReference tr = t.toTypeReference(); | ||
| 50 | if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below | ||
| 51 | TypeReference resolved = tr.resolve(); | ||
| 52 | if (resolved != null) | ||
| 53 | return resolved; | ||
| 54 | } | ||
| 55 | return tr; | ||
| 56 | }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) { | ||
| 57 | //these are invalid for invocations, let the compiler work it out | ||
| 58 | referenceExpression.getTypeArguments().clear(); | ||
| 59 | } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){ | ||
| 60 | //all are <Object>, thereby redundant and/or bad | ||
| 61 | referenceExpression.getTypeArguments().clear(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | return null; | ||
| 65 | } | ||
| 66 | |||
| 67 | @Override | ||
| 68 | public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { | ||
| 69 | super.visitObjectCreationExpression(node, data); | ||
| 70 | AstType type = node.getType(); | ||
| 71 | if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){ | ||
| 72 | SimpleType simpleType = (SimpleType) type; | ||
| 73 | AstNodeCollection<AstType> typeArguments = simpleType.getTypeArguments(); | ||
| 74 | if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){ | ||
| 75 | //all are <Object>, thereby redundant and/or bad | ||
| 76 | typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create("")); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | return null; | ||
| 80 | } | ||
| 81 | |||
| 82 | @Override | ||
| 83 | public Void visitCastExpression(CastExpression node, Void data) { | ||
| 84 | boolean doReplace = false; | ||
| 85 | TypeReference typeReference = node.getType().toTypeReference(); | ||
| 86 | if (typeReference.isArray() && typeReference.getElementType().isGenericType()){ | ||
| 87 | doReplace = true; | ||
| 88 | } else if (typeReference.isGenericType()) { | ||
| 89 | Expression target = node.getExpression(); | ||
| 90 | if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){ | ||
| 91 | doReplace = true; | ||
| 92 | } else if (target instanceof InvocationExpression) { | ||
| 93 | InvocationExpression invocationExpression = (InvocationExpression)target; | ||
| 94 | if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) { | ||
| 95 | ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear(); | ||
| 96 | doReplace = true; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | super.visitCastExpression(node, data); | ||
| 101 | if (doReplace){ | ||
| 102 | node.replaceWith(node.getExpression()); | ||
| 103 | } | ||
| 104 | return null; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java deleted file mode 100644 index 32bb72f..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java +++ /dev/null | |||
| @@ -1,414 +0,0 @@ | |||
| 1 | /* | ||
| 2 | * Originally: | ||
| 3 | * EnumSwitchRewriterTransform.java | ||
| 4 | * | ||
| 5 | * Copyright (c) 2013 Mike Strobel | ||
| 6 | * | ||
| 7 | * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; | ||
| 8 | * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. | ||
| 9 | * | ||
| 10 | * This source code is subject to terms and conditions of the Apache License, Version 2.0. | ||
| 11 | * A copy of the license can be found in the License.html file at the root of this distribution. | ||
| 12 | * By using this source code in any fashion, you are agreeing to be bound by the terms of the | ||
| 13 | * Apache License, Version 2.0. | ||
| 14 | * | ||
| 15 | * You must not remove this notice, or any other, from this software. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package cuchaz.enigma.source.procyon.transformers; | ||
| 19 | |||
| 20 | import com.strobel.assembler.metadata.BuiltinTypes; | ||
| 21 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 22 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 23 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 24 | import com.strobel.assembler.metadata.TypeReference; | ||
| 25 | import com.strobel.core.SafeCloseable; | ||
| 26 | import com.strobel.core.VerifyArgument; | ||
| 27 | import com.strobel.decompiler.DecompilerContext; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.AssignmentExpression; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 31 | import com.strobel.decompiler.languages.java.ast.CaseLabel; | ||
| 32 | import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; | ||
| 33 | import com.strobel.decompiler.languages.java.ast.Expression; | ||
| 34 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 35 | import com.strobel.decompiler.languages.java.ast.IndexerExpression; | ||
| 36 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.SwitchSection; | ||
| 41 | import com.strobel.decompiler.languages.java.ast.SwitchStatement; | ||
| 42 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 43 | import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; | ||
| 44 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 45 | |||
| 46 | import java.util.ArrayList; | ||
| 47 | import java.util.IdentityHashMap; | ||
| 48 | import java.util.LinkedHashMap; | ||
| 49 | import java.util.List; | ||
| 50 | import java.util.Map; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: | ||
| 54 | * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) | ||
| 55 | * - Ignore classes *with* SwitchMap$ names (so the original can handle it) | ||
| 56 | * - Ignores inner synthetics that are not package private | ||
| 57 | */ | ||
| 58 | @SuppressWarnings("Duplicates") | ||
| 59 | public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { | ||
| 60 | private final DecompilerContext _context; | ||
| 61 | |||
| 62 | public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { | ||
| 63 | _context = VerifyArgument.notNull(context, "context"); | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public void run(final AstNode compilationUnit) { | ||
| 68 | compilationUnit.acceptVisitor(new Visitor(_context), null); | ||
| 69 | } | ||
| 70 | |||
| 71 | private final static class Visitor extends ContextTrackingVisitor<Void> { | ||
| 72 | private final static class SwitchMapInfo { | ||
| 73 | final String enclosingType; | ||
| 74 | final Map<String, List<SwitchStatement>> switches = new LinkedHashMap<>(); | ||
| 75 | final Map<String, Map<Integer, Expression>> mappings = new LinkedHashMap<>(); | ||
| 76 | |||
| 77 | TypeDeclaration enclosingTypeDeclaration; | ||
| 78 | |||
| 79 | SwitchMapInfo(final String enclosingType) { | ||
| 80 | this.enclosingType = enclosingType; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | private final Map<String, SwitchMapInfo> _switchMaps = new LinkedHashMap<>(); | ||
| 85 | private boolean _isSwitchMapWrapper; | ||
| 86 | |||
| 87 | protected Visitor(final DecompilerContext context) { | ||
| 88 | super(context); | ||
| 89 | } | ||
| 90 | |||
| 91 | @Override | ||
| 92 | public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { | ||
| 93 | final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; | ||
| 94 | final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); | ||
| 95 | final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); | ||
| 96 | |||
| 97 | if (isSwitchMapWrapper) { | ||
| 98 | final String internalName = typeDefinition.getInternalName(); | ||
| 99 | |||
| 100 | SwitchMapInfo info = _switchMaps.get(internalName); | ||
| 101 | |||
| 102 | if (info == null) { | ||
| 103 | _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); | ||
| 104 | } | ||
| 105 | |||
| 106 | info.enclosingTypeDeclaration = typeDeclaration; | ||
| 107 | } | ||
| 108 | |||
| 109 | _isSwitchMapWrapper = isSwitchMapWrapper; | ||
| 110 | |||
| 111 | try { | ||
| 112 | super.visitTypeDeclaration(typeDeclaration, p); | ||
| 113 | } | ||
| 114 | finally { | ||
| 115 | _isSwitchMapWrapper = oldIsSwitchMapWrapper; | ||
| 116 | } | ||
| 117 | |||
| 118 | rewrite(); | ||
| 119 | |||
| 120 | return null; | ||
| 121 | } | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public Void visitSwitchStatement(final SwitchStatement node, final Void data) { | ||
| 125 | final Expression test = node.getExpression(); | ||
| 126 | |||
| 127 | if (test instanceof IndexerExpression) { | ||
| 128 | final IndexerExpression indexer = (IndexerExpression) test; | ||
| 129 | final Expression array = indexer.getTarget(); | ||
| 130 | final Expression argument = indexer.getArgument(); | ||
| 131 | |||
| 132 | if (!(array instanceof MemberReferenceExpression)) { | ||
| 133 | return super.visitSwitchStatement(node, data); | ||
| 134 | } | ||
| 135 | |||
| 136 | final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; | ||
| 137 | final Expression arrayOwner = arrayAccess.getTarget(); | ||
| 138 | final String mapName = arrayAccess.getMemberName(); | ||
| 139 | |||
| 140 | if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { | ||
| 141 | return super.visitSwitchStatement(node, data); | ||
| 142 | } | ||
| 143 | |||
| 144 | final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; | ||
| 145 | final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); | ||
| 146 | |||
| 147 | if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { | ||
| 148 | return super.visitSwitchStatement(node, data); | ||
| 149 | } | ||
| 150 | |||
| 151 | final InvocationExpression invocation = (InvocationExpression) argument; | ||
| 152 | final Expression invocationTarget = invocation.getTarget(); | ||
| 153 | |||
| 154 | if (!(invocationTarget instanceof MemberReferenceExpression)) { | ||
| 155 | return super.visitSwitchStatement(node, data); | ||
| 156 | } | ||
| 157 | |||
| 158 | final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; | ||
| 159 | |||
| 160 | if (!"ordinal".equals(memberReference.getMemberName())) { | ||
| 161 | return super.visitSwitchStatement(node, data); | ||
| 162 | } | ||
| 163 | |||
| 164 | final String enclosingTypeName = enclosingType.getInternalName(); | ||
| 165 | |||
| 166 | SwitchMapInfo info = _switchMaps.get(enclosingTypeName); | ||
| 167 | |||
| 168 | if (info == null) { | ||
| 169 | _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); | ||
| 170 | |||
| 171 | final TypeDefinition resolvedType = enclosingType.resolve(); | ||
| 172 | |||
| 173 | if (resolvedType != null) { | ||
| 174 | AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); | ||
| 175 | |||
| 176 | if (astBuilder == null) { | ||
| 177 | astBuilder = new AstBuilder(context); | ||
| 178 | } | ||
| 179 | |||
| 180 | try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { | ||
| 181 | final TypeDeclaration declaration = astBuilder.createType(resolvedType); | ||
| 182 | |||
| 183 | declaration.acceptVisitor(this, data); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | List<SwitchStatement> switches = info.switches.get(mapName); | ||
| 189 | |||
| 190 | if (switches == null) { | ||
| 191 | info.switches.put(mapName, switches = new ArrayList<>()); | ||
| 192 | } | ||
| 193 | |||
| 194 | switches.add(node); | ||
| 195 | } | ||
| 196 | |||
| 197 | return super.visitSwitchStatement(node, data); | ||
| 198 | } | ||
| 199 | |||
| 200 | @Override | ||
| 201 | public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { | ||
| 202 | final TypeDefinition currentType = context.getCurrentType(); | ||
| 203 | final MethodDefinition currentMethod = context.getCurrentMethod(); | ||
| 204 | |||
| 205 | if (_isSwitchMapWrapper && | ||
| 206 | currentType != null && | ||
| 207 | currentMethod != null && | ||
| 208 | currentMethod.isTypeInitializer()) { | ||
| 209 | |||
| 210 | final Expression left = node.getLeft(); | ||
| 211 | final Expression right = node.getRight(); | ||
| 212 | |||
| 213 | if (left instanceof IndexerExpression && | ||
| 214 | right instanceof PrimitiveExpression) { | ||
| 215 | |||
| 216 | String mapName = null; | ||
| 217 | |||
| 218 | final Expression array = ((IndexerExpression) left).getTarget(); | ||
| 219 | final Expression argument = ((IndexerExpression) left).getArgument(); | ||
| 220 | |||
| 221 | if (array instanceof MemberReferenceExpression) { | ||
| 222 | mapName = ((MemberReferenceExpression) array).getMemberName(); | ||
| 223 | } | ||
| 224 | else if (array instanceof IdentifierExpression) { | ||
| 225 | mapName = ((IdentifierExpression) array).getIdentifier(); | ||
| 226 | } | ||
| 227 | |||
| 228 | if (mapName == null || mapName.startsWith("$SwitchMap$")) { | ||
| 229 | return super.visitAssignmentExpression(node, data); | ||
| 230 | } | ||
| 231 | |||
| 232 | if (!(argument instanceof InvocationExpression)) { | ||
| 233 | return super.visitAssignmentExpression(node, data); | ||
| 234 | } | ||
| 235 | |||
| 236 | final InvocationExpression invocation = (InvocationExpression) argument; | ||
| 237 | final Expression invocationTarget = invocation.getTarget(); | ||
| 238 | |||
| 239 | if (!(invocationTarget instanceof MemberReferenceExpression)) { | ||
| 240 | return super.visitAssignmentExpression(node, data); | ||
| 241 | } | ||
| 242 | |||
| 243 | final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; | ||
| 244 | final Expression memberTarget = memberReference.getTarget(); | ||
| 245 | |||
| 246 | if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { | ||
| 247 | return super.visitAssignmentExpression(node, data); | ||
| 248 | } | ||
| 249 | |||
| 250 | final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; | ||
| 251 | final Expression outerMemberTarget = outerMemberReference.getTarget(); | ||
| 252 | |||
| 253 | if (!(outerMemberTarget instanceof TypeReferenceExpression)) { | ||
| 254 | return super.visitAssignmentExpression(node, data); | ||
| 255 | } | ||
| 256 | |||
| 257 | final String enclosingType = currentType.getInternalName(); | ||
| 258 | |||
| 259 | SwitchMapInfo info = _switchMaps.get(enclosingType); | ||
| 260 | |||
| 261 | if (info == null) { | ||
| 262 | _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); | ||
| 263 | |||
| 264 | AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); | ||
| 265 | |||
| 266 | if (astBuilder == null) { | ||
| 267 | astBuilder = new AstBuilder(context); | ||
| 268 | } | ||
| 269 | |||
| 270 | info.enclosingTypeDeclaration = astBuilder.createType(currentType); | ||
| 271 | } | ||
| 272 | |||
| 273 | final PrimitiveExpression value = (PrimitiveExpression) right; | ||
| 274 | |||
| 275 | assert value.getValue() instanceof Integer; | ||
| 276 | |||
| 277 | Map<Integer, Expression> mapping = info.mappings.get(mapName); | ||
| 278 | |||
| 279 | if (mapping == null) { | ||
| 280 | info.mappings.put(mapName, mapping = new LinkedHashMap<>()); | ||
| 281 | } | ||
| 282 | |||
| 283 | final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); | ||
| 284 | |||
| 285 | enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); | ||
| 286 | |||
| 287 | mapping.put(((Number) value.getValue()).intValue(), enumValue); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | return super.visitAssignmentExpression(node, data); | ||
| 292 | } | ||
| 293 | |||
| 294 | private void rewrite() { | ||
| 295 | if (_switchMaps.isEmpty()) { | ||
| 296 | return; | ||
| 297 | } | ||
| 298 | |||
| 299 | for (final SwitchMapInfo info : _switchMaps.values()) { | ||
| 300 | rewrite(info); | ||
| 301 | } | ||
| 302 | |||
| 303 | // | ||
| 304 | // Remove switch map type wrappers that are no longer referenced. | ||
| 305 | // | ||
| 306 | |||
| 307 | outer: | ||
| 308 | for (final SwitchMapInfo info : _switchMaps.values()) { | ||
| 309 | for (final String mapName : info.switches.keySet()) { | ||
| 310 | final List<SwitchStatement> switches = info.switches.get(mapName); | ||
| 311 | |||
| 312 | if (switches != null && !switches.isEmpty()) { | ||
| 313 | continue outer; | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; | ||
| 318 | |||
| 319 | if (enclosingTypeDeclaration != null) { | ||
| 320 | enclosingTypeDeclaration.remove(); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | private void rewrite(final SwitchMapInfo info) { | ||
| 326 | if (info.switches.isEmpty()) { | ||
| 327 | return; | ||
| 328 | } | ||
| 329 | |||
| 330 | for (final String mapName : info.switches.keySet()) { | ||
| 331 | final List<SwitchStatement> switches = info.switches.get(mapName); | ||
| 332 | final Map<Integer, Expression> mappings = info.mappings.get(mapName); | ||
| 333 | |||
| 334 | if (switches != null && mappings != null) { | ||
| 335 | for (int i = 0; i < switches.size(); i++) { | ||
| 336 | if (rewriteSwitch(switches.get(i), mappings)) { | ||
| 337 | switches.remove(i--); | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | private boolean rewriteSwitch(final SwitchStatement s, final Map<Integer, Expression> mappings) { | ||
| 345 | final Map<Expression, Expression> replacements = new IdentityHashMap<>(); | ||
| 346 | |||
| 347 | for (final SwitchSection section : s.getSwitchSections()) { | ||
| 348 | for (final CaseLabel caseLabel : section.getCaseLabels()) { | ||
| 349 | final Expression expression = caseLabel.getExpression(); | ||
| 350 | |||
| 351 | if (expression.isNull()) { | ||
| 352 | continue; | ||
| 353 | } | ||
| 354 | |||
| 355 | if (expression instanceof PrimitiveExpression) { | ||
| 356 | final Object value = ((PrimitiveExpression) expression).getValue(); | ||
| 357 | |||
| 358 | if (value instanceof Integer) { | ||
| 359 | final Expression replacement = mappings.get(value); | ||
| 360 | |||
| 361 | if (replacement != null) { | ||
| 362 | replacements.put(expression, replacement); | ||
| 363 | continue; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | // | ||
| 369 | // If we can't rewrite all cases, we abort. | ||
| 370 | // | ||
| 371 | |||
| 372 | return false; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | final IndexerExpression indexer = (IndexerExpression) s.getExpression(); | ||
| 377 | final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); | ||
| 378 | final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); | ||
| 379 | final Expression newTest = memberReference.getTarget(); | ||
| 380 | |||
| 381 | newTest.remove(); | ||
| 382 | indexer.replaceWith(newTest); | ||
| 383 | |||
| 384 | for (final Map.Entry<Expression, Expression> entry : replacements.entrySet()) { | ||
| 385 | entry.getKey().replaceWith(entry.getValue().clone()); | ||
| 386 | } | ||
| 387 | |||
| 388 | return true; | ||
| 389 | } | ||
| 390 | |||
| 391 | private static boolean isSwitchMapWrapper(final TypeReference type) { | ||
| 392 | if (type == null) { | ||
| 393 | return false; | ||
| 394 | } | ||
| 395 | |||
| 396 | final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type | ||
| 397 | : type.resolve(); | ||
| 398 | |||
| 399 | if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { | ||
| 400 | return false; | ||
| 401 | } | ||
| 402 | |||
| 403 | for (final FieldDefinition field : definition.getDeclaredFields()) { | ||
| 404 | if (!field.getName().startsWith("$SwitchMap$") && | ||
| 405 | BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { | ||
| 406 | |||
| 407 | return true; | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | return false; | ||
| 412 | } | ||
| 413 | } | ||
| 414 | } \ No newline at end of file | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java deleted file mode 100644 index cf0376f..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.BuiltinTypes; | ||
| 4 | import com.strobel.decompiler.DecompilerContext; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; | ||
| 8 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Created by Thiakil on 11/07/2018. | ||
| 12 | */ | ||
| 13 | public class RemoveObjectCasts implements IAstTransform { | ||
| 14 | private final DecompilerContext _context; | ||
| 15 | |||
| 16 | public RemoveObjectCasts(DecompilerContext context) { | ||
| 17 | _context = context; | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public void run(AstNode compilationUnit) { | ||
| 22 | compilationUnit.acceptVisitor(new Visitor(_context), null); | ||
| 23 | } | ||
| 24 | |||
| 25 | private final static class Visitor extends ContextTrackingVisitor<Void>{ | ||
| 26 | |||
| 27 | protected Visitor(DecompilerContext context) { | ||
| 28 | super(context); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public Void visitCastExpression(CastExpression node, Void data) { | ||
| 33 | if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ | ||
| 34 | node.replaceWith(node.getExpression()); | ||
| 35 | } | ||
| 36 | return super.visitCastExpression(node, data); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java deleted file mode 100644 index d3ddaab..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java +++ /dev/null | |||
| @@ -1,197 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.MemberReference; | ||
| 4 | import com.strobel.assembler.metadata.MetadataFilters; | ||
| 5 | import com.strobel.assembler.metadata.MetadataHelper; | ||
| 6 | import com.strobel.assembler.metadata.MethodBinder; | ||
| 7 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 8 | import com.strobel.assembler.metadata.MethodReference; | ||
| 9 | import com.strobel.assembler.metadata.TypeReference; | ||
| 10 | import com.strobel.core.StringUtilities; | ||
| 11 | import com.strobel.core.VerifyArgument; | ||
| 12 | import com.strobel.decompiler.DecompilerContext; | ||
| 13 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 14 | import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.AstNodeCollection; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.Expression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.JavaResolver; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 27 | import com.strobel.decompiler.semantics.ResolveResult; | ||
| 28 | |||
| 29 | import java.util.ArrayList; | ||
| 30 | import java.util.List; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Created by Thiakil on 12/07/2018. | ||
| 34 | */ | ||
| 35 | public class VarargsFixer implements IAstTransform { | ||
| 36 | private final DecompilerContext _context; | ||
| 37 | |||
| 38 | public VarargsFixer(final DecompilerContext context) { | ||
| 39 | _context = VerifyArgument.notNull(context, "context"); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public void run(AstNode compilationUnit) { | ||
| 44 | compilationUnit.acceptVisitor(new Visitor(_context), null); | ||
| 45 | } | ||
| 46 | |||
| 47 | class Visitor extends ContextTrackingVisitor<Void> { | ||
| 48 | private final JavaResolver _resolver; | ||
| 49 | protected Visitor(DecompilerContext context) { | ||
| 50 | super(context); | ||
| 51 | _resolver = new JavaResolver(context); | ||
| 52 | } | ||
| 53 | |||
| 54 | //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them | ||
| 55 | @Override | ||
| 56 | public Void visitInvocationExpression(InvocationExpression node, Void data) { | ||
| 57 | super.visitInvocationExpression(node, data); | ||
| 58 | MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 59 | if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){ | ||
| 60 | AstNodeCollection<Expression> arguments = node.getArguments(); | ||
| 61 | Expression lastParam = arguments.lastOrNullObject(); | ||
| 62 | if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){ | ||
| 63 | ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam; | ||
| 64 | if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){ | ||
| 65 | lastParam.remove(); | ||
| 66 | } else { | ||
| 67 | for (Expression e : varargArray.getInitializer().getElements()){ | ||
| 68 | arguments.insertBefore(varargArray, e.clone()); | ||
| 69 | } | ||
| 70 | varargArray.remove(); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | return null; | ||
| 75 | } | ||
| 76 | |||
| 77 | //applies the vararg transform to object creation | ||
| 78 | @Override | ||
| 79 | public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { | ||
| 80 | super.visitObjectCreationExpression(node, data); | ||
| 81 | final AstNodeCollection<Expression> arguments = node.getArguments(); | ||
| 82 | final Expression lastArgument = arguments.lastOrNullObject(); | ||
| 83 | |||
| 84 | Expression arrayArg = lastArgument; | ||
| 85 | |||
| 86 | if (arrayArg instanceof CastExpression) | ||
| 87 | arrayArg = ((CastExpression) arrayArg).getExpression(); | ||
| 88 | |||
| 89 | if (arrayArg == null || | ||
| 90 | arrayArg.isNull() || | ||
| 91 | !(arrayArg instanceof ArrayCreationExpression && | ||
| 92 | node.getTarget() instanceof MemberReferenceExpression)) { | ||
| 93 | |||
| 94 | return null; | ||
| 95 | } | ||
| 96 | |||
| 97 | final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg; | ||
| 98 | final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget(); | ||
| 99 | |||
| 100 | if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) { | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | |||
| 104 | final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 105 | |||
| 106 | if (method == null) { | ||
| 107 | return null; | ||
| 108 | } | ||
| 109 | |||
| 110 | final MethodDefinition resolved = method.resolve(); | ||
| 111 | |||
| 112 | if (resolved == null || !resolved.isVarArgs()) { | ||
| 113 | return null; | ||
| 114 | } | ||
| 115 | |||
| 116 | final List<MethodReference> candidates; | ||
| 117 | final Expression invocationTarget = target.getTarget(); | ||
| 118 | |||
| 119 | if (invocationTarget == null || invocationTarget.isNull()) { | ||
| 120 | candidates = MetadataHelper.findMethods( | ||
| 121 | context.getCurrentType(), | ||
| 122 | MetadataFilters.matchName(resolved.getName()) | ||
| 123 | ); | ||
| 124 | } | ||
| 125 | else { | ||
| 126 | final ResolveResult targetResult = _resolver.apply(invocationTarget); | ||
| 127 | |||
| 128 | if (targetResult == null || targetResult.getType() == null) { | ||
| 129 | return null; | ||
| 130 | } | ||
| 131 | |||
| 132 | candidates = MetadataHelper.findMethods( | ||
| 133 | targetResult.getType(), | ||
| 134 | MetadataFilters.matchName(resolved.getName()) | ||
| 135 | ); | ||
| 136 | } | ||
| 137 | |||
| 138 | final List<TypeReference> argTypes = new ArrayList<>(); | ||
| 139 | |||
| 140 | for (final Expression argument : arguments) { | ||
| 141 | final ResolveResult argResult = _resolver.apply(argument); | ||
| 142 | |||
| 143 | if (argResult == null || argResult.getType() == null) { | ||
| 144 | return null; | ||
| 145 | } | ||
| 146 | |||
| 147 | argTypes.add(argResult.getType()); | ||
| 148 | } | ||
| 149 | |||
| 150 | final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes); | ||
| 151 | |||
| 152 | if (c1.isFailure() || c1.isAmbiguous()) { | ||
| 153 | return null; | ||
| 154 | } | ||
| 155 | |||
| 156 | argTypes.remove(argTypes.size() - 1); | ||
| 157 | |||
| 158 | final ArrayInitializerExpression initializer = newArray.getInitializer(); | ||
| 159 | final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty(); | ||
| 160 | |||
| 161 | if (hasElements) { | ||
| 162 | for (final Expression argument : initializer.getElements()) { | ||
| 163 | final ResolveResult argResult = _resolver.apply(argument); | ||
| 164 | |||
| 165 | if (argResult == null || argResult.getType() == null) { | ||
| 166 | return null; | ||
| 167 | } | ||
| 168 | |||
| 169 | argTypes.add(argResult.getType()); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes); | ||
| 174 | |||
| 175 | if (c2.isFailure() || | ||
| 176 | c2.isAmbiguous() || | ||
| 177 | !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) { | ||
| 178 | |||
| 179 | return null; | ||
| 180 | } | ||
| 181 | |||
| 182 | lastArgument.remove(); | ||
| 183 | |||
| 184 | if (!hasElements) { | ||
| 185 | lastArgument.remove(); | ||
| 186 | return null; | ||
| 187 | } | ||
| 188 | |||
| 189 | for (final Expression newArg : initializer.getElements()) { | ||
| 190 | newArg.remove(); | ||
| 191 | arguments.add(newArg); | ||
| 192 | } | ||
| 193 | |||
| 194 | return null; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java deleted file mode 100644 index e702956..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.Buffer; | ||
| 4 | import com.strobel.assembler.metadata.ClasspathTypeLoader; | ||
| 5 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Caching version of {@link ClasspathTypeLoader} | ||
| 9 | */ | ||
| 10 | public class CachingClasspathTypeLoader extends CachingTypeLoader { | ||
| 11 | private static ITypeLoader extraClassPathLoader = null; | ||
| 12 | |||
| 13 | public static void setExtraClassPathLoader(ITypeLoader loader){ | ||
| 14 | extraClassPathLoader = loader; | ||
| 15 | } | ||
| 16 | |||
| 17 | private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); | ||
| 18 | |||
| 19 | @Override | ||
| 20 | protected byte[] doLoad(String className) { | ||
| 21 | Buffer parentBuf = new Buffer(); | ||
| 22 | if (classpathLoader.tryLoadType(className, parentBuf)) { | ||
| 23 | return parentBuf.array(); | ||
| 24 | } | ||
| 25 | if (extraClassPathLoader != null){ | ||
| 26 | parentBuf.reset(); | ||
| 27 | if (extraClassPathLoader.tryLoadType(className, parentBuf)){ | ||
| 28 | return parentBuf.array(); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | return EMPTY_ARRAY;//need to return *something* as null means no store | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java deleted file mode 100644 index 5be5ddd..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.google.common.collect.Maps; | ||
| 4 | import com.strobel.assembler.metadata.Buffer; | ||
| 5 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 6 | |||
| 7 | import java.util.Map; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Common cache functions | ||
| 11 | */ | ||
| 12 | public abstract class CachingTypeLoader implements ITypeLoader { | ||
| 13 | protected static final byte[] EMPTY_ARRAY = {}; | ||
| 14 | |||
| 15 | private final Map<String, byte[]> cache = Maps.newHashMap(); | ||
| 16 | |||
| 17 | protected abstract byte[] doLoad(String className); | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public boolean tryLoadType(String className, Buffer out) { | ||
| 21 | |||
| 22 | // check the cache | ||
| 23 | byte[] data = this.cache.computeIfAbsent(className, this::doLoad); | ||
| 24 | |||
| 25 | if (data == EMPTY_ARRAY) { | ||
| 26 | return false; | ||
| 27 | } | ||
| 28 | |||
| 29 | out.reset(data.length); | ||
| 30 | System.arraycopy(data, 0, out.array(), out.position(), data.length); | ||
| 31 | out.position(0); | ||
| 32 | return true; | ||
| 33 | } | ||
| 34 | |||
| 35 | public void clearCache() { | ||
| 36 | this.cache.clear(); | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java deleted file mode 100644 index e703d3b..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java +++ /dev/null | |||
| @@ -1,140 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import com.strobel.assembler.metadata.Buffer; | ||
| 16 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 17 | import cuchaz.enigma.ClassProvider; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | import org.objectweb.asm.ClassVisitor; | ||
| 20 | import org.objectweb.asm.ClassWriter; | ||
| 21 | import org.objectweb.asm.Opcodes; | ||
| 22 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 23 | import org.objectweb.asm.tree.ClassNode; | ||
| 24 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 25 | import org.objectweb.asm.tree.MethodNode; | ||
| 26 | |||
| 27 | import java.util.Collection; | ||
| 28 | import java.util.LinkedList; | ||
| 29 | import java.util.List; | ||
| 30 | import java.util.function.Function; | ||
| 31 | |||
| 32 | public class CompiledSourceTypeLoader extends CachingTypeLoader { | ||
| 33 | //Store one instance as the classpath shouldn't change during load | ||
| 34 | private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); | ||
| 35 | |||
| 36 | private final ClassProvider compiledSource; | ||
| 37 | private final LinkedList<Function<ClassVisitor, ClassVisitor>> visitors = new LinkedList<>(); | ||
| 38 | |||
| 39 | public CompiledSourceTypeLoader(ClassProvider compiledSource) { | ||
| 40 | this.compiledSource = compiledSource; | ||
| 41 | } | ||
| 42 | |||
| 43 | public void addVisitor(Function<ClassVisitor, ClassVisitor> visitor) { | ||
| 44 | this.visitors.addFirst(visitor); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | protected byte[] doLoad(String className) { | ||
| 49 | byte[] data = loadType(className); | ||
| 50 | if (data == null) { | ||
| 51 | return loadClasspath(className); | ||
| 52 | } | ||
| 53 | |||
| 54 | return data; | ||
| 55 | } | ||
| 56 | |||
| 57 | private byte[] loadClasspath(String name) { | ||
| 58 | Buffer parentBuf = new Buffer(); | ||
| 59 | if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { | ||
| 60 | return parentBuf.array(); | ||
| 61 | } | ||
| 62 | return EMPTY_ARRAY; | ||
| 63 | } | ||
| 64 | |||
| 65 | private byte[] loadType(String className) { | ||
| 66 | ClassEntry entry = new ClassEntry(className); | ||
| 67 | |||
| 68 | // find the class in the jar | ||
| 69 | ClassNode node = findClassNode(entry); | ||
| 70 | if (node == null) { | ||
| 71 | // couldn't find it | ||
| 72 | return null; | ||
| 73 | } | ||
| 74 | |||
| 75 | removeRedundantClassCalls(node); | ||
| 76 | |||
| 77 | ClassWriter writer = new ClassWriter(0); | ||
| 78 | |||
| 79 | ClassVisitor visitor = writer; | ||
| 80 | for (Function<ClassVisitor, ClassVisitor> visitorFunction : this.visitors) { | ||
| 81 | visitor = visitorFunction.apply(visitor); | ||
| 82 | } | ||
| 83 | |||
| 84 | node.accept(visitor); | ||
| 85 | |||
| 86 | // we have a transformed class! | ||
| 87 | return writer.toByteArray(); | ||
| 88 | } | ||
| 89 | |||
| 90 | private void removeRedundantClassCalls(ClassNode node) { | ||
| 91 | // remove <obj>.getClass() calls that are seemingly injected | ||
| 92 | // DUP | ||
| 93 | // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; | ||
| 94 | // POP | ||
| 95 | for (MethodNode methodNode : node.methods) { | ||
| 96 | AbstractInsnNode insnNode = methodNode.instructions.getFirst(); | ||
| 97 | while (insnNode != null) { | ||
| 98 | if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { | ||
| 99 | MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; | ||
| 100 | if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { | ||
| 101 | AbstractInsnNode previous = methodInsnNode.getPrevious(); | ||
| 102 | AbstractInsnNode next = methodInsnNode.getNext(); | ||
| 103 | if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { | ||
| 104 | insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction | ||
| 105 | methodNode.instructions.remove(previous); | ||
| 106 | methodNode.instructions.remove(methodInsnNode); | ||
| 107 | methodNode.instructions.remove(next); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | insnNode = insnNode.getNext(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | private ClassNode findClassNode(ClassEntry entry) { | ||
| 117 | // try to find the class in the jar | ||
| 118 | for (String className : getClassNamesToTry(entry)) { | ||
| 119 | ClassNode node = compiledSource.getClassNode(className); | ||
| 120 | if (node != null) { | ||
| 121 | return node; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | // didn't find it ;_; | ||
| 126 | return null; | ||
| 127 | } | ||
| 128 | |||
| 129 | private Collection<String> getClassNamesToTry(ClassEntry entry) { | ||
| 130 | List<String> classNamesToTry = Lists.newArrayList(); | ||
| 131 | classNamesToTry.add(entry.getFullName()); | ||
| 132 | |||
| 133 | ClassEntry outerClass = entry.getOuterClass(); | ||
| 134 | if (outerClass != null) { | ||
| 135 | classNamesToTry.addAll(getClassNamesToTry(outerClass)); | ||
| 136 | } | ||
| 137 | |||
| 138 | return classNamesToTry; | ||
| 139 | } | ||
| 140 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java deleted file mode 100644 index c4732b0..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 4 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 5 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 6 | import com.strobel.assembler.metadata.TypeReference; | ||
| 7 | |||
| 8 | import java.util.Collections; | ||
| 9 | import java.util.Set; | ||
| 10 | import java.util.concurrent.ConcurrentHashMap; | ||
| 11 | |||
| 12 | public final class NoRetryMetadataSystem extends MetadataSystem { | ||
| 13 | private final Set<String> failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); | ||
| 14 | |||
| 15 | public NoRetryMetadataSystem(final ITypeLoader typeLoader) { | ||
| 16 | super(typeLoader); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { | ||
| 21 | if (failedTypes.contains(descriptor)) { | ||
| 22 | return null; | ||
| 23 | } | ||
| 24 | |||
| 25 | final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); | ||
| 26 | |||
| 27 | if (result == null) { | ||
| 28 | failedTypes.add(descriptor); | ||
| 29 | } | ||
| 30 | |||
| 31 | return result; | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public synchronized TypeDefinition resolve(final TypeReference type) { | ||
| 36 | return super.resolve(type); | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java deleted file mode 100644 index 86c6ecc..0000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.Buffer; | ||
| 4 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 5 | |||
| 6 | /** | ||
| 7 | * Typeloader with synchronized tryLoadType method | ||
| 8 | */ | ||
| 9 | public class SynchronizedTypeLoader implements ITypeLoader { | ||
| 10 | private final ITypeLoader delegate; | ||
| 11 | |||
| 12 | public SynchronizedTypeLoader(ITypeLoader delegate) { | ||
| 13 | this.delegate = delegate; | ||
| 14 | } | ||
| 15 | |||
| 16 | @Override | ||
| 17 | public synchronized boolean tryLoadType(String internalName, Buffer buffer) { | ||
| 18 | return delegate.tryLoadType(internalName, buffer); | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java b/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java deleted file mode 100644 index 0155ad2..0000000 --- a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.throwables; | ||
| 13 | |||
| 14 | public class IllegalNameException extends RuntimeException { | ||
| 15 | |||
| 16 | private String name; | ||
| 17 | private String reason; | ||
| 18 | |||
| 19 | public IllegalNameException(String name, String reason) { | ||
| 20 | this.name = name; | ||
| 21 | this.reason = reason; | ||
| 22 | } | ||
| 23 | |||
| 24 | public String getReason() { | ||
| 25 | return this.reason; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public String getMessage() { | ||
| 30 | StringBuilder buf = new StringBuilder(); | ||
| 31 | buf.append("Illegal name: "); | ||
| 32 | buf.append(this.name); | ||
| 33 | if (this.reason != null) { | ||
| 34 | buf.append(" because "); | ||
| 35 | buf.append(this.reason); | ||
| 36 | } | ||
| 37 | return buf.toString(); | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java b/src/main/java/cuchaz/enigma/throwables/MappingConflict.java deleted file mode 100644 index 95cd449..0000000 --- a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.throwables; | ||
| 2 | |||
| 3 | public class MappingConflict extends Exception { | ||
| 4 | public MappingConflict(String clazz, String name, String nameExisting) { | ||
| 5 | super(String.format("Conflicting mappings found for %s. The mapping file is %s and the second is %s", clazz, name, nameExisting)); | ||
| 6 | } | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java deleted file mode 100644 index b7e6d42..0000000 --- a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.throwables; | ||
| 13 | |||
| 14 | import java.io.File; | ||
| 15 | import java.util.function.Supplier; | ||
| 16 | |||
| 17 | public class MappingParseException extends Exception { | ||
| 18 | |||
| 19 | private int line; | ||
| 20 | private String message; | ||
| 21 | private String filePath; | ||
| 22 | |||
| 23 | public MappingParseException(File file, int line, String message) { | ||
| 24 | this.line = line; | ||
| 25 | this.message = message; | ||
| 26 | filePath = file.getAbsolutePath(); | ||
| 27 | } | ||
| 28 | |||
| 29 | public MappingParseException(Supplier<String> filenameProvider, int line, String message) { | ||
| 30 | this.line = line; | ||
| 31 | this.message = message; | ||
| 32 | filePath = filenameProvider.get(); | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public String getMessage() { | ||
| 37 | return "Line " + line + ": " + message + " in file " + filePath; | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java deleted file mode 100644 index 18c966c..0000000 --- a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java +++ /dev/null | |||
| @@ -1,44 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.NameValidator; | ||
| 4 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 5 | |||
| 6 | import java.util.Collection; | ||
| 7 | import java.util.Locale; | ||
| 8 | |||
| 9 | public class LocalNameGenerator { | ||
| 10 | public static String generateArgumentName(int index, TypeDescriptor desc, Collection<TypeDescriptor> arguments) { | ||
| 11 | boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1; | ||
| 12 | String translatedName; | ||
| 13 | int nameIndex = index + 1; | ||
| 14 | StringBuilder nameBuilder = new StringBuilder(getTypeName(desc)); | ||
| 15 | if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) { | ||
| 16 | nameBuilder.append(nameIndex); | ||
| 17 | } | ||
| 18 | translatedName = nameBuilder.toString(); | ||
| 19 | return translatedName; | ||
| 20 | } | ||
| 21 | |||
| 22 | public static String generateLocalVariableName(int index, TypeDescriptor desc) { | ||
| 23 | int nameIndex = index + 1; | ||
| 24 | return getTypeName(desc) + nameIndex; | ||
| 25 | } | ||
| 26 | |||
| 27 | private static String getTypeName(TypeDescriptor desc) { | ||
| 28 | // Unfortunately each of these have different name getters, so they have different code paths | ||
| 29 | if (desc.isPrimitive()) { | ||
| 30 | TypeDescriptor.Primitive argCls = desc.getPrimitive(); | ||
| 31 | return argCls.name().toLowerCase(Locale.ROOT); | ||
| 32 | } else if (desc.isArray()) { | ||
| 33 | // List types would require this whole block again, so just go with aListx | ||
| 34 | return "arr"; | ||
| 35 | } else if (desc.isType()) { | ||
| 36 | String typeName = desc.getTypeEntry().getSimpleName().replace("$", ""); | ||
| 37 | typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1); | ||
| 38 | return typeName; | ||
| 39 | } else { | ||
| 40 | System.err.println("Encountered invalid argument type descriptor " + desc.toString()); | ||
| 41 | return "var"; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java deleted file mode 100644 index 529d0ed..0000000 --- a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java +++ /dev/null | |||
| @@ -1,24 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 6 | |||
| 7 | public class MappingTranslator implements Translator { | ||
| 8 | private final EntryMap<EntryMapping> mappings; | ||
| 9 | private final EntryResolver resolver; | ||
| 10 | |||
| 11 | public MappingTranslator(EntryMap<EntryMapping> mappings, EntryResolver resolver) { | ||
| 12 | this.mappings = mappings; | ||
| 13 | this.resolver = resolver; | ||
| 14 | } | ||
| 15 | |||
| 16 | @SuppressWarnings("unchecked") | ||
| 17 | @Override | ||
| 18 | public <T extends Translatable> T translate(T translatable) { | ||
| 19 | if (translatable == null) { | ||
| 20 | return null; | ||
| 21 | } | ||
| 22 | return (T) translatable.translate(this, resolver, mappings); | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java deleted file mode 100644 index 3783053..0000000 --- a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java +++ /dev/null | |||
| @@ -1,92 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | |||
| 16 | import java.io.IOException; | ||
| 17 | import java.io.StringReader; | ||
| 18 | import java.util.List; | ||
| 19 | |||
| 20 | public class SignatureUpdater { | ||
| 21 | |||
| 22 | public static String update(String signature, ClassNameUpdater updater) { | ||
| 23 | try { | ||
| 24 | StringBuilder buf = new StringBuilder(); | ||
| 25 | |||
| 26 | // read the signature character-by-character | ||
| 27 | StringReader reader = new StringReader(signature); | ||
| 28 | int i; | ||
| 29 | while ((i = reader.read()) != -1) { | ||
| 30 | char c = (char) i; | ||
| 31 | |||
| 32 | // does this character start a class name? | ||
| 33 | if (c == 'L') { | ||
| 34 | // update the class name and add it to the buffer | ||
| 35 | buf.append('L'); | ||
| 36 | String className = readClass(reader); | ||
| 37 | if (className == null) { | ||
| 38 | throw new IllegalArgumentException("Malformed signature: " + signature); | ||
| 39 | } | ||
| 40 | buf.append(updater.update(className)); | ||
| 41 | buf.append(';'); | ||
| 42 | } else { | ||
| 43 | // copy the character into the buffer | ||
| 44 | buf.append(c); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | return buf.toString(); | ||
| 49 | } catch (IOException ex) { | ||
| 50 | // I'm pretty sure a StringReader will never throw one of these | ||
| 51 | throw new Error(ex); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | private static String readClass(StringReader reader) throws IOException { | ||
| 56 | // read all the characters in the buffer until we hit a ';' | ||
| 57 | // remember to treat generics correctly | ||
| 58 | StringBuilder buf = new StringBuilder(); | ||
| 59 | int depth = 0; | ||
| 60 | int i; | ||
| 61 | while ((i = reader.read()) != -1) { | ||
| 62 | char c = (char) i; | ||
| 63 | |||
| 64 | if (c == '<') { | ||
| 65 | depth++; | ||
| 66 | } else if (c == '>') { | ||
| 67 | depth--; | ||
| 68 | } else if (depth == 0) { | ||
| 69 | if (c == ';') { | ||
| 70 | return buf.toString(); | ||
| 71 | } else { | ||
| 72 | buf.append(c); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | return null; | ||
| 78 | } | ||
| 79 | |||
| 80 | public static List<String> getClasses(String signature) { | ||
| 81 | final List<String> classNames = Lists.newArrayList(); | ||
| 82 | update(signature, className -> { | ||
| 83 | classNames.add(className); | ||
| 84 | return className; | ||
| 85 | }); | ||
| 86 | return classNames; | ||
| 87 | } | ||
| 88 | |||
| 89 | public interface ClassNameUpdater { | ||
| 90 | String update(String className); | ||
| 91 | } | ||
| 92 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/Translatable.java b/src/main/java/cuchaz/enigma/translation/Translatable.java deleted file mode 100644 index 0370ef1..0000000 --- a/src/main/java/cuchaz/enigma/translation/Translatable.java +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 6 | |||
| 7 | public interface Translatable { | ||
| 8 | Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings); | ||
| 9 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java deleted file mode 100644 index 2ecb30b..0000000 --- a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java +++ /dev/null | |||
| @@ -1,36 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation; | ||
| 13 | |||
| 14 | public enum TranslationDirection { | ||
| 15 | |||
| 16 | DEOBFUSCATING { | ||
| 17 | @Override | ||
| 18 | public <T> T choose(T deobfChoice, T obfChoice) { | ||
| 19 | if (deobfChoice == null) { | ||
| 20 | return obfChoice; | ||
| 21 | } | ||
| 22 | return deobfChoice; | ||
| 23 | } | ||
| 24 | }, | ||
| 25 | OBFUSCATING { | ||
| 26 | @Override | ||
| 27 | public <T> T choose(T deobfChoice, T obfChoice) { | ||
| 28 | if (obfChoice == null) { | ||
| 29 | return deobfChoice; | ||
| 30 | } | ||
| 31 | return obfChoice; | ||
| 32 | } | ||
| 33 | }; | ||
| 34 | |||
| 35 | public abstract <T> T choose(T deobfChoice, T obfChoice); | ||
| 36 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/Translator.java b/src/main/java/cuchaz/enigma/translation/Translator.java deleted file mode 100644 index c70141f..0000000 --- a/src/main/java/cuchaz/enigma/translation/Translator.java +++ /dev/null | |||
| @@ -1,61 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation; | ||
| 13 | |||
| 14 | import com.google.common.collect.HashMultimap; | ||
| 15 | import com.google.common.collect.Multimap; | ||
| 16 | |||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.HashMap; | ||
| 19 | import java.util.Map; | ||
| 20 | import java.util.Set; | ||
| 21 | import java.util.stream.Collectors; | ||
| 22 | |||
| 23 | public interface Translator { | ||
| 24 | <T extends Translatable> T translate(T translatable); | ||
| 25 | |||
| 26 | default <T extends Translatable> Collection<T> translate(Collection<T> translatable) { | ||
| 27 | return translatable.stream() | ||
| 28 | .map(this::translate) | ||
| 29 | .collect(Collectors.toList()); | ||
| 30 | } | ||
| 31 | |||
| 32 | default <T extends Translatable> Set<T> translate(Set<T> translatable) { | ||
| 33 | return translatable.stream() | ||
| 34 | .map(this::translate) | ||
| 35 | .collect(Collectors.toSet()); | ||
| 36 | } | ||
| 37 | |||
| 38 | default <T extends Translatable, V> Map<T, V> translateKeys(Map<T, V> translatable) { | ||
| 39 | Map<T, V> result = new HashMap<>(translatable.size()); | ||
| 40 | for (Map.Entry<T, V> entry : translatable.entrySet()) { | ||
| 41 | result.put(translate(entry.getKey()), entry.getValue()); | ||
| 42 | } | ||
| 43 | return result; | ||
| 44 | } | ||
| 45 | |||
| 46 | default <K extends Translatable, V extends Translatable> Map<K, V> translate(Map<K, V> translatable) { | ||
| 47 | Map<K, V> result = new HashMap<>(translatable.size()); | ||
| 48 | for (Map.Entry<K, V> entry : translatable.entrySet()) { | ||
| 49 | result.put(translate(entry.getKey()), translate(entry.getValue())); | ||
| 50 | } | ||
| 51 | return result; | ||
| 52 | } | ||
| 53 | |||
| 54 | default <K extends Translatable, V extends Translatable> Multimap<K, V> translate(Multimap<K, V> translatable) { | ||
| 55 | Multimap<K, V> result = HashMultimap.create(translatable.size(), 1); | ||
| 56 | for (Map.Entry<K, Collection<V>> entry : translatable.asMap().entrySet()) { | ||
| 57 | result.putAll(translate(entry.getKey()), translate(entry.getValue())); | ||
| 58 | } | ||
| 59 | return result; | ||
| 60 | } | ||
| 61 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java deleted file mode 100644 index c010833..0000000 --- a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation; | ||
| 2 | |||
| 3 | public enum VoidTranslator implements Translator { | ||
| 4 | INSTANCE; | ||
| 5 | |||
| 6 | @Override | ||
| 7 | public <T extends Translatable> T translate(T translatable) { | ||
| 8 | return translatable; | ||
| 9 | } | ||
| 10 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java deleted file mode 100644 index 5b79b79..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java +++ /dev/null | |||
| @@ -1,25 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 4 | |||
| 5 | public enum AccessModifier { | ||
| 6 | UNCHANGED, PUBLIC, PROTECTED, PRIVATE; | ||
| 7 | |||
| 8 | public String getFormattedName() { | ||
| 9 | return "ACC:" + super.toString(); | ||
| 10 | } | ||
| 11 | |||
| 12 | public AccessFlags transform(AccessFlags access) { | ||
| 13 | switch (this) { | ||
| 14 | case PUBLIC: | ||
| 15 | return access.setPublic(); | ||
| 16 | case PROTECTED: | ||
| 17 | return access.setProtected(); | ||
| 18 | case PRIVATE: | ||
| 19 | return access.setPrivate(); | ||
| 20 | case UNCHANGED: | ||
| 21 | default: | ||
| 22 | return access; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java deleted file mode 100644 index e1a3253..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java +++ /dev/null | |||
| @@ -1,24 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | import java.util.stream.Stream; | ||
| 7 | |||
| 8 | public interface EntryMap<T> { | ||
| 9 | void insert(Entry<?> entry, T value); | ||
| 10 | |||
| 11 | @Nullable | ||
| 12 | T remove(Entry<?> entry); | ||
| 13 | |||
| 14 | @Nullable | ||
| 15 | T get(Entry<?> entry); | ||
| 16 | |||
| 17 | default boolean contains(Entry<?> entry) { | ||
| 18 | return get(entry) != null; | ||
| 19 | } | ||
| 20 | |||
| 21 | Stream<Entry<?>> getAllEntries(); | ||
| 22 | |||
| 23 | boolean isEmpty(); | ||
| 24 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java deleted file mode 100644 index c607817..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java +++ /dev/null | |||
| @@ -1,75 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import javax.annotation.Nonnull; | ||
| 4 | import javax.annotation.Nullable; | ||
| 5 | |||
| 6 | public class EntryMapping { | ||
| 7 | private final String targetName; | ||
| 8 | private final AccessModifier accessModifier; | ||
| 9 | private final @Nullable String javadoc; | ||
| 10 | |||
| 11 | public EntryMapping(@Nonnull String targetName) { | ||
| 12 | this(targetName, AccessModifier.UNCHANGED); | ||
| 13 | } | ||
| 14 | |||
| 15 | public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) { | ||
| 16 | this(targetName, AccessModifier.UNCHANGED, javadoc); | ||
| 17 | } | ||
| 18 | |||
| 19 | public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) { | ||
| 20 | this(targetName, accessModifier, null); | ||
| 21 | } | ||
| 22 | |||
| 23 | public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier, @Nullable String javadoc) { | ||
| 24 | this.targetName = targetName; | ||
| 25 | this.accessModifier = accessModifier; | ||
| 26 | this.javadoc = javadoc; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Nonnull | ||
| 30 | public String getTargetName() { | ||
| 31 | return targetName; | ||
| 32 | } | ||
| 33 | |||
| 34 | @Nonnull | ||
| 35 | public AccessModifier getAccessModifier() { | ||
| 36 | if (accessModifier == null) { | ||
| 37 | return AccessModifier.UNCHANGED; | ||
| 38 | } | ||
| 39 | return accessModifier; | ||
| 40 | } | ||
| 41 | |||
| 42 | @Nullable | ||
| 43 | public String getJavadoc() { | ||
| 44 | return javadoc; | ||
| 45 | } | ||
| 46 | |||
| 47 | public EntryMapping withName(String newName) { | ||
| 48 | return new EntryMapping(newName, accessModifier, javadoc); | ||
| 49 | } | ||
| 50 | |||
| 51 | public EntryMapping withModifier(AccessModifier newModifier) { | ||
| 52 | return new EntryMapping(targetName, newModifier, javadoc); | ||
| 53 | } | ||
| 54 | |||
| 55 | public EntryMapping withDocs(String newDocs) { | ||
| 56 | return new EntryMapping(targetName, accessModifier, newDocs); | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public boolean equals(Object obj) { | ||
| 61 | if (obj == this) return true; | ||
| 62 | |||
| 63 | if (obj instanceof EntryMapping) { | ||
| 64 | EntryMapping mapping = (EntryMapping) obj; | ||
| 65 | return mapping.targetName.equals(targetName) && mapping.accessModifier.equals(accessModifier); | ||
| 66 | } | ||
| 67 | |||
| 68 | return false; | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public int hashCode() { | ||
| 73 | return targetName.hashCode() + accessModifier.hashCode() * 31; | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java deleted file mode 100644 index ad36c97..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java +++ /dev/null | |||
| @@ -1,105 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 4 | import cuchaz.enigma.translation.MappingTranslator; | ||
| 5 | import cuchaz.enigma.translation.Translatable; | ||
| 6 | import cuchaz.enigma.translation.Translator; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 9 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 11 | |||
| 12 | import javax.annotation.Nullable; | ||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.function.UnaryOperator; | ||
| 15 | import java.util.stream.Stream; | ||
| 16 | |||
| 17 | public class EntryRemapper { | ||
| 18 | private final DeltaTrackingTree<EntryMapping> obfToDeobf; | ||
| 19 | |||
| 20 | private final EntryResolver obfResolver; | ||
| 21 | private final Translator deobfuscator; | ||
| 22 | |||
| 23 | private final MappingValidator validator; | ||
| 24 | |||
| 25 | private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf) { | ||
| 26 | this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf); | ||
| 27 | |||
| 28 | this.obfResolver = jarIndex.getEntryResolver(); | ||
| 29 | |||
| 30 | this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver); | ||
| 31 | |||
| 32 | this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex); | ||
| 33 | } | ||
| 34 | |||
| 35 | public static EntryRemapper mapped(JarIndex index, EntryTree<EntryMapping> obfToDeobf) { | ||
| 36 | return new EntryRemapper(index, obfToDeobf); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static EntryRemapper empty(JarIndex index) { | ||
| 40 | return new EntryRemapper(index, new HashEntryTree<>()); | ||
| 41 | } | ||
| 42 | |||
| 43 | public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { | ||
| 44 | mapFromObf(obfuscatedEntry, deobfMapping, true); | ||
| 45 | } | ||
| 46 | |||
| 47 | public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { | ||
| 48 | Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); | ||
| 49 | |||
| 50 | if (renaming && deobfMapping != null) { | ||
| 51 | for (E resolvedEntry : resolvedEntries) { | ||
| 52 | validator.validateRename(resolvedEntry, deobfMapping.getTargetName()); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | for (E resolvedEntry : resolvedEntries) { | ||
| 57 | obfToDeobf.insert(resolvedEntry, deobfMapping); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | public void removeByObf(Entry<?> obfuscatedEntry) { | ||
| 62 | mapFromObf(obfuscatedEntry, null); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Nullable | ||
| 66 | public EntryMapping getDeobfMapping(Entry<?> entry) { | ||
| 67 | return obfToDeobf.get(entry); | ||
| 68 | } | ||
| 69 | |||
| 70 | public boolean hasDeobfMapping(Entry<?> obfEntry) { | ||
| 71 | return obfToDeobf.contains(obfEntry); | ||
| 72 | } | ||
| 73 | |||
| 74 | public <T extends Translatable> T deobfuscate(T translatable) { | ||
| 75 | return deobfuscator.translate(translatable); | ||
| 76 | } | ||
| 77 | |||
| 78 | public Translator getDeobfuscator() { | ||
| 79 | return deobfuscator; | ||
| 80 | } | ||
| 81 | |||
| 82 | public Stream<Entry<?>> getObfEntries() { | ||
| 83 | return obfToDeobf.getAllEntries(); | ||
| 84 | } | ||
| 85 | |||
| 86 | public Collection<Entry<?>> getObfChildren(Entry<?> obfuscatedEntry) { | ||
| 87 | return obfToDeobf.getChildren(obfuscatedEntry); | ||
| 88 | } | ||
| 89 | |||
| 90 | public DeltaTrackingTree<EntryMapping> getObfToDeobf() { | ||
| 91 | return obfToDeobf; | ||
| 92 | } | ||
| 93 | |||
| 94 | public MappingDelta<EntryMapping> takeMappingDelta() { | ||
| 95 | return obfToDeobf.takeDelta(); | ||
| 96 | } | ||
| 97 | |||
| 98 | public boolean isDirty() { | ||
| 99 | return obfToDeobf.isDirty(); | ||
| 100 | } | ||
| 101 | |||
| 102 | public EntryResolver getObfResolver() { | ||
| 103 | return obfResolver; | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java deleted file mode 100644 index 521f72d..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java +++ /dev/null | |||
| @@ -1,41 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import com.google.common.collect.Streams; | ||
| 4 | import cuchaz.enigma.analysis.EntryReference; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 7 | |||
| 8 | import java.util.Collection; | ||
| 9 | import java.util.Set; | ||
| 10 | import java.util.stream.Collectors; | ||
| 11 | |||
| 12 | public interface EntryResolver { | ||
| 13 | <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy); | ||
| 14 | |||
| 15 | default <E extends Entry<?>> E resolveFirstEntry(E entry, ResolutionStrategy strategy) { | ||
| 16 | return resolveEntry(entry, strategy).stream().findFirst().orElse(entry); | ||
| 17 | } | ||
| 18 | |||
| 19 | default <E extends Entry<?>, C extends Entry<?>> Collection<EntryReference<E, C>> resolveReference(EntryReference<E, C> reference, ResolutionStrategy strategy) { | ||
| 20 | Collection<E> entry = resolveEntry(reference.entry, strategy); | ||
| 21 | if (reference.context != null) { | ||
| 22 | Collection<C> context = resolveEntry(reference.context, strategy); | ||
| 23 | return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference)) | ||
| 24 | .collect(Collectors.toList()); | ||
| 25 | } else { | ||
| 26 | return entry.stream() | ||
| 27 | .map(e -> new EntryReference<>(e, null, reference)) | ||
| 28 | .collect(Collectors.toList()); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | default <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> resolveFirstReference(EntryReference<E, C> reference, ResolutionStrategy strategy) { | ||
| 33 | E entry = resolveFirstEntry(reference.entry, strategy); | ||
| 34 | C context = resolveFirstEntry(reference.context, strategy); | ||
| 35 | return new EntryReference<>(entry, context, reference); | ||
| 36 | } | ||
| 37 | |||
| 38 | Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry); | ||
| 39 | |||
| 40 | Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry); | ||
| 41 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java deleted file mode 100644 index 78231dd..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java +++ /dev/null | |||
| @@ -1,227 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import com.google.common.collect.Sets; | ||
| 4 | import cuchaz.enigma.analysis.IndexTreeBuilder; | ||
| 5 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 6 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 7 | import cuchaz.enigma.analysis.index.BridgeMethodIndex; | ||
| 8 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 9 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 10 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 11 | import cuchaz.enigma.translation.VoidTranslator; | ||
| 12 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 16 | |||
| 17 | import javax.annotation.Nullable; | ||
| 18 | import java.util.*; | ||
| 19 | import java.util.stream.Collectors; | ||
| 20 | |||
| 21 | public class IndexEntryResolver implements EntryResolver { | ||
| 22 | private final EntryIndex entryIndex; | ||
| 23 | private final InheritanceIndex inheritanceIndex; | ||
| 24 | private final BridgeMethodIndex bridgeMethodIndex; | ||
| 25 | |||
| 26 | private final IndexTreeBuilder treeBuilder; | ||
| 27 | |||
| 28 | public IndexEntryResolver(JarIndex index) { | ||
| 29 | this.entryIndex = index.getEntryIndex(); | ||
| 30 | this.inheritanceIndex = index.getInheritanceIndex(); | ||
| 31 | this.bridgeMethodIndex = index.getBridgeMethodIndex(); | ||
| 32 | |||
| 33 | this.treeBuilder = new IndexTreeBuilder(index); | ||
| 34 | } | ||
| 35 | |||
| 36 | @Override | ||
| 37 | @SuppressWarnings("unchecked") | ||
| 38 | public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) { | ||
| 39 | if (entry == null) { | ||
| 40 | return Collections.emptySet(); | ||
| 41 | } | ||
| 42 | |||
| 43 | Entry<ClassEntry> classChild = getClassChild(entry); | ||
| 44 | if (classChild != null && !(classChild instanceof ClassEntry)) { | ||
| 45 | AccessFlags access = entryIndex.getEntryAccess(classChild); | ||
| 46 | |||
| 47 | // If we're looking for the closest and this entry exists, we're done looking | ||
| 48 | if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) { | ||
| 49 | return Collections.singleton(entry); | ||
| 50 | } | ||
| 51 | |||
| 52 | if (access == null || !access.isPrivate()) { | ||
| 53 | Collection<Entry<ClassEntry>> resolvedChildren = resolveChildEntry(classChild, strategy); | ||
| 54 | if (!resolvedChildren.isEmpty()) { | ||
| 55 | return resolvedChildren.stream() | ||
| 56 | .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild)) | ||
| 57 | .collect(Collectors.toList()); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | return Collections.singleton(entry); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Nullable | ||
| 66 | private Entry<ClassEntry> getClassChild(Entry<?> entry) { | ||
| 67 | if (entry instanceof ClassEntry) { | ||
| 68 | return null; | ||
| 69 | } | ||
| 70 | |||
| 71 | // get the entry in the hierarchy that is the child of a class | ||
| 72 | List<Entry<?>> ancestry = entry.getAncestry(); | ||
| 73 | for (int i = ancestry.size() - 1; i > 0; i--) { | ||
| 74 | Entry<?> child = ancestry.get(i); | ||
| 75 | Entry<ClassEntry> cast = child.castParent(ClassEntry.class); | ||
| 76 | if (cast != null && !(cast instanceof ClassEntry)) { | ||
| 77 | // we found the entry which is a child of a class, we are now able to resolve the owner of this entry | ||
| 78 | return cast; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | return null; | ||
| 83 | } | ||
| 84 | |||
| 85 | private Set<Entry<ClassEntry>> resolveChildEntry(Entry<ClassEntry> entry, ResolutionStrategy strategy) { | ||
| 86 | ClassEntry ownerClass = entry.getParent(); | ||
| 87 | |||
| 88 | if (entry instanceof MethodEntry) { | ||
| 89 | MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromSpecialized((MethodEntry) entry); | ||
| 90 | if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) { | ||
| 91 | Set<Entry<ClassEntry>> resolvedBridge = resolveChildEntry(bridgeMethod, strategy); | ||
| 92 | if (!resolvedBridge.isEmpty()) { | ||
| 93 | return resolvedBridge; | ||
| 94 | } else { | ||
| 95 | return Collections.singleton(bridgeMethod); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | Set<Entry<ClassEntry>> resolvedEntries = new HashSet<>(); | ||
| 101 | |||
| 102 | for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) { | ||
| 103 | Entry<ClassEntry> parentEntry = entry.withParent(parentClass); | ||
| 104 | |||
| 105 | if (strategy == ResolutionStrategy.RESOLVE_ROOT) { | ||
| 106 | resolvedEntries.addAll(resolveRoot(parentEntry, strategy)); | ||
| 107 | } else { | ||
| 108 | resolvedEntries.addAll(resolveClosest(parentEntry, strategy)); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | return resolvedEntries; | ||
| 113 | } | ||
| 114 | |||
| 115 | private Collection<Entry<ClassEntry>> resolveRoot(Entry<ClassEntry> entry, ResolutionStrategy strategy) { | ||
| 116 | // When resolving root, we want to first look for the lowest entry before returning ourselves | ||
| 117 | Set<Entry<ClassEntry>> parentResolution = resolveChildEntry(entry, strategy); | ||
| 118 | |||
| 119 | if (parentResolution.isEmpty()) { | ||
| 120 | AccessFlags parentAccess = entryIndex.getEntryAccess(entry); | ||
| 121 | if (parentAccess != null && !parentAccess.isPrivate()) { | ||
| 122 | return Collections.singleton(entry); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | return parentResolution; | ||
| 127 | } | ||
| 128 | |||
| 129 | private Collection<Entry<ClassEntry>> resolveClosest(Entry<ClassEntry> entry, ResolutionStrategy strategy) { | ||
| 130 | // When resolving closest, we want to first check if we exist before looking further down | ||
| 131 | AccessFlags parentAccess = entryIndex.getEntryAccess(entry); | ||
| 132 | if (parentAccess != null && !parentAccess.isPrivate()) { | ||
| 133 | return Collections.singleton(entry); | ||
| 134 | } else { | ||
| 135 | return resolveChildEntry(entry, strategy); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | @Override | ||
| 140 | public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) { | ||
| 141 | MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class); | ||
| 142 | if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) { | ||
| 143 | return Collections.singleton(entry); | ||
| 144 | } | ||
| 145 | |||
| 146 | Set<MethodEntry> equivalentMethods = resolveEquivalentMethods(relevantMethod); | ||
| 147 | Set<Entry<?>> equivalentEntries = new HashSet<>(equivalentMethods.size()); | ||
| 148 | |||
| 149 | for (MethodEntry equivalentMethod : equivalentMethods) { | ||
| 150 | Entry<?> equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod); | ||
| 151 | equivalentEntries.add(equivalentEntry); | ||
| 152 | } | ||
| 153 | |||
| 154 | return equivalentEntries; | ||
| 155 | } | ||
| 156 | |||
| 157 | @Override | ||
| 158 | public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) { | ||
| 159 | AccessFlags access = entryIndex.getMethodAccess(methodEntry); | ||
| 160 | if (access == null) { | ||
| 161 | throw new IllegalArgumentException("Could not find method " + methodEntry); | ||
| 162 | } | ||
| 163 | |||
| 164 | if (!canInherit(methodEntry, access)) { | ||
| 165 | return Collections.singleton(methodEntry); | ||
| 166 | } | ||
| 167 | |||
| 168 | Set<MethodEntry> methodEntries = Sets.newHashSet(); | ||
| 169 | resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry)); | ||
| 170 | return methodEntries; | ||
| 171 | } | ||
| 172 | |||
| 173 | private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { | ||
| 174 | MethodEntry methodEntry = node.getMethodEntry(); | ||
| 175 | if (methodEntries.contains(methodEntry)) { | ||
| 176 | return; | ||
| 177 | } | ||
| 178 | |||
| 179 | AccessFlags flags = entryIndex.getMethodAccess(methodEntry); | ||
| 180 | if (flags != null && canInherit(methodEntry, flags)) { | ||
| 181 | // collect the entry | ||
| 182 | methodEntries.add(methodEntry); | ||
| 183 | } | ||
| 184 | |||
| 185 | // look at bridge methods! | ||
| 186 | MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry); | ||
| 187 | while (bridgedMethod != null) { | ||
| 188 | methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); | ||
| 189 | bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod); | ||
| 190 | } | ||
| 191 | |||
| 192 | // look at interface methods too | ||
| 193 | for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) { | ||
| 194 | resolveEquivalentMethods(methodEntries, implementationsNode); | ||
| 195 | } | ||
| 196 | |||
| 197 | // recurse | ||
| 198 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 199 | resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { | ||
| 204 | MethodEntry methodEntry = node.getMethodEntry(); | ||
| 205 | AccessFlags flags = entryIndex.getMethodAccess(methodEntry); | ||
| 206 | if (flags != null && !flags.isPrivate() && !flags.isStatic()) { | ||
| 207 | // collect the entry | ||
| 208 | methodEntries.add(methodEntry); | ||
| 209 | } | ||
| 210 | |||
| 211 | // look at bridge methods! | ||
| 212 | MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry); | ||
| 213 | while (bridgedMethod != null) { | ||
| 214 | methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); | ||
| 215 | bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod); | ||
| 216 | } | ||
| 217 | |||
| 218 | // recurse | ||
| 219 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 220 | resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | private boolean canInherit(MethodEntry entry, AccessFlags access) { | ||
| 225 | return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal(); | ||
| 226 | } | ||
| 227 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java deleted file mode 100644 index 1407bb6..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java +++ /dev/null | |||
| @@ -1,54 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translatable; | ||
| 4 | import cuchaz.enigma.translation.Translator; | ||
| 5 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 6 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 9 | |||
| 10 | import java.util.stream.Stream; | ||
| 11 | |||
| 12 | public class MappingDelta<T> implements Translatable { | ||
| 13 | public static final Object PLACEHOLDER = new Object(); | ||
| 14 | |||
| 15 | private final EntryTree<T> baseMappings; | ||
| 16 | |||
| 17 | private final EntryTree<Object> changes; | ||
| 18 | |||
| 19 | public MappingDelta(EntryTree<T> baseMappings, EntryTree<Object> changes) { | ||
| 20 | this.baseMappings = baseMappings; | ||
| 21 | this.changes = changes; | ||
| 22 | } | ||
| 23 | |||
| 24 | public MappingDelta(EntryTree<T> baseMappings) { | ||
| 25 | this(baseMappings, new HashEntryTree<>()); | ||
| 26 | } | ||
| 27 | |||
| 28 | public static <T> MappingDelta<T> added(EntryTree<T> mappings) { | ||
| 29 | EntryTree<Object> changes = new HashEntryTree<>(); | ||
| 30 | mappings.getAllEntries().forEach(entry -> changes.insert(entry, PLACEHOLDER)); | ||
| 31 | |||
| 32 | return new MappingDelta<>(new HashEntryTree<>(), changes); | ||
| 33 | } | ||
| 34 | |||
| 35 | public EntryTree<T> getBaseMappings() { | ||
| 36 | return baseMappings; | ||
| 37 | } | ||
| 38 | |||
| 39 | public EntryTree<?> getChanges() { | ||
| 40 | return changes; | ||
| 41 | } | ||
| 42 | |||
| 43 | public Stream<Entry<?>> getChangedRoots() { | ||
| 44 | return changes.getRootNodes().map(EntryTreeNode::getEntry); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public MappingDelta<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 49 | return new MappingDelta<>( | ||
| 50 | translator.translate(baseMappings), | ||
| 51 | translator.translate(changes) | ||
| 52 | ); | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java deleted file mode 100644 index e40bfe7..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import com.google.gson.annotations.SerializedName; | ||
| 4 | |||
| 5 | public enum MappingFileNameFormat { | ||
| 6 | @SerializedName("by_obf") | ||
| 7 | BY_OBF, | ||
| 8 | @SerializedName("by_deobf") | ||
| 9 | BY_DEOBF | ||
| 10 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java deleted file mode 100644 index 5d39e3d..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java +++ /dev/null | |||
| @@ -1,32 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | public class MappingPair<E extends Entry<?>, M> { | ||
| 8 | private final E entry; | ||
| 9 | private M mapping; | ||
| 10 | |||
| 11 | public MappingPair(E entry, @Nullable M mapping) { | ||
| 12 | this.entry = entry; | ||
| 13 | this.mapping = mapping; | ||
| 14 | } | ||
| 15 | |||
| 16 | public MappingPair(E entry) { | ||
| 17 | this(entry, null); | ||
| 18 | } | ||
| 19 | |||
| 20 | public E getEntry() { | ||
| 21 | return entry; | ||
| 22 | } | ||
| 23 | |||
| 24 | @Nullable | ||
| 25 | public M getMapping() { | ||
| 26 | return mapping; | ||
| 27 | } | ||
| 28 | |||
| 29 | public void setMapping(M mapping) { | ||
| 30 | this.mapping = mapping; | ||
| 31 | } | ||
| 32 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java deleted file mode 100644 index 07065d6..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import com.google.gson.annotations.SerializedName; | ||
| 4 | |||
| 5 | public class MappingSaveParameters { | ||
| 6 | @SerializedName("file_name_format") | ||
| 7 | private final MappingFileNameFormat fileNameFormat; | ||
| 8 | |||
| 9 | public MappingSaveParameters(MappingFileNameFormat fileNameFormat) { | ||
| 10 | this.fileNameFormat = fileNameFormat; | ||
| 11 | } | ||
| 12 | |||
| 13 | public MappingFileNameFormat getFileNameFormat() { | ||
| 14 | return fileNameFormat; | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java deleted file mode 100644 index dffcb0c..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java +++ /dev/null | |||
| @@ -1,76 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 5 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 6 | import cuchaz.enigma.translation.Translator; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | |||
| 11 | import java.util.Collection; | ||
| 12 | import java.util.HashSet; | ||
| 13 | import java.util.stream.Collectors; | ||
| 14 | |||
| 15 | public class MappingValidator { | ||
| 16 | private final EntryTree<EntryMapping> obfToDeobf; | ||
| 17 | private final Translator deobfuscator; | ||
| 18 | private final JarIndex index; | ||
| 19 | |||
| 20 | public MappingValidator(EntryTree<EntryMapping> obfToDeobf, Translator deobfuscator, JarIndex index) { | ||
| 21 | this.obfToDeobf = obfToDeobf; | ||
| 22 | this.deobfuscator = deobfuscator; | ||
| 23 | this.index = index; | ||
| 24 | } | ||
| 25 | |||
| 26 | public void validateRename(Entry<?> entry, String name) throws IllegalNameException { | ||
| 27 | Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); | ||
| 28 | for (Entry<?> equivalentEntry : equivalentEntries) { | ||
| 29 | equivalentEntry.validateName(name); | ||
| 30 | validateUnique(equivalentEntry, name); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | private void validateUnique(Entry<?> entry, String name) { | ||
| 35 | ClassEntry containingClass = entry.getContainingClass(); | ||
| 36 | Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass); | ||
| 37 | |||
| 38 | for (ClassEntry relatedClass : relatedClasses) { | ||
| 39 | Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass); | ||
| 40 | Entry<?> translatedEntry = deobfuscator.translate(relatedEntry); | ||
| 41 | |||
| 42 | Collection<Entry<?>> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream() | ||
| 43 | .map(deobfuscator::translate) | ||
| 44 | .collect(Collectors.toList()); | ||
| 45 | |||
| 46 | if (!isUnique(translatedEntry, translatedSiblings, name)) { | ||
| 47 | Entry<?> parent = translatedEntry.getParent(); | ||
| 48 | if (parent != null) { | ||
| 49 | throw new IllegalNameException(name, "Name is not unique in " + parent + "!"); | ||
| 50 | } else { | ||
| 51 | throw new IllegalNameException(name, "Name is not unique!"); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) { | ||
| 58 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | ||
| 59 | |||
| 60 | Collection<ClassEntry> relatedClasses = new HashSet<>(); | ||
| 61 | relatedClasses.add(classEntry); | ||
| 62 | relatedClasses.addAll(inheritanceIndex.getChildren(classEntry)); | ||
| 63 | relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry)); | ||
| 64 | |||
| 65 | return relatedClasses; | ||
| 66 | } | ||
| 67 | |||
| 68 | private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) { | ||
| 69 | for (Entry<?> sibling : siblings) { | ||
| 70 | if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) { | ||
| 71 | return false; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | return true; | ||
| 75 | } | ||
| 76 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java deleted file mode 100644 index 5d9794f..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java +++ /dev/null | |||
| @@ -1,99 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.mapping; | ||
| 13 | |||
| 14 | import cuchaz.enigma.ProgressListener; | ||
| 15 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 16 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 17 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 23 | |||
| 24 | import java.util.Collection; | ||
| 25 | import java.util.HashMap; | ||
| 26 | import java.util.Map; | ||
| 27 | import java.util.stream.Collectors; | ||
| 28 | |||
| 29 | public class MappingsChecker { | ||
| 30 | private final JarIndex index; | ||
| 31 | private final EntryTree<EntryMapping> mappings; | ||
| 32 | |||
| 33 | public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) { | ||
| 34 | this.index = index; | ||
| 35 | this.mappings = mappings; | ||
| 36 | } | ||
| 37 | |||
| 38 | public Dropped dropBrokenMappings(ProgressListener progress) { | ||
| 39 | Dropped dropped = new Dropped(); | ||
| 40 | |||
| 41 | Collection<Entry<?>> obfEntries = mappings.getAllEntries() | ||
| 42 | .filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry) | ||
| 43 | .collect(Collectors.toList()); | ||
| 44 | |||
| 45 | progress.init(obfEntries.size(), "Checking for dropped mappings"); | ||
| 46 | |||
| 47 | int steps = 0; | ||
| 48 | for (Entry<?> entry : obfEntries) { | ||
| 49 | progress.step(steps++, entry.toString()); | ||
| 50 | tryDropEntry(dropped, entry); | ||
| 51 | } | ||
| 52 | |||
| 53 | dropped.apply(mappings); | ||
| 54 | |||
| 55 | return dropped; | ||
| 56 | } | ||
| 57 | |||
| 58 | private void tryDropEntry(Dropped dropped, Entry<?> entry) { | ||
| 59 | if (shouldDropEntry(entry)) { | ||
| 60 | EntryMapping mapping = mappings.get(entry); | ||
| 61 | if (mapping != null) { | ||
| 62 | dropped.drop(entry, mapping); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | private boolean shouldDropEntry(Entry<?> entry) { | ||
| 68 | if (!index.getEntryIndex().hasEntry(entry)) { | ||
| 69 | return true; | ||
| 70 | } | ||
| 71 | Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 72 | return !resolvedEntries.contains(entry); | ||
| 73 | } | ||
| 74 | |||
| 75 | public static class Dropped { | ||
| 76 | private final Map<Entry<?>, String> droppedMappings = new HashMap<>(); | ||
| 77 | |||
| 78 | public void drop(Entry<?> entry, EntryMapping mapping) { | ||
| 79 | droppedMappings.put(entry, mapping.getTargetName()); | ||
| 80 | } | ||
| 81 | |||
| 82 | void apply(EntryTree<EntryMapping> mappings) { | ||
| 83 | for (Entry<?> entry : droppedMappings.keySet()) { | ||
| 84 | EntryTreeNode<EntryMapping> node = mappings.findNode(entry); | ||
| 85 | if (node == null) { | ||
| 86 | continue; | ||
| 87 | } | ||
| 88 | |||
| 89 | for (Entry<?> childEntry : node.getChildrenRecursively()) { | ||
| 90 | mappings.remove(childEntry); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | public Map<Entry<?>, String> getDroppedMappings() { | ||
| 96 | return droppedMappings; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java deleted file mode 100644 index 5bc2f67..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java +++ /dev/null | |||
| @@ -1,53 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.mapping; | ||
| 13 | |||
| 14 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 16 | |||
| 17 | import java.util.Arrays; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.regex.Pattern; | ||
| 20 | |||
| 21 | public class NameValidator { | ||
| 22 | private static final Pattern IDENTIFIER_PATTERN; | ||
| 23 | private static final Pattern CLASS_PATTERN; | ||
| 24 | private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList( | ||
| 25 | "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", | ||
| 26 | "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", | ||
| 27 | "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", | ||
| 28 | "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", | ||
| 29 | "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_" | ||
| 30 | ); | ||
| 31 | |||
| 32 | static { | ||
| 33 | String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; | ||
| 34 | IDENTIFIER_PATTERN = Pattern.compile(identifierRegex); | ||
| 35 | CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); | ||
| 36 | } | ||
| 37 | |||
| 38 | public static void validateClassName(String name) { | ||
| 39 | if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { | ||
| 40 | throw new IllegalNameException(name, "This doesn't look like a legal class name"); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | public static void validateIdentifier(String name) { | ||
| 45 | if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { | ||
| 46 | throw new IllegalNameException(name, "This doesn't look like a legal identifier"); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | public static boolean isReserved(String name) { | ||
| 51 | return ILLEGAL_IDENTIFIERS.contains(name); | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java deleted file mode 100644 index 1c28e02..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java +++ /dev/null | |||
| @@ -1,6 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | public enum ResolutionStrategy { | ||
| 4 | RESOLVE_ROOT, | ||
| 5 | RESOLVE_CLOSEST | ||
| 6 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java deleted file mode 100644 index 2eab55f..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java +++ /dev/null | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 4 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 5 | |||
| 6 | import java.util.Collection; | ||
| 7 | import java.util.Collections; | ||
| 8 | import java.util.Set; | ||
| 9 | |||
| 10 | public enum VoidEntryResolver implements EntryResolver { | ||
| 11 | INSTANCE; | ||
| 12 | |||
| 13 | @Override | ||
| 14 | public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) { | ||
| 15 | return Collections.singleton(entry); | ||
| 16 | } | ||
| 17 | |||
| 18 | @Override | ||
| 19 | public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) { | ||
| 20 | return Collections.singleton(entry); | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) { | ||
| 25 | return Collections.singleton(methodEntry); | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java deleted file mode 100644 index af92ffb..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | public class EnigmaFormat { | ||
| 4 | public static final String COMMENT = "COMMENT"; | ||
| 5 | public static final String CLASS = "CLASS"; | ||
| 6 | public static final String FIELD = "FIELD"; | ||
| 7 | public static final String METHOD = "METHOD"; | ||
| 8 | public static final String PARAMETER = "ARG"; | ||
| 9 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java deleted file mode 100644 index 53bbaa3..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java +++ /dev/null | |||
| @@ -1,319 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import com.google.common.base.Charsets; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 6 | import cuchaz.enigma.translation.mapping.AccessModifier; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 8 | import cuchaz.enigma.translation.mapping.MappingPair; | ||
| 9 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 10 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 11 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 12 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 13 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 15 | import cuchaz.enigma.utils.I18n; | ||
| 16 | |||
| 17 | import javax.annotation.Nullable; | ||
| 18 | import java.io.IOException; | ||
| 19 | import java.nio.file.FileSystem; | ||
| 20 | import java.nio.file.FileSystems; | ||
| 21 | import java.nio.file.Files; | ||
| 22 | import java.nio.file.Path; | ||
| 23 | import java.util.ArrayDeque; | ||
| 24 | import java.util.Arrays; | ||
| 25 | import java.util.Deque; | ||
| 26 | import java.util.List; | ||
| 27 | import java.util.Locale; | ||
| 28 | import java.util.stream.Collectors; | ||
| 29 | |||
| 30 | public enum EnigmaMappingsReader implements MappingsReader { | ||
| 31 | FILE { | ||
| 32 | @Override | ||
| 33 | public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { | ||
| 34 | progress.init(1, I18n.translate("progress.mappings.enigma_file.loading")); | ||
| 35 | |||
| 36 | EntryTree<EntryMapping> mappings = new HashEntryTree<>(); | ||
| 37 | readFile(path, mappings); | ||
| 38 | |||
| 39 | progress.step(1, I18n.translate("progress.mappings.enigma_file.done")); | ||
| 40 | |||
| 41 | return mappings; | ||
| 42 | } | ||
| 43 | }, | ||
| 44 | DIRECTORY { | ||
| 45 | @Override | ||
| 46 | public EntryTree<EntryMapping> read(Path root, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { | ||
| 47 | EntryTree<EntryMapping> mappings = new HashEntryTree<>(); | ||
| 48 | |||
| 49 | List<Path> files = Files.walk(root) | ||
| 50 | .filter(f -> !Files.isDirectory(f)) | ||
| 51 | .filter(f -> f.toString().endsWith(".mapping")) | ||
| 52 | .collect(Collectors.toList()); | ||
| 53 | |||
| 54 | progress.init(files.size(), I18n.translate("progress.mappings.enigma_directory.loading")); | ||
| 55 | int step = 0; | ||
| 56 | |||
| 57 | for (Path file : files) { | ||
| 58 | progress.step(step++, root.relativize(file).toString()); | ||
| 59 | if (Files.isHidden(file)) { | ||
| 60 | continue; | ||
| 61 | } | ||
| 62 | readFile(file, mappings); | ||
| 63 | } | ||
| 64 | |||
| 65 | return mappings; | ||
| 66 | } | ||
| 67 | }, | ||
| 68 | ZIP { | ||
| 69 | @Override | ||
| 70 | public EntryTree<EntryMapping> read(Path zip, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { | ||
| 71 | try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) { | ||
| 72 | return DIRECTORY.read(fs.getPath("/"), progress, saveParameters); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | }; | ||
| 76 | |||
| 77 | protected void readFile(Path path, EntryTree<EntryMapping> mappings) throws IOException, MappingParseException { | ||
| 78 | List<String> lines = Files.readAllLines(path, Charsets.UTF_8); | ||
| 79 | Deque<MappingPair<?, RawEntryMapping>> mappingStack = new ArrayDeque<>(); | ||
| 80 | |||
| 81 | for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { | ||
| 82 | String line = lines.get(lineNumber); | ||
| 83 | int indentation = countIndentation(line); | ||
| 84 | |||
| 85 | line = formatLine(line); | ||
| 86 | if (line == null) { | ||
| 87 | continue; | ||
| 88 | } | ||
| 89 | |||
| 90 | cleanMappingStack(indentation, mappingStack, mappings); | ||
| 91 | |||
| 92 | try { | ||
| 93 | MappingPair<?, RawEntryMapping> pair = parseLine(mappingStack.peek(), line); | ||
| 94 | if (pair != null) { | ||
| 95 | mappingStack.push(pair); | ||
| 96 | if (pair.getMapping() != null) { | ||
| 97 | |||
| 98 | } | ||
| 99 | } | ||
| 100 | } catch (Throwable t) { | ||
| 101 | t.printStackTrace(); | ||
| 102 | throw new MappingParseException(path::toString, lineNumber, t.toString()); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | // Clean up rest | ||
| 107 | cleanMappingStack(0, mappingStack, mappings); | ||
| 108 | } | ||
| 109 | |||
| 110 | private void cleanMappingStack(int indentation, Deque<MappingPair<?, RawEntryMapping>> mappingStack, EntryTree<EntryMapping> mappings) { | ||
| 111 | while (indentation < mappingStack.size()) { | ||
| 112 | MappingPair<?, RawEntryMapping> pair = mappingStack.pop(); | ||
| 113 | if (pair.getMapping() != null) { | ||
| 114 | mappings.insert(pair.getEntry(), pair.getMapping().bake()); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | @Nullable | ||
| 120 | private String formatLine(String line) { | ||
| 121 | line = stripComment(line); | ||
| 122 | line = line.trim(); | ||
| 123 | |||
| 124 | if (line.isEmpty()) { | ||
| 125 | return null; | ||
| 126 | } | ||
| 127 | |||
| 128 | return line; | ||
| 129 | } | ||
| 130 | |||
| 131 | private String stripComment(String line) { | ||
| 132 | //Dont support comments on javadoc lines | ||
| 133 | if (line.trim().startsWith(EnigmaFormat.COMMENT)) { | ||
| 134 | return line; | ||
| 135 | } | ||
| 136 | |||
| 137 | int commentPos = line.indexOf('#'); | ||
| 138 | if (commentPos >= 0) { | ||
| 139 | return line.substring(0, commentPos); | ||
| 140 | } | ||
| 141 | return line; | ||
| 142 | } | ||
| 143 | |||
| 144 | private int countIndentation(String line) { | ||
| 145 | int indent = 0; | ||
| 146 | for (int i = 0; i < line.length(); i++) { | ||
| 147 | if (line.charAt(i) != '\t') { | ||
| 148 | break; | ||
| 149 | } | ||
| 150 | indent++; | ||
| 151 | } | ||
| 152 | return indent; | ||
| 153 | } | ||
| 154 | |||
| 155 | private MappingPair<?, RawEntryMapping> parseLine(@Nullable MappingPair<?, RawEntryMapping> parent, String line) { | ||
| 156 | String[] tokens = line.trim().split("\\s"); | ||
| 157 | String keyToken = tokens[0].toUpperCase(Locale.ROOT); | ||
| 158 | Entry<?> parentEntry = parent == null ? null : parent.getEntry(); | ||
| 159 | |||
| 160 | switch (keyToken) { | ||
| 161 | case EnigmaFormat.CLASS: | ||
| 162 | return parseClass(parentEntry, tokens); | ||
| 163 | case EnigmaFormat.FIELD: | ||
| 164 | return parseField(parentEntry, tokens); | ||
| 165 | case EnigmaFormat.METHOD: | ||
| 166 | return parseMethod(parentEntry, tokens); | ||
| 167 | case EnigmaFormat.PARAMETER: | ||
| 168 | return parseArgument(parentEntry, tokens); | ||
| 169 | case EnigmaFormat.COMMENT: | ||
| 170 | readJavadoc(parent, tokens); | ||
| 171 | return null; | ||
| 172 | default: | ||
| 173 | throw new RuntimeException("Unknown token '" + keyToken + "'"); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | private void readJavadoc(MappingPair<?, RawEntryMapping> parent, String[] tokens) { | ||
| 178 | if (parent == null) | ||
| 179 | throw new IllegalStateException("Javadoc has no parent!"); | ||
| 180 | // Empty string to concat | ||
| 181 | String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens,1,tokens.length)) : ""; | ||
| 182 | if (parent.getMapping() == null) { | ||
| 183 | parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED)); | ||
| 184 | } | ||
| 185 | parent.getMapping().addJavadocLine(MappingHelper.unescape(jdLine)); | ||
| 186 | } | ||
| 187 | |||
| 188 | private MappingPair<ClassEntry, RawEntryMapping> parseClass(@Nullable Entry<?> parent, String[] tokens) { | ||
| 189 | String obfuscatedName = ClassEntry.getInnerName(tokens[1]); | ||
| 190 | ClassEntry obfuscatedEntry; | ||
| 191 | if (parent instanceof ClassEntry) { | ||
| 192 | obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName); | ||
| 193 | } else { | ||
| 194 | obfuscatedEntry = new ClassEntry(obfuscatedName); | ||
| 195 | } | ||
| 196 | |||
| 197 | String mapping = null; | ||
| 198 | AccessModifier modifier = AccessModifier.UNCHANGED; | ||
| 199 | |||
| 200 | if (tokens.length == 3) { | ||
| 201 | AccessModifier parsedModifier = parseModifier(tokens[2]); | ||
| 202 | if (parsedModifier != null) { | ||
| 203 | modifier = parsedModifier; | ||
| 204 | mapping = obfuscatedName; | ||
| 205 | } else { | ||
| 206 | mapping = tokens[2]; | ||
| 207 | } | ||
| 208 | } else if (tokens.length == 4) { | ||
| 209 | mapping = tokens[2]; | ||
| 210 | modifier = parseModifier(tokens[3]); | ||
| 211 | } | ||
| 212 | |||
| 213 | if (mapping != null) { | ||
| 214 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); | ||
| 215 | } else { | ||
| 216 | return new MappingPair<>(obfuscatedEntry); | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | private MappingPair<FieldEntry, RawEntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) { | ||
| 221 | if (!(parent instanceof ClassEntry)) { | ||
| 222 | throw new RuntimeException("Field must be a child of a class!"); | ||
| 223 | } | ||
| 224 | |||
| 225 | ClassEntry ownerEntry = (ClassEntry) parent; | ||
| 226 | |||
| 227 | String obfuscatedName = tokens[1]; | ||
| 228 | String mapping = obfuscatedName; | ||
| 229 | AccessModifier modifier = AccessModifier.UNCHANGED; | ||
| 230 | TypeDescriptor descriptor; | ||
| 231 | |||
| 232 | if (tokens.length == 3) { | ||
| 233 | mapping = tokens[1]; | ||
| 234 | descriptor = new TypeDescriptor(tokens[2]); | ||
| 235 | } else if (tokens.length == 4) { | ||
| 236 | AccessModifier parsedModifier = parseModifier(tokens[3]); | ||
| 237 | if (parsedModifier != null) { | ||
| 238 | descriptor = new TypeDescriptor(tokens[2]); | ||
| 239 | modifier = parsedModifier; | ||
| 240 | } else { | ||
| 241 | mapping = tokens[2]; | ||
| 242 | descriptor = new TypeDescriptor(tokens[3]); | ||
| 243 | } | ||
| 244 | } else if (tokens.length == 5) { | ||
| 245 | descriptor = new TypeDescriptor(tokens[3]); | ||
| 246 | mapping = tokens[2]; | ||
| 247 | modifier = parseModifier(tokens[4]); | ||
| 248 | } else { | ||
| 249 | throw new RuntimeException("Invalid field declaration"); | ||
| 250 | } | ||
| 251 | |||
| 252 | FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor); | ||
| 253 | if (mapping != null) { | ||
| 254 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); | ||
| 255 | } else { | ||
| 256 | return new MappingPair<>(obfuscatedEntry); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | private MappingPair<MethodEntry, RawEntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) { | ||
| 261 | if (!(parent instanceof ClassEntry)) { | ||
| 262 | throw new RuntimeException("Method must be a child of a class!"); | ||
| 263 | } | ||
| 264 | |||
| 265 | ClassEntry ownerEntry = (ClassEntry) parent; | ||
| 266 | |||
| 267 | String obfuscatedName = tokens[1]; | ||
| 268 | String mapping = null; | ||
| 269 | AccessModifier modifier = AccessModifier.UNCHANGED; | ||
| 270 | MethodDescriptor descriptor; | ||
| 271 | |||
| 272 | if (tokens.length == 3) { | ||
| 273 | descriptor = new MethodDescriptor(tokens[2]); | ||
| 274 | } else if (tokens.length == 4) { | ||
| 275 | AccessModifier parsedModifier = parseModifier(tokens[3]); | ||
| 276 | if (parsedModifier != null) { | ||
| 277 | modifier = parsedModifier; | ||
| 278 | mapping = obfuscatedName; | ||
| 279 | descriptor = new MethodDescriptor(tokens[2]); | ||
| 280 | } else { | ||
| 281 | mapping = tokens[2]; | ||
| 282 | descriptor = new MethodDescriptor(tokens[3]); | ||
| 283 | } | ||
| 284 | } else if (tokens.length == 5) { | ||
| 285 | mapping = tokens[2]; | ||
| 286 | modifier = parseModifier(tokens[4]); | ||
| 287 | descriptor = new MethodDescriptor(tokens[3]); | ||
| 288 | } else { | ||
| 289 | throw new RuntimeException("Invalid method declaration"); | ||
| 290 | } | ||
| 291 | |||
| 292 | MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor); | ||
| 293 | if (mapping != null) { | ||
| 294 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); | ||
| 295 | } else { | ||
| 296 | return new MappingPair<>(obfuscatedEntry); | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) { | ||
| 301 | if (!(parent instanceof MethodEntry)) { | ||
| 302 | throw new RuntimeException("Method arg must be a child of a method!"); | ||
| 303 | } | ||
| 304 | |||
| 305 | MethodEntry ownerEntry = (MethodEntry) parent; | ||
| 306 | LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true, null); | ||
| 307 | String mapping = tokens[2]; | ||
| 308 | |||
| 309 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); | ||
| 310 | } | ||
| 311 | |||
| 312 | @Nullable | ||
| 313 | private AccessModifier parseModifier(String token) { | ||
| 314 | if (token.startsWith("ACC:")) { | ||
| 315 | return AccessModifier.valueOf(token.substring(4)); | ||
| 316 | } | ||
| 317 | return null; | ||
| 318 | } | ||
| 319 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java deleted file mode 100644 index be0fceb..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java +++ /dev/null | |||
| @@ -1,316 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.mapping.serde; | ||
| 13 | |||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.PrintWriter; | ||
| 16 | import java.net.URI; | ||
| 17 | import java.net.URISyntaxException; | ||
| 18 | import java.nio.file.DirectoryStream; | ||
| 19 | import java.nio.file.FileSystem; | ||
| 20 | import java.nio.file.FileSystems; | ||
| 21 | import java.nio.file.Files; | ||
| 22 | import java.nio.file.Path; | ||
| 23 | import java.nio.file.Paths; | ||
| 24 | import java.util.ArrayList; | ||
| 25 | import java.util.Collection; | ||
| 26 | import java.util.Collections; | ||
| 27 | import java.util.Objects; | ||
| 28 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 29 | import java.util.stream.Collectors; | ||
| 30 | import java.util.stream.Stream; | ||
| 31 | |||
| 32 | import cuchaz.enigma.ProgressListener; | ||
| 33 | import cuchaz.enigma.translation.MappingTranslator; | ||
| 34 | import cuchaz.enigma.translation.Translator; | ||
| 35 | import cuchaz.enigma.translation.mapping.AccessModifier; | ||
| 36 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 37 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 38 | import cuchaz.enigma.translation.mapping.MappingFileNameFormat; | ||
| 39 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 40 | import cuchaz.enigma.translation.mapping.VoidEntryResolver; | ||
| 41 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 42 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 43 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 44 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 45 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 46 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 47 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 48 | import cuchaz.enigma.utils.I18n; | ||
| 49 | import cuchaz.enigma.utils.LFPrintWriter; | ||
| 50 | |||
| 51 | public enum EnigmaMappingsWriter implements MappingsWriter { | ||
| 52 | FILE { | ||
| 53 | @Override | ||
| 54 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { | ||
| 55 | Collection<ClassEntry> classes = mappings.getRootNodes() | ||
| 56 | .filter(entry -> entry instanceof ClassEntry) | ||
| 57 | .map(entry -> (ClassEntry) entry) | ||
| 58 | .collect(Collectors.toList()); | ||
| 59 | |||
| 60 | progress.init(classes.size(), I18n.translate("progress.mappings.enigma_file.writing")); | ||
| 61 | |||
| 62 | int steps = 0; | ||
| 63 | try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) { | ||
| 64 | for (ClassEntry classEntry : classes) { | ||
| 65 | progress.step(steps++, classEntry.getFullName()); | ||
| 66 | writeRoot(writer, mappings, classEntry); | ||
| 67 | } | ||
| 68 | } catch (IOException e) { | ||
| 69 | e.printStackTrace(); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | }, | ||
| 73 | DIRECTORY { | ||
| 74 | @Override | ||
| 75 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { | ||
| 76 | Collection<ClassEntry> changedClasses = delta.getChangedRoots() | ||
| 77 | .filter(entry -> entry instanceof ClassEntry) | ||
| 78 | .map(entry -> (ClassEntry) entry) | ||
| 79 | .collect(Collectors.toList()); | ||
| 80 | |||
| 81 | applyDeletions(path, changedClasses, mappings, delta.getBaseMappings(), saveParameters.getFileNameFormat()); | ||
| 82 | |||
| 83 | progress.init(changedClasses.size(), I18n.translate("progress.mappings.enigma_directory.writing")); | ||
| 84 | |||
| 85 | AtomicInteger steps = new AtomicInteger(); | ||
| 86 | |||
| 87 | Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); | ||
| 88 | changedClasses.parallelStream().forEach(classEntry -> { | ||
| 89 | progress.step(steps.getAndIncrement(), classEntry.getFullName()); | ||
| 90 | |||
| 91 | try { | ||
| 92 | ClassEntry fileEntry = classEntry; | ||
| 93 | if (saveParameters.getFileNameFormat() == MappingFileNameFormat.BY_DEOBF) { | ||
| 94 | fileEntry = translator.translate(fileEntry); | ||
| 95 | } | ||
| 96 | |||
| 97 | Path classPath = resolve(path, fileEntry); | ||
| 98 | Files.createDirectories(classPath.getParent()); | ||
| 99 | Files.deleteIfExists(classPath); | ||
| 100 | |||
| 101 | try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(classPath))) { | ||
| 102 | writeRoot(writer, mappings, classEntry); | ||
| 103 | } | ||
| 104 | } catch (Throwable t) { | ||
| 105 | System.err.println("Failed to write class '" + classEntry.getFullName() + "'"); | ||
| 106 | t.printStackTrace(); | ||
| 107 | } | ||
| 108 | }); | ||
| 109 | } | ||
| 110 | |||
| 111 | private void applyDeletions(Path root, Collection<ClassEntry> changedClasses, EntryTree<EntryMapping> mappings, EntryTree<EntryMapping> oldMappings, MappingFileNameFormat fileNameFormat) { | ||
| 112 | Translator oldMappingTranslator = new MappingTranslator(oldMappings, VoidEntryResolver.INSTANCE); | ||
| 113 | |||
| 114 | Stream<ClassEntry> deletedClassStream = changedClasses.stream() | ||
| 115 | .filter(e -> !Objects.equals(oldMappings.get(e), mappings.get(e))); | ||
| 116 | |||
| 117 | if (fileNameFormat == MappingFileNameFormat.BY_DEOBF) { | ||
| 118 | deletedClassStream = deletedClassStream.map(oldMappingTranslator::translate); | ||
| 119 | } | ||
| 120 | |||
| 121 | Collection<ClassEntry> deletedClasses = deletedClassStream.collect(Collectors.toList()); | ||
| 122 | |||
| 123 | for (ClassEntry classEntry : deletedClasses) { | ||
| 124 | try { | ||
| 125 | Files.deleteIfExists(resolve(root, classEntry)); | ||
| 126 | } catch (IOException e) { | ||
| 127 | System.err.println("Failed to delete deleted class '" + classEntry + "'"); | ||
| 128 | e.printStackTrace(); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | for (ClassEntry classEntry : deletedClasses) { | ||
| 133 | String packageName = classEntry.getPackageName(); | ||
| 134 | if (packageName != null) { | ||
| 135 | Path packagePath = Paths.get(packageName); | ||
| 136 | try { | ||
| 137 | deleteDeadPackages(root, packagePath); | ||
| 138 | } catch (IOException e) { | ||
| 139 | System.err.println("Failed to delete dead package '" + packageName + "'"); | ||
| 140 | e.printStackTrace(); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | private void deleteDeadPackages(Path root, Path packagePath) throws IOException { | ||
| 147 | for (int i = packagePath.getNameCount() - 1; i >= 0; i--) { | ||
| 148 | Path subPath = packagePath.subpath(0, i + 1); | ||
| 149 | Path packagePart = root.resolve(subPath); | ||
| 150 | if (isEmpty(packagePart)) { | ||
| 151 | Files.deleteIfExists(packagePart); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | private boolean isEmpty(Path path) { | ||
| 157 | try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) { | ||
| 158 | return !stream.iterator().hasNext(); | ||
| 159 | } catch (IOException e) { | ||
| 160 | return false; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | private Path resolve(Path root, ClassEntry classEntry) { | ||
| 165 | return root.resolve(classEntry.getFullName() + ".mapping"); | ||
| 166 | } | ||
| 167 | }, | ||
| 168 | ZIP { | ||
| 169 | @Override | ||
| 170 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { | ||
| 171 | try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) { | ||
| 172 | DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters); | ||
| 173 | } catch (IOException e) { | ||
| 174 | e.printStackTrace(); | ||
| 175 | } catch (URISyntaxException e) { | ||
| 176 | throw new RuntimeException("Unexpected error creating URI for " + zip, e); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | }; | ||
| 180 | |||
| 181 | protected void writeRoot(PrintWriter writer, EntryTree<EntryMapping> mappings, ClassEntry classEntry) { | ||
| 182 | Collection<Entry<?>> children = groupChildren(mappings.getChildren(classEntry)); | ||
| 183 | |||
| 184 | EntryMapping classEntryMapping = mappings.get(classEntry); | ||
| 185 | |||
| 186 | writer.println(writeClass(classEntry, classEntryMapping).trim()); | ||
| 187 | if (classEntryMapping != null && classEntryMapping.getJavadoc() != null) { | ||
| 188 | writeDocs(writer, classEntryMapping, 0); | ||
| 189 | } | ||
| 190 | |||
| 191 | for (Entry<?> child : children) { | ||
| 192 | writeEntry(writer, mappings, child, 1); | ||
| 193 | } | ||
| 194 | |||
| 195 | } | ||
| 196 | |||
| 197 | private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) { | ||
| 198 | String jd = mapping.getJavadoc(); | ||
| 199 | if (jd != null) { | ||
| 200 | for (String line : jd.split("\\R")) { | ||
| 201 | writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1)); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | protected void writeEntry(PrintWriter writer, EntryTree<EntryMapping> mappings, Entry<?> entry, int depth) { | ||
| 207 | EntryTreeNode<EntryMapping> node = mappings.findNode(entry); | ||
| 208 | if (node == null) { | ||
| 209 | return; | ||
| 210 | } | ||
| 211 | |||
| 212 | EntryMapping mapping = node.getValue(); | ||
| 213 | |||
| 214 | if (entry instanceof ClassEntry) { | ||
| 215 | String line = writeClass((ClassEntry) entry, mapping); | ||
| 216 | writer.println(indent(line, depth)); | ||
| 217 | } else if (entry instanceof MethodEntry) { | ||
| 218 | String line = writeMethod((MethodEntry) entry, mapping); | ||
| 219 | writer.println(indent(line, depth)); | ||
| 220 | } else if (entry instanceof FieldEntry) { | ||
| 221 | String line = writeField((FieldEntry) entry, mapping); | ||
| 222 | writer.println(indent(line, depth)); | ||
| 223 | } else if (entry instanceof LocalVariableEntry && mapping != null) { | ||
| 224 | String line = writeArgument((LocalVariableEntry) entry, mapping); | ||
| 225 | writer.println(indent(line, depth)); | ||
| 226 | } | ||
| 227 | if (mapping != null && mapping.getJavadoc() != null) { | ||
| 228 | writeDocs(writer, mapping, depth); | ||
| 229 | } | ||
| 230 | |||
| 231 | Collection<Entry<?>> children = groupChildren(node.getChildren()); | ||
| 232 | for (Entry<?> child : children) { | ||
| 233 | writeEntry(writer, mappings, child, depth + 1); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | private Collection<Entry<?>> groupChildren(Collection<Entry<?>> children) { | ||
| 238 | Collection<Entry<?>> result = new ArrayList<>(children.size()); | ||
| 239 | |||
| 240 | children.stream().filter(e -> e instanceof FieldEntry) | ||
| 241 | .map(e -> (FieldEntry) e) | ||
| 242 | .sorted() | ||
| 243 | .forEach(result::add); | ||
| 244 | |||
| 245 | children.stream().filter(e -> e instanceof MethodEntry) | ||
| 246 | .map(e -> (MethodEntry) e) | ||
| 247 | .sorted() | ||
| 248 | .forEach(result::add); | ||
| 249 | |||
| 250 | children.stream().filter(e -> e instanceof LocalVariableEntry) | ||
| 251 | .map(e -> (LocalVariableEntry) e) | ||
| 252 | .sorted() | ||
| 253 | .forEach(result::add); | ||
| 254 | |||
| 255 | children.stream().filter(e -> e instanceof ClassEntry) | ||
| 256 | .map(e -> (ClassEntry) e) | ||
| 257 | .sorted() | ||
| 258 | .forEach(result::add); | ||
| 259 | |||
| 260 | return result; | ||
| 261 | } | ||
| 262 | |||
| 263 | protected String writeClass(ClassEntry entry, EntryMapping mapping) { | ||
| 264 | StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS +" "); | ||
| 265 | builder.append(entry.getName()).append(' '); | ||
| 266 | writeMapping(builder, mapping); | ||
| 267 | |||
| 268 | return builder.toString(); | ||
| 269 | } | ||
| 270 | |||
| 271 | protected String writeMethod(MethodEntry entry, EntryMapping mapping) { | ||
| 272 | StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " "); | ||
| 273 | builder.append(entry.getName()).append(' '); | ||
| 274 | if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { | ||
| 275 | writeMapping(builder, mapping); | ||
| 276 | } | ||
| 277 | |||
| 278 | builder.append(entry.getDesc().toString()); | ||
| 279 | |||
| 280 | return builder.toString(); | ||
| 281 | } | ||
| 282 | |||
| 283 | protected String writeField(FieldEntry entry, EntryMapping mapping) { | ||
| 284 | StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " "); | ||
| 285 | builder.append(entry.getName()).append(' '); | ||
| 286 | if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { | ||
| 287 | writeMapping(builder, mapping); | ||
| 288 | } | ||
| 289 | |||
| 290 | builder.append(entry.getDesc().toString()); | ||
| 291 | |||
| 292 | return builder.toString(); | ||
| 293 | } | ||
| 294 | |||
| 295 | protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) { | ||
| 296 | return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.getTargetName(); | ||
| 297 | } | ||
| 298 | |||
| 299 | private void writeMapping(StringBuilder builder, EntryMapping mapping) { | ||
| 300 | if (mapping != null) { | ||
| 301 | builder.append(mapping.getTargetName()).append(' '); | ||
| 302 | if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) { | ||
| 303 | builder.append(mapping.getAccessModifier().getFormattedName()).append(' '); | ||
| 304 | } | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | private String indent(String line, int depth) { | ||
| 309 | StringBuilder builder = new StringBuilder(); | ||
| 310 | for (int i = 0; i < depth; i++) { | ||
| 311 | builder.append("\t"); | ||
| 312 | } | ||
| 313 | builder.append(line.trim()); | ||
| 314 | return builder.toString(); | ||
| 315 | } | ||
| 316 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java deleted file mode 100644 index 6c8c343..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java +++ /dev/null | |||
| @@ -1,59 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 9 | |||
| 10 | import javax.annotation.Nullable; | ||
| 11 | import java.io.IOException; | ||
| 12 | import java.nio.file.Path; | ||
| 13 | |||
| 14 | public enum MappingFormat { | ||
| 15 | ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE), | ||
| 16 | ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY), | ||
| 17 | ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP), | ||
| 18 | TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()), | ||
| 19 | TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE), | ||
| 20 | SRG_FILE(SrgMappingsWriter.INSTANCE, null), | ||
| 21 | PROGUARD(null, ProguardMappingsReader.INSTANCE); | ||
| 22 | |||
| 23 | |||
| 24 | private final MappingsWriter writer; | ||
| 25 | private final MappingsReader reader; | ||
| 26 | |||
| 27 | MappingFormat(MappingsWriter writer, MappingsReader reader) { | ||
| 28 | this.writer = writer; | ||
| 29 | this.reader = reader; | ||
| 30 | } | ||
| 31 | |||
| 32 | public void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { | ||
| 33 | write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters); | ||
| 34 | } | ||
| 35 | |||
| 36 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { | ||
| 37 | if (writer == null) { | ||
| 38 | throw new IllegalStateException(name() + " does not support writing"); | ||
| 39 | } | ||
| 40 | writer.write(mappings, delta, path, progressListener, saveParameters); | ||
| 41 | } | ||
| 42 | |||
| 43 | public EntryTree<EntryMapping> read(Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) throws IOException, MappingParseException { | ||
| 44 | if (reader == null) { | ||
| 45 | throw new IllegalStateException(name() + " does not support reading"); | ||
| 46 | } | ||
| 47 | return reader.read(path, progressListener, saveParameters); | ||
| 48 | } | ||
| 49 | |||
| 50 | @Nullable | ||
| 51 | public MappingsWriter getWriter() { | ||
| 52 | return writer; | ||
| 53 | } | ||
| 54 | |||
| 55 | @Nullable | ||
| 56 | public MappingsReader getReader() { | ||
| 57 | return reader; | ||
| 58 | } | ||
| 59 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java deleted file mode 100644 index 7c8f6cc..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java +++ /dev/null | |||
| @@ -1,51 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | public final class MappingHelper { | ||
| 4 | private static final String TO_ESCAPE = "\\\n\r\0\t"; | ||
| 5 | private static final String ESCAPED = "\\nr0t"; | ||
| 6 | |||
| 7 | public static String escape(String raw) { | ||
| 8 | StringBuilder builder = new StringBuilder(raw.length() + 1); | ||
| 9 | for (int i = 0; i < raw.length(); i++) { | ||
| 10 | final char c = raw.charAt(i); | ||
| 11 | final int r = TO_ESCAPE.indexOf(c); | ||
| 12 | if (r < 0) { | ||
| 13 | builder.append(c); | ||
| 14 | } else { | ||
| 15 | builder.append('\\').append(ESCAPED.charAt(r)); | ||
| 16 | } | ||
| 17 | } | ||
| 18 | return builder.toString(); | ||
| 19 | } | ||
| 20 | |||
| 21 | public static String unescape(String str) { | ||
| 22 | int pos = str.indexOf('\\'); | ||
| 23 | if (pos < 0) return str; | ||
| 24 | |||
| 25 | StringBuilder ret = new StringBuilder(str.length() - 1); | ||
| 26 | int start = 0; | ||
| 27 | |||
| 28 | do { | ||
| 29 | ret.append(str, start, pos); | ||
| 30 | pos++; | ||
| 31 | int type; | ||
| 32 | |||
| 33 | if (pos >= str.length()) { | ||
| 34 | throw new RuntimeException("incomplete escape sequence at the end"); | ||
| 35 | } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) { | ||
| 36 | throw new RuntimeException("invalid escape character: \\" + str.charAt(pos)); | ||
| 37 | } else { | ||
| 38 | ret.append(TO_ESCAPE.charAt(type)); | ||
| 39 | } | ||
| 40 | |||
| 41 | start = pos + 1; | ||
| 42 | } while ((pos = str.indexOf('\\', start)) >= 0); | ||
| 43 | |||
| 44 | ret.append(str, start, str.length()); | ||
| 45 | |||
| 46 | return ret.toString(); | ||
| 47 | } | ||
| 48 | |||
| 49 | private MappingHelper() { | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java deleted file mode 100644 index 4c60787..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java +++ /dev/null | |||
| @@ -1,14 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | |||
| 9 | import java.io.IOException; | ||
| 10 | import java.nio.file.Path; | ||
| 11 | |||
| 12 | public interface MappingsReader { | ||
| 13 | EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException; | ||
| 14 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java deleted file mode 100644 index 8815986..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java +++ /dev/null | |||
| @@ -1,17 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | |||
| 9 | import java.nio.file.Path; | ||
| 10 | |||
| 11 | public interface MappingsWriter { | ||
| 12 | void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); | ||
| 13 | |||
| 14 | default void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { | ||
| 15 | write(mappings, MappingDelta.added(mappings), path, progress, saveParameters); | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java deleted file mode 100644 index b5ede39..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java +++ /dev/null | |||
| @@ -1,134 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.command.MappingCommandsUtil; | ||
| 5 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 9 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 10 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 11 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 15 | |||
| 16 | import java.io.IOException; | ||
| 17 | import java.nio.charset.StandardCharsets; | ||
| 18 | import java.nio.file.Files; | ||
| 19 | import java.nio.file.Path; | ||
| 20 | import java.util.regex.Matcher; | ||
| 21 | import java.util.regex.Pattern; | ||
| 22 | |||
| 23 | public class ProguardMappingsReader implements MappingsReader { | ||
| 24 | public static final ProguardMappingsReader INSTANCE = new ProguardMappingsReader(); | ||
| 25 | private static final String NAME = "[a-zA-Z0-9_\\-.$<>]+"; | ||
| 26 | private static final String TYPE = NAME + "(?:\\[])*"; | ||
| 27 | private static final String TYPE_LIST = "|(?:(?:" + TYPE + ",)*" + TYPE + ")"; | ||
| 28 | private static final Pattern CLASS = Pattern.compile("(" + NAME + ") -> (" + NAME + "):"); | ||
| 29 | private static final Pattern FIELD = Pattern.compile(" {4}(" + TYPE + ") (" + NAME + ") -> (" + NAME + ")"); | ||
| 30 | private static final Pattern METHOD = Pattern.compile(" {4}(?:[0-9]+:[0-9]+:)?(" + TYPE + ") (" + NAME + ")\\((" + TYPE_LIST + ")\\) -> (" + NAME + ")"); | ||
| 31 | |||
| 32 | public ProguardMappingsReader() {} | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { | ||
| 36 | EntryTree<EntryMapping> mappings = new HashEntryTree<>(); | ||
| 37 | |||
| 38 | int lineNumber = 0; | ||
| 39 | ClassEntry currentClass = null; | ||
| 40 | for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) { | ||
| 41 | lineNumber++; | ||
| 42 | |||
| 43 | if (line.startsWith("#") || line.isEmpty()) { | ||
| 44 | continue; | ||
| 45 | } | ||
| 46 | |||
| 47 | Matcher classMatcher = CLASS.matcher(line); | ||
| 48 | Matcher fieldMatcher = FIELD.matcher(line); | ||
| 49 | Matcher methodMatcher = METHOD.matcher(line); | ||
| 50 | |||
| 51 | if (classMatcher.matches()) { | ||
| 52 | String name = classMatcher.group(1); | ||
| 53 | String targetName = classMatcher.group(2); | ||
| 54 | |||
| 55 | mappings.insert(currentClass = new ClassEntry(name.replace('.', '/')), new EntryMapping(ClassEntry.getInnerName(targetName.replace('.', '/')))); | ||
| 56 | } else if (fieldMatcher.matches()) { | ||
| 57 | String type = fieldMatcher.group(1); | ||
| 58 | String name = fieldMatcher.group(2); | ||
| 59 | String targetName = fieldMatcher.group(3); | ||
| 60 | |||
| 61 | if (currentClass == null) { | ||
| 62 | throw new MappingParseException(path::toString, lineNumber, "field mapping not inside class: " + line); | ||
| 63 | } | ||
| 64 | |||
| 65 | mappings.insert(new FieldEntry(currentClass, name, new TypeDescriptor(getDescriptor(type))), new EntryMapping(targetName)); | ||
| 66 | } else if (methodMatcher.matches()) { | ||
| 67 | String returnType = methodMatcher.group(1); | ||
| 68 | String name = methodMatcher.group(2); | ||
| 69 | String[] parameterTypes = methodMatcher.group(3).isEmpty() ? new String[0] : methodMatcher.group(3).split(","); | ||
| 70 | String targetName = methodMatcher.group(4); | ||
| 71 | |||
| 72 | if (currentClass == null) { | ||
| 73 | throw new MappingParseException(path::toString, lineNumber, "method mapping not inside class: " + line); | ||
| 74 | } | ||
| 75 | |||
| 76 | mappings.insert(new MethodEntry(currentClass, name, new MethodDescriptor(getDescriptor(returnType, parameterTypes))), new EntryMapping(targetName)); | ||
| 77 | } else { | ||
| 78 | throw new MappingParseException(path::toString, lineNumber, "invalid mapping line: " + line); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | return MappingCommandsUtil.invert(mappings); | ||
| 83 | } | ||
| 84 | |||
| 85 | private String getDescriptor(String type) { | ||
| 86 | StringBuilder descriptor = new StringBuilder(); | ||
| 87 | |||
| 88 | while (type.endsWith("[]")) { | ||
| 89 | descriptor.append("["); | ||
| 90 | type = type.substring(0, type.length() - 2); | ||
| 91 | } | ||
| 92 | |||
| 93 | switch (type) { | ||
| 94 | case "byte": | ||
| 95 | return descriptor + "B"; | ||
| 96 | case "char": | ||
| 97 | return descriptor + "C"; | ||
| 98 | case "short": | ||
| 99 | return descriptor + "S"; | ||
| 100 | case "int": | ||
| 101 | return descriptor + "I"; | ||
| 102 | case "long": | ||
| 103 | return descriptor + "J"; | ||
| 104 | case "float": | ||
| 105 | return descriptor + "F"; | ||
| 106 | case "double": | ||
| 107 | return descriptor + "D"; | ||
| 108 | case "boolean": | ||
| 109 | return descriptor + "Z"; | ||
| 110 | case "void": | ||
| 111 | return descriptor + "V"; | ||
| 112 | } | ||
| 113 | |||
| 114 | descriptor.append("L"); | ||
| 115 | descriptor.append(type.replace('.', '/')); | ||
| 116 | descriptor.append(";"); | ||
| 117 | |||
| 118 | return descriptor.toString(); | ||
| 119 | } | ||
| 120 | |||
| 121 | private String getDescriptor(String returnType, String[] parameterTypes) { | ||
| 122 | StringBuilder descriptor = new StringBuilder(); | ||
| 123 | descriptor.append('('); | ||
| 124 | |||
| 125 | for (String parameterType : parameterTypes) { | ||
| 126 | descriptor.append(getDescriptor(parameterType)); | ||
| 127 | } | ||
| 128 | |||
| 129 | descriptor.append(')'); | ||
| 130 | descriptor.append(getDescriptor(returnType)); | ||
| 131 | |||
| 132 | return descriptor.toString(); | ||
| 133 | } | ||
| 134 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java deleted file mode 100644 index afb40e9..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java +++ /dev/null | |||
| @@ -1,30 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.AccessModifier; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | |||
| 6 | import java.util.ArrayList; | ||
| 7 | import java.util.List; | ||
| 8 | |||
| 9 | final class RawEntryMapping { | ||
| 10 | private final String targetName; | ||
| 11 | private final AccessModifier access; | ||
| 12 | private List<String> javadocs = new ArrayList<>(); | ||
| 13 | |||
| 14 | RawEntryMapping(String targetName) { | ||
| 15 | this(targetName, null); | ||
| 16 | } | ||
| 17 | |||
| 18 | RawEntryMapping(String targetName, AccessModifier access) { | ||
| 19 | this.access = access; | ||
| 20 | this.targetName = targetName; | ||
| 21 | } | ||
| 22 | |||
| 23 | void addJavadocLine(String line) { | ||
| 24 | javadocs.add(line); | ||
| 25 | } | ||
| 26 | |||
| 27 | EntryMapping bake() { | ||
| 28 | return new EntryMapping(targetName, access, javadocs.isEmpty() ? null : String.join("\n", javadocs)); | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java deleted file mode 100644 index f67f8fc..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java +++ /dev/null | |||
| @@ -1,118 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import com.google.common.collect.Lists; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.translation.MappingTranslator; | ||
| 6 | import cuchaz.enigma.translation.Translator; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 8 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 9 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 10 | import cuchaz.enigma.translation.mapping.VoidEntryResolver; | ||
| 11 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 12 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 17 | import cuchaz.enigma.utils.I18n; | ||
| 18 | import cuchaz.enigma.utils.LFPrintWriter; | ||
| 19 | |||
| 20 | import java.io.IOException; | ||
| 21 | import java.io.PrintWriter; | ||
| 22 | import java.nio.file.Files; | ||
| 23 | import java.nio.file.Path; | ||
| 24 | import java.util.ArrayList; | ||
| 25 | import java.util.Collection; | ||
| 26 | import java.util.Comparator; | ||
| 27 | import java.util.List; | ||
| 28 | import java.util.stream.Collectors; | ||
| 29 | |||
| 30 | public enum SrgMappingsWriter implements MappingsWriter { | ||
| 31 | INSTANCE; | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { | ||
| 35 | try { | ||
| 36 | Files.deleteIfExists(path); | ||
| 37 | Files.createFile(path); | ||
| 38 | } catch (IOException e) { | ||
| 39 | e.printStackTrace(); | ||
| 40 | } | ||
| 41 | |||
| 42 | List<String> classLines = new ArrayList<>(); | ||
| 43 | List<String> fieldLines = new ArrayList<>(); | ||
| 44 | List<String> methodLines = new ArrayList<>(); | ||
| 45 | |||
| 46 | Collection<Entry<?>> rootEntries = Lists.newArrayList(mappings).stream() | ||
| 47 | .map(EntryTreeNode::getEntry) | ||
| 48 | .collect(Collectors.toList()); | ||
| 49 | progress.init(rootEntries.size(), I18n.translate("progress.mappings.srg_file.generating")); | ||
| 50 | |||
| 51 | int steps = 0; | ||
| 52 | for (Entry<?> entry : sorted(rootEntries)) { | ||
| 53 | progress.step(steps++, entry.getName()); | ||
| 54 | writeEntry(classLines, fieldLines, methodLines, mappings, entry); | ||
| 55 | } | ||
| 56 | |||
| 57 | progress.init(3, I18n.translate("progress.mappings.srg_file.writing")); | ||
| 58 | try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) { | ||
| 59 | progress.step(0, I18n.translate("type.classes")); | ||
| 60 | classLines.forEach(writer::println); | ||
| 61 | progress.step(1, I18n.translate("type.fields")); | ||
| 62 | fieldLines.forEach(writer::println); | ||
| 63 | progress.step(2, I18n.translate("type.methods")); | ||
| 64 | methodLines.forEach(writer::println); | ||
| 65 | } catch (IOException e) { | ||
| 66 | e.printStackTrace(); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | private void writeEntry(List<String> classes, List<String> fields, List<String> methods, EntryTree<EntryMapping> mappings, Entry<?> entry) { | ||
| 71 | EntryTreeNode<EntryMapping> node = mappings.findNode(entry); | ||
| 72 | if (node == null) { | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | |||
| 76 | Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); | ||
| 77 | if (entry instanceof ClassEntry) { | ||
| 78 | classes.add(generateClassLine((ClassEntry) entry, translator)); | ||
| 79 | } else if (entry instanceof FieldEntry) { | ||
| 80 | fields.add(generateFieldLine((FieldEntry) entry, translator)); | ||
| 81 | } else if (entry instanceof MethodEntry) { | ||
| 82 | methods.add(generateMethodLine((MethodEntry) entry, translator)); | ||
| 83 | } | ||
| 84 | |||
| 85 | for (Entry<?> child : sorted(node.getChildren())) { | ||
| 86 | writeEntry(classes, fields, methods, mappings, child); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | private String generateClassLine(ClassEntry sourceEntry, Translator translator) { | ||
| 91 | ClassEntry targetEntry = translator.translate(sourceEntry); | ||
| 92 | return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName(); | ||
| 93 | } | ||
| 94 | |||
| 95 | private String generateMethodLine(MethodEntry sourceEntry, Translator translator) { | ||
| 96 | MethodEntry targetEntry = translator.translate(sourceEntry); | ||
| 97 | return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry); | ||
| 98 | } | ||
| 99 | |||
| 100 | private String describeMethod(MethodEntry entry) { | ||
| 101 | return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc(); | ||
| 102 | } | ||
| 103 | |||
| 104 | private String generateFieldLine(FieldEntry sourceEntry, Translator translator) { | ||
| 105 | FieldEntry targetEntry = translator.translate(sourceEntry); | ||
| 106 | return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry); | ||
| 107 | } | ||
| 108 | |||
| 109 | private String describeField(FieldEntry entry) { | ||
| 110 | return entry.getParent().getFullName() + "/" + entry.getName(); | ||
| 111 | } | ||
| 112 | |||
| 113 | private Collection<Entry<?>> sorted(Iterable<Entry<?>> iterable) { | ||
| 114 | ArrayList<Entry<?>> sorted = Lists.newArrayList(iterable); | ||
| 115 | sorted.sort(Comparator.comparing(Entry::getName)); | ||
| 116 | return sorted; | ||
| 117 | } | ||
| 118 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java deleted file mode 100644 index 773c95e..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java +++ /dev/null | |||
| @@ -1,115 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import com.google.common.base.Charsets; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingPair; | ||
| 8 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 9 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 10 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 11 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 12 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 17 | import cuchaz.enigma.utils.I18n; | ||
| 18 | |||
| 19 | import java.io.IOException; | ||
| 20 | import java.nio.file.Files; | ||
| 21 | import java.nio.file.Path; | ||
| 22 | import java.util.List; | ||
| 23 | |||
| 24 | public enum TinyMappingsReader implements MappingsReader { | ||
| 25 | INSTANCE; | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { | ||
| 29 | return read(path, Files.readAllLines(path, Charsets.UTF_8), progress); | ||
| 30 | } | ||
| 31 | |||
| 32 | private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException { | ||
| 33 | EntryTree<EntryMapping> mappings = new HashEntryTree<>(); | ||
| 34 | lines.remove(0); | ||
| 35 | |||
| 36 | progress.init(lines.size(), I18n.translate("progress.mappings.tiny_file.loading")); | ||
| 37 | |||
| 38 | for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { | ||
| 39 | progress.step(lineNumber, ""); | ||
| 40 | |||
| 41 | String line = lines.get(lineNumber); | ||
| 42 | |||
| 43 | if (line.trim().startsWith("#")) { | ||
| 44 | continue; | ||
| 45 | } | ||
| 46 | |||
| 47 | try { | ||
| 48 | MappingPair<?, EntryMapping> mapping = parseLine(line); | ||
| 49 | mappings.insert(mapping.getEntry(), mapping.getMapping()); | ||
| 50 | } catch (Throwable t) { | ||
| 51 | t.printStackTrace(); | ||
| 52 | throw new MappingParseException(path::toString, lineNumber, t.toString()); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | return mappings; | ||
| 57 | } | ||
| 58 | |||
| 59 | private MappingPair<?, EntryMapping> parseLine(String line) { | ||
| 60 | String[] tokens = line.split("\t"); | ||
| 61 | |||
| 62 | String key = tokens[0]; | ||
| 63 | switch (key) { | ||
| 64 | case "CLASS": | ||
| 65 | return parseClass(tokens); | ||
| 66 | case "FIELD": | ||
| 67 | return parseField(tokens); | ||
| 68 | case "METHOD": | ||
| 69 | return parseMethod(tokens); | ||
| 70 | case "MTH-ARG": | ||
| 71 | return parseArgument(tokens); | ||
| 72 | default: | ||
| 73 | throw new RuntimeException("Unknown token '" + key + "'!"); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | private MappingPair<ClassEntry, EntryMapping> parseClass(String[] tokens) { | ||
| 78 | ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]); | ||
| 79 | String mapping = tokens[2]; | ||
| 80 | if (mapping.indexOf('$') > 0) { | ||
| 81 | // inner classes should map to only the final part | ||
| 82 | mapping = mapping.substring(mapping.lastIndexOf('$') + 1); | ||
| 83 | } | ||
| 84 | return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); | ||
| 85 | } | ||
| 86 | |||
| 87 | private MappingPair<FieldEntry, EntryMapping> parseField(String[] tokens) { | ||
| 88 | ClassEntry ownerClass = new ClassEntry(tokens[1]); | ||
| 89 | TypeDescriptor descriptor = new TypeDescriptor(tokens[2]); | ||
| 90 | |||
| 91 | FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor); | ||
| 92 | String mapping = tokens[4]; | ||
| 93 | return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); | ||
| 94 | } | ||
| 95 | |||
| 96 | private MappingPair<MethodEntry, EntryMapping> parseMethod(String[] tokens) { | ||
| 97 | ClassEntry ownerClass = new ClassEntry(tokens[1]); | ||
| 98 | MethodDescriptor descriptor = new MethodDescriptor(tokens[2]); | ||
| 99 | |||
| 100 | MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor); | ||
| 101 | String mapping = tokens[4]; | ||
| 102 | return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); | ||
| 103 | } | ||
| 104 | |||
| 105 | private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(String[] tokens) { | ||
| 106 | ClassEntry ownerClass = new ClassEntry(tokens[1]); | ||
| 107 | MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]); | ||
| 108 | MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor); | ||
| 109 | int variableIndex = Integer.parseInt(tokens[4]); | ||
| 110 | |||
| 111 | String mapping = tokens[5]; | ||
| 112 | LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null); | ||
| 113 | return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); | ||
| 114 | } | ||
| 115 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java deleted file mode 100644 index c82f262..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java +++ /dev/null | |||
| @@ -1,148 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import com.google.common.base.Joiner; | ||
| 4 | import com.google.common.collect.Lists; | ||
| 5 | import cuchaz.enigma.ProgressListener; | ||
| 6 | import cuchaz.enigma.translation.MappingTranslator; | ||
| 7 | import cuchaz.enigma.translation.Translator; | ||
| 8 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 9 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 10 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 11 | import cuchaz.enigma.translation.mapping.VoidEntryResolver; | ||
| 12 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 13 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 18 | |||
| 19 | import java.io.BufferedWriter; | ||
| 20 | import java.io.IOException; | ||
| 21 | import java.io.Writer; | ||
| 22 | import java.nio.charset.StandardCharsets; | ||
| 23 | import java.nio.file.Files; | ||
| 24 | import java.nio.file.Path; | ||
| 25 | import java.util.Comparator; | ||
| 26 | import java.util.HashSet; | ||
| 27 | import java.util.Set; | ||
| 28 | |||
| 29 | public class TinyMappingsWriter implements MappingsWriter { | ||
| 30 | private static final String VERSION_CONSTANT = "v1"; | ||
| 31 | private static final Joiner TAB_JOINER = Joiner.on('\t'); | ||
| 32 | |||
| 33 | //Possibly add a gui or a way to select the namespaces when exporting from the gui | ||
| 34 | public static final TinyMappingsWriter INSTANCE = new TinyMappingsWriter("intermediary", "named"); | ||
| 35 | |||
| 36 | // HACK: as of enigma 0.13.1, some fields seem to appear duplicated? | ||
| 37 | private final Set<String> writtenLines = new HashSet<>(); | ||
| 38 | private final String nameObf; | ||
| 39 | private final String nameDeobf; | ||
| 40 | |||
| 41 | public TinyMappingsWriter(String nameObf, String nameDeobf) { | ||
| 42 | this.nameObf = nameObf; | ||
| 43 | this.nameDeobf = nameDeobf; | ||
| 44 | } | ||
| 45 | |||
| 46 | @Override | ||
| 47 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { | ||
| 48 | try { | ||
| 49 | Files.deleteIfExists(path); | ||
| 50 | Files.createFile(path); | ||
| 51 | } catch (IOException e) { | ||
| 52 | e.printStackTrace(); | ||
| 53 | } | ||
| 54 | |||
| 55 | try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { | ||
| 56 | writeLine(writer, new String[]{VERSION_CONSTANT, nameObf, nameDeobf}); | ||
| 57 | |||
| 58 | Lists.newArrayList(mappings).stream() | ||
| 59 | .map(EntryTreeNode::getEntry).sorted(Comparator.comparing(Object::toString)) | ||
| 60 | .forEach(entry -> writeEntry(writer, mappings, entry)); | ||
| 61 | } catch (IOException e) { | ||
| 62 | e.printStackTrace(); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | private void writeEntry(Writer writer, EntryTree<EntryMapping> mappings, Entry<?> entry) { | ||
| 67 | EntryTreeNode<EntryMapping> node = mappings.findNode(entry); | ||
| 68 | if (node == null) { | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | |||
| 72 | Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); | ||
| 73 | |||
| 74 | EntryMapping mapping = mappings.get(entry); | ||
| 75 | if (mapping != null && !entry.getName().equals(mapping.getTargetName())) { | ||
| 76 | if (entry instanceof ClassEntry) { | ||
| 77 | writeClass(writer, (ClassEntry) entry, translator); | ||
| 78 | } else if (entry instanceof FieldEntry) { | ||
| 79 | writeLine(writer, serializeEntry(entry, mapping.getTargetName())); | ||
| 80 | } else if (entry instanceof MethodEntry) { | ||
| 81 | writeLine(writer, serializeEntry(entry, mapping.getTargetName())); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | writeChildren(writer, mappings, node); | ||
| 86 | } | ||
| 87 | |||
| 88 | private void writeChildren(Writer writer, EntryTree<EntryMapping> mappings, EntryTreeNode<EntryMapping> node) { | ||
| 89 | node.getChildren().stream() | ||
| 90 | .filter(e -> e instanceof FieldEntry).sorted() | ||
| 91 | .forEach(child -> writeEntry(writer, mappings, child)); | ||
| 92 | |||
| 93 | node.getChildren().stream() | ||
| 94 | .filter(e -> e instanceof MethodEntry).sorted() | ||
| 95 | .forEach(child -> writeEntry(writer, mappings, child)); | ||
| 96 | |||
| 97 | node.getChildren().stream() | ||
| 98 | .filter(e -> e instanceof ClassEntry).sorted() | ||
| 99 | .forEach(child -> writeEntry(writer, mappings, child)); | ||
| 100 | } | ||
| 101 | |||
| 102 | private void writeClass(Writer writer, ClassEntry entry, Translator translator) { | ||
| 103 | ClassEntry translatedEntry = translator.translate(entry); | ||
| 104 | |||
| 105 | String obfClassName = entry.getFullName(); | ||
| 106 | String deobfClassName = translatedEntry.getFullName(); | ||
| 107 | writeLine(writer, new String[]{"CLASS", obfClassName, deobfClassName}); | ||
| 108 | } | ||
| 109 | |||
| 110 | private void writeLine(Writer writer, String[] data) { | ||
| 111 | try { | ||
| 112 | String line = TAB_JOINER.join(data) + "\n"; | ||
| 113 | if (writtenLines.add(line)) { | ||
| 114 | writer.write(line); | ||
| 115 | } | ||
| 116 | } catch (IOException e) { | ||
| 117 | throw new RuntimeException(e); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | private String[] serializeEntry(Entry<?> entry, String... extraFields) { | ||
| 122 | String[] data = null; | ||
| 123 | |||
| 124 | if (entry instanceof FieldEntry) { | ||
| 125 | data = new String[4 + extraFields.length]; | ||
| 126 | data[0] = "FIELD"; | ||
| 127 | data[1] = entry.getContainingClass().getFullName(); | ||
| 128 | data[2] = ((FieldEntry) entry).getDesc().toString(); | ||
| 129 | data[3] = entry.getName(); | ||
| 130 | } else if (entry instanceof MethodEntry) { | ||
| 131 | data = new String[4 + extraFields.length]; | ||
| 132 | data[0] = "METHOD"; | ||
| 133 | data[1] = entry.getContainingClass().getFullName(); | ||
| 134 | data[2] = ((MethodEntry) entry).getDesc().toString(); | ||
| 135 | data[3] = entry.getName(); | ||
| 136 | } else if (entry instanceof ClassEntry) { | ||
| 137 | data = new String[2 + extraFields.length]; | ||
| 138 | data[0] = "CLASS"; | ||
| 139 | data[1] = ((ClassEntry) entry).getFullName(); | ||
| 140 | } | ||
| 141 | |||
| 142 | if (data != null) { | ||
| 143 | System.arraycopy(extraFields, 0, data, data.length - extraFields.length, extraFields.length); | ||
| 144 | } | ||
| 145 | |||
| 146 | return data; | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java deleted file mode 100644 index d81cbdb..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java +++ /dev/null | |||
| @@ -1,295 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ProgressListener; | ||
| 4 | import cuchaz.enigma.throwables.MappingParseException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.MappingPair; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 9 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 10 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 11 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 17 | import cuchaz.enigma.utils.I18n; | ||
| 18 | |||
| 19 | import java.io.IOException; | ||
| 20 | import java.nio.charset.StandardCharsets; | ||
| 21 | import java.nio.file.Files; | ||
| 22 | import java.nio.file.Path; | ||
| 23 | import java.util.BitSet; | ||
| 24 | import java.util.List; | ||
| 25 | |||
| 26 | final class TinyV2Reader implements MappingsReader { | ||
| 27 | |||
| 28 | private static final String MINOR_VERSION = "0"; | ||
| 29 | // 0 indent | ||
| 30 | private static final int IN_HEADER = 0; | ||
| 31 | private static final int IN_CLASS = IN_HEADER + 1; | ||
| 32 | // 1 indent | ||
| 33 | private static final int IN_METHOD = IN_CLASS + 1; | ||
| 34 | private static final int IN_FIELD = IN_METHOD + 1; | ||
| 35 | // 2 indent | ||
| 36 | private static final int IN_PARAMETER = IN_FIELD + 1; | ||
| 37 | // general properties | ||
| 38 | private static final int STATE_SIZE = IN_PARAMETER + 1; | ||
| 39 | private static final int[] INDENT_CLEAR_START = {IN_HEADER, IN_METHOD, IN_PARAMETER, STATE_SIZE}; | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { | ||
| 43 | return read(path, Files.readAllLines(path, StandardCharsets.UTF_8), progress); | ||
| 44 | } | ||
| 45 | |||
| 46 | private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException { | ||
| 47 | EntryTree<EntryMapping> mappings = new HashEntryTree<>(); | ||
| 48 | |||
| 49 | progress.init(lines.size(), I18n.translate("progress.mappings.tiny_v2.loading")); | ||
| 50 | |||
| 51 | BitSet state = new BitSet(STATE_SIZE); | ||
| 52 | @SuppressWarnings({"unchecked", "rawtypes"}) | ||
| 53 | MappingPair<? extends Entry<?>, RawEntryMapping>[] holds = new MappingPair[STATE_SIZE]; | ||
| 54 | boolean escapeNames = false; | ||
| 55 | |||
| 56 | for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { | ||
| 57 | try { | ||
| 58 | progress.step(lineNumber, ""); | ||
| 59 | String line = lines.get(lineNumber); | ||
| 60 | |||
| 61 | int indent = 0; | ||
| 62 | while (line.charAt(indent) == '\t') | ||
| 63 | indent++; | ||
| 64 | |||
| 65 | String[] parts = line.substring(indent).split("\t", -1); | ||
| 66 | if (parts.length == 0 || indent >= INDENT_CLEAR_START.length) | ||
| 67 | throw new IllegalArgumentException("Invalid format"); | ||
| 68 | |||
| 69 | // clean and register stuff in stack | ||
| 70 | for (int i = INDENT_CLEAR_START[indent]; i < STATE_SIZE; i++) { | ||
| 71 | state.clear(i); | ||
| 72 | if (holds[i] != null) { | ||
| 73 | RawEntryMapping mapping = holds[i].getMapping(); | ||
| 74 | if (mapping != null) { | ||
| 75 | EntryMapping baked = mapping.bake(); | ||
| 76 | if (baked != null) { | ||
| 77 | mappings.insert(holds[i].getEntry(), baked); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | holds[i] = null; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | switch (indent) { | ||
| 85 | case 0: | ||
| 86 | switch (parts[0]) { | ||
| 87 | case "tiny": // header | ||
| 88 | if (lineNumber != 0) { | ||
| 89 | throw new IllegalArgumentException("Header can only be on the first line"); | ||
| 90 | } | ||
| 91 | if (parts.length < 5) { | ||
| 92 | throw new IllegalArgumentException("Not enough header columns, needs at least 5"); | ||
| 93 | } | ||
| 94 | if (!"2".equals(parts[1]) || !MINOR_VERSION.equals(parts[2])) { | ||
| 95 | throw new IllegalArgumentException("Unsupported TinyV2 version, requires major " + "2" + " and minor " + MINOR_VERSION + ""); | ||
| 96 | } | ||
| 97 | state.set(IN_HEADER); | ||
| 98 | break; | ||
| 99 | case "c": // class | ||
| 100 | state.set(IN_CLASS); | ||
| 101 | holds[IN_CLASS] = parseClass(parts, escapeNames); | ||
| 102 | break; | ||
| 103 | default: | ||
| 104 | unsupportKey(parts); | ||
| 105 | } | ||
| 106 | |||
| 107 | break; | ||
| 108 | case 1: | ||
| 109 | if (state.get(IN_HEADER)) { | ||
| 110 | if (parts[0].equals("esacpe-names")) { | ||
| 111 | escapeNames = true; | ||
| 112 | } | ||
| 113 | |||
| 114 | break; | ||
| 115 | } | ||
| 116 | |||
| 117 | if (state.get(IN_CLASS)) { | ||
| 118 | switch (parts[0]) { | ||
| 119 | case "m": // method | ||
| 120 | state.set(IN_METHOD); | ||
| 121 | holds[IN_METHOD] = parseMethod(holds[IN_CLASS], parts, escapeNames); | ||
| 122 | break; | ||
| 123 | case "f": // field | ||
| 124 | state.set(IN_FIELD); | ||
| 125 | holds[IN_FIELD] = parseField(holds[IN_CLASS], parts, escapeNames); | ||
| 126 | break; | ||
| 127 | case "c": // class javadoc | ||
| 128 | addJavadoc(holds[IN_CLASS], parts); | ||
| 129 | break; | ||
| 130 | default: | ||
| 131 | unsupportKey(parts); | ||
| 132 | } | ||
| 133 | break; | ||
| 134 | } | ||
| 135 | |||
| 136 | unsupportKey(parts); | ||
| 137 | case 2: | ||
| 138 | if (state.get(IN_METHOD)) { | ||
| 139 | switch (parts[0]) { | ||
| 140 | case "p": // parameter | ||
| 141 | state.set(IN_PARAMETER); | ||
| 142 | holds[IN_PARAMETER] = parseArgument(holds[IN_METHOD], parts, escapeNames); | ||
| 143 | break; | ||
| 144 | case "v": // local variable | ||
| 145 | // TODO add local var mapping | ||
| 146 | break; | ||
| 147 | case "c": // method javadoc | ||
| 148 | addJavadoc(holds[IN_METHOD], parts); | ||
| 149 | break; | ||
| 150 | default: | ||
| 151 | unsupportKey(parts); | ||
| 152 | } | ||
| 153 | break; | ||
| 154 | } | ||
| 155 | |||
| 156 | if (state.get(IN_FIELD)) { | ||
| 157 | switch (parts[0]) { | ||
| 158 | case "c": // field javadoc | ||
| 159 | addJavadoc(holds[IN_FIELD], parts); | ||
| 160 | break; | ||
| 161 | default: | ||
| 162 | unsupportKey(parts); | ||
| 163 | } | ||
| 164 | break; | ||
| 165 | } | ||
| 166 | unsupportKey(parts); | ||
| 167 | case 3: | ||
| 168 | if (state.get(IN_PARAMETER)) { | ||
| 169 | switch (parts[0]) { | ||
| 170 | case "c": | ||
| 171 | addJavadoc(holds[IN_PARAMETER], parts); | ||
| 172 | break; | ||
| 173 | default: | ||
| 174 | unsupportKey(parts); | ||
| 175 | } | ||
| 176 | break; | ||
| 177 | } | ||
| 178 | unsupportKey(parts); | ||
| 179 | default: | ||
| 180 | unsupportKey(parts); | ||
| 181 | } | ||
| 182 | |||
| 183 | } catch (Throwable t) { | ||
| 184 | t.printStackTrace(); | ||
| 185 | throw new MappingParseException(path::toString, lineNumber + 1, t.toString()); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | return mappings; | ||
| 190 | } | ||
| 191 | |||
| 192 | private void unsupportKey(String[] parts) { | ||
| 193 | throw new IllegalArgumentException("Unsupported key " + parts[0]); | ||
| 194 | } | ||
| 195 | |||
| 196 | private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String[] parts) { | ||
| 197 | if (parts.length != 2) { | ||
| 198 | throw new IllegalArgumentException("Invalid javadoc declaration"); | ||
| 199 | } | ||
| 200 | |||
| 201 | addJavadoc(pair, parts[1]); | ||
| 202 | } | ||
| 203 | |||
| 204 | private MappingPair<ClassEntry, RawEntryMapping> parseClass(String[] tokens, boolean escapeNames) { | ||
| 205 | ClassEntry obfuscatedEntry = new ClassEntry(unescapeOpt(tokens[1], escapeNames)); | ||
| 206 | if (tokens.length <= 2) | ||
| 207 | return new MappingPair<>(obfuscatedEntry); | ||
| 208 | String token2 = unescapeOpt(tokens[2], escapeNames); | ||
| 209 | String mapping = token2.substring(token2.lastIndexOf('$') + 1); | ||
| 210 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); | ||
| 211 | } | ||
| 212 | |||
| 213 | private MappingPair<FieldEntry, RawEntryMapping> parseField(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) { | ||
| 214 | ClassEntry ownerClass = (ClassEntry) parent.getEntry(); | ||
| 215 | TypeDescriptor descriptor = new TypeDescriptor(unescapeOpt(tokens[1], escapeNames)); | ||
| 216 | |||
| 217 | FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor); | ||
| 218 | if (tokens.length <= 3) | ||
| 219 | return new MappingPair<>(obfuscatedEntry); | ||
| 220 | String mapping = unescapeOpt(tokens[3], escapeNames); | ||
| 221 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); | ||
| 222 | } | ||
| 223 | |||
| 224 | private MappingPair<MethodEntry, RawEntryMapping> parseMethod(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) { | ||
| 225 | ClassEntry ownerClass = (ClassEntry) parent.getEntry(); | ||
| 226 | MethodDescriptor descriptor = new MethodDescriptor(unescapeOpt(tokens[1], escapeNames)); | ||
| 227 | |||
| 228 | MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor); | ||
| 229 | if (tokens.length <= 3) | ||
| 230 | return new MappingPair<>(obfuscatedEntry); | ||
| 231 | String mapping = unescapeOpt(tokens[3], escapeNames); | ||
| 232 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); | ||
| 233 | } | ||
| 234 | |||
| 235 | |||
| 236 | |||
| 237 | private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String javadoc) { | ||
| 238 | RawEntryMapping mapping = pair.getMapping(); | ||
| 239 | if (mapping == null) { | ||
| 240 | throw new IllegalArgumentException("Javadoc requires a mapping in enigma!"); | ||
| 241 | } | ||
| 242 | mapping.addJavadocLine(unescape(javadoc)); | ||
| 243 | } | ||
| 244 | |||
| 245 | |||
| 246 | |||
| 247 | private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) { | ||
| 248 | MethodEntry ownerMethod = (MethodEntry) parent.getEntry(); | ||
| 249 | int variableIndex = Integer.parseInt(tokens[1]); | ||
| 250 | |||
| 251 | // tokens[2] is the useless obf name | ||
| 252 | |||
| 253 | LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null); | ||
| 254 | if (tokens.length <= 3) | ||
| 255 | return new MappingPair<>(obfuscatedEntry); | ||
| 256 | String mapping = unescapeOpt(tokens[3], escapeNames); | ||
| 257 | return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); | ||
| 258 | } | ||
| 259 | |||
| 260 | private static final String TO_ESCAPE = "\\\n\r\0\t"; | ||
| 261 | private static final String ESCAPED = "\\nr0t"; | ||
| 262 | |||
| 263 | private static String unescapeOpt(String raw, boolean escapedStrings) { | ||
| 264 | return escapedStrings ? unescape(raw) : raw; | ||
| 265 | } | ||
| 266 | |||
| 267 | private static String unescape(String str) { | ||
| 268 | // copied from matcher, lazy! | ||
| 269 | int pos = str.indexOf('\\'); | ||
| 270 | if (pos < 0) return str; | ||
| 271 | |||
| 272 | StringBuilder ret = new StringBuilder(str.length() - 1); | ||
| 273 | int start = 0; | ||
| 274 | |||
| 275 | do { | ||
| 276 | ret.append(str, start, pos); | ||
| 277 | pos++; | ||
| 278 | int type; | ||
| 279 | |||
| 280 | if (pos >= str.length()) { | ||
| 281 | throw new RuntimeException("incomplete escape sequence at the end"); | ||
| 282 | } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) { | ||
| 283 | throw new RuntimeException("invalid escape character: \\" + str.charAt(pos)); | ||
| 284 | } else { | ||
| 285 | ret.append(TO_ESCAPE.charAt(type)); | ||
| 286 | } | ||
| 287 | |||
| 288 | start = pos + 1; | ||
| 289 | } while ((pos = str.indexOf('\\', start)) >= 0); | ||
| 290 | |||
| 291 | ret.append(str, start, str.length()); | ||
| 292 | |||
| 293 | return ret.toString(); | ||
| 294 | } | ||
| 295 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java deleted file mode 100644 index 95e04c3..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java +++ /dev/null | |||
| @@ -1,169 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.serde; | ||
| 2 | |||
| 3 | import com.google.common.base.Strings; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 8 | import cuchaz.enigma.translation.mapping.MappingSaveParameters; | ||
| 9 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 10 | import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | ||
| 15 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 16 | import cuchaz.enigma.utils.LFPrintWriter; | ||
| 17 | |||
| 18 | import java.io.IOException; | ||
| 19 | import java.io.PrintWriter; | ||
| 20 | import java.nio.file.Files; | ||
| 21 | import java.nio.file.Path; | ||
| 22 | import java.util.Deque; | ||
| 23 | import java.util.LinkedList; | ||
| 24 | import java.util.List; | ||
| 25 | import java.util.stream.Collectors; | ||
| 26 | import java.util.stream.StreamSupport; | ||
| 27 | |||
| 28 | public final class TinyV2Writer implements MappingsWriter { | ||
| 29 | |||
| 30 | private static final String MINOR_VERSION = "0"; | ||
| 31 | private final String obfHeader; | ||
| 32 | private final String deobfHeader; | ||
| 33 | |||
| 34 | public TinyV2Writer(String obfHeader, String deobfHeader) { | ||
| 35 | this.obfHeader = obfHeader; | ||
| 36 | this.deobfHeader = deobfHeader; | ||
| 37 | } | ||
| 38 | |||
| 39 | @Override | ||
| 40 | public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { | ||
| 41 | List<EntryTreeNode<EntryMapping>> classes = StreamSupport.stream(mappings.spliterator(), false).filter(node -> node.getEntry() instanceof ClassEntry).collect(Collectors.toList()); | ||
| 42 | |||
| 43 | try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) { | ||
| 44 | writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfHeader + "\t" + deobfHeader); | ||
| 45 | |||
| 46 | // no escape names | ||
| 47 | |||
| 48 | for (EntryTreeNode<EntryMapping> node : classes) { | ||
| 49 | writeClass(writer, node, mappings); | ||
| 50 | } | ||
| 51 | } catch (IOException ex) { | ||
| 52 | ex.printStackTrace(); // TODO add some better logging system | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | private void writeClass(PrintWriter writer, EntryTreeNode<EntryMapping> node, EntryMap<EntryMapping> tree) { | ||
| 57 | writer.print("c\t"); | ||
| 58 | ClassEntry classEntry = (ClassEntry) node.getEntry(); | ||
| 59 | String fullName = classEntry.getFullName(); | ||
| 60 | writer.print(fullName); | ||
| 61 | Deque<String> parts = new LinkedList<>(); | ||
| 62 | do { | ||
| 63 | EntryMapping mapping = tree.get(classEntry); | ||
| 64 | if (mapping != null) { | ||
| 65 | parts.addFirst(mapping.getTargetName()); | ||
| 66 | } else { | ||
| 67 | parts.addFirst(classEntry.getName()); | ||
| 68 | } | ||
| 69 | classEntry = classEntry.getOuterClass(); | ||
| 70 | } while (classEntry != null); | ||
| 71 | |||
| 72 | String mappedName = String.join("$", parts); | ||
| 73 | |||
| 74 | writer.print("\t"); | ||
| 75 | |||
| 76 | writer.print(mappedName); // todo escaping when we have v2 fixed later | ||
| 77 | |||
| 78 | writer.println(); | ||
| 79 | |||
| 80 | writeComment(writer, node.getValue(), 1); | ||
| 81 | |||
| 82 | for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) { | ||
| 83 | Entry entry = child.getEntry(); | ||
| 84 | if (entry instanceof FieldEntry) { | ||
| 85 | writeField(writer, child); | ||
| 86 | } else if (entry instanceof MethodEntry) { | ||
| 87 | writeMethod(writer, child); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | private void writeMethod(PrintWriter writer, EntryTreeNode<EntryMapping> node) { | ||
| 93 | writer.print(indent(1)); | ||
| 94 | writer.print("m\t"); | ||
| 95 | writer.print(((MethodEntry) node.getEntry()).getDesc().toString()); | ||
| 96 | writer.print("\t"); | ||
| 97 | writer.print(node.getEntry().getName()); | ||
| 98 | writer.print("\t"); | ||
| 99 | EntryMapping mapping = node.getValue(); | ||
| 100 | if (mapping == null) { | ||
| 101 | writer.println(node.getEntry().getName()); // todo fix v2 name inference | ||
| 102 | } else { | ||
| 103 | writer.println(mapping.getTargetName()); | ||
| 104 | |||
| 105 | writeComment(writer, mapping, 2); | ||
| 106 | } | ||
| 107 | |||
| 108 | for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) { | ||
| 109 | Entry entry = child.getEntry(); | ||
| 110 | if (entry instanceof LocalVariableEntry) { | ||
| 111 | writeParameter(writer, child); | ||
| 112 | } | ||
| 113 | // TODO write actual local variables | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | private void writeField(PrintWriter writer, EntryTreeNode<EntryMapping> node) { | ||
| 118 | if (node.getValue() == null) | ||
| 119 | return; // Shortcut | ||
| 120 | |||
| 121 | writer.print(indent(1)); | ||
| 122 | writer.print("f\t"); | ||
| 123 | writer.print(((FieldEntry) node.getEntry()).getDesc().toString()); | ||
| 124 | writer.print("\t"); | ||
| 125 | writer.print(node.getEntry().getName()); | ||
| 126 | writer.print("\t"); | ||
| 127 | EntryMapping mapping = node.getValue(); | ||
| 128 | if (mapping == null) { | ||
| 129 | writer.println(node.getEntry().getName()); // todo fix v2 name inference | ||
| 130 | } else { | ||
| 131 | writer.println(mapping.getTargetName()); | ||
| 132 | |||
| 133 | writeComment(writer, mapping, 2); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | private void writeParameter(PrintWriter writer, EntryTreeNode<EntryMapping> node) { | ||
| 138 | if (node.getValue() == null) | ||
| 139 | return; // Shortcut | ||
| 140 | |||
| 141 | writer.print(indent(2)); | ||
| 142 | writer.print("p\t"); | ||
| 143 | writer.print(((LocalVariableEntry) node.getEntry()).getIndex()); | ||
| 144 | writer.print("\t"); | ||
| 145 | writer.print(node.getEntry().getName()); | ||
| 146 | writer.print("\t"); | ||
| 147 | EntryMapping mapping = node.getValue(); | ||
| 148 | if (mapping == null) { | ||
| 149 | writer.println(); // todo ??? | ||
| 150 | } else { | ||
| 151 | writer.println(mapping.getTargetName()); | ||
| 152 | |||
| 153 | writeComment(writer, mapping, 3); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) { | ||
| 158 | if (mapping != null && mapping.getJavadoc() != null) { | ||
| 159 | writer.print(indent(indent)); | ||
| 160 | writer.print("c\t"); | ||
| 161 | writer.print(MappingHelper.escape(mapping.getJavadoc())); | ||
| 162 | writer.println(); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | private String indent(int level) { | ||
| 167 | return Strings.repeat("\t", level); | ||
| 168 | } | ||
| 169 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java deleted file mode 100644 index 255fa5f..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java +++ /dev/null | |||
| @@ -1,110 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.tree; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translator; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 7 | import cuchaz.enigma.translation.mapping.MappingDelta; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 9 | |||
| 10 | import javax.annotation.Nullable; | ||
| 11 | import java.util.Collection; | ||
| 12 | import java.util.Iterator; | ||
| 13 | import java.util.stream.Stream; | ||
| 14 | |||
| 15 | public class DeltaTrackingTree<T> implements EntryTree<T> { | ||
| 16 | private final EntryTree<T> delegate; | ||
| 17 | |||
| 18 | private EntryTree<T> deltaReference; | ||
| 19 | private EntryTree<Object> changes = new HashEntryTree<>(); | ||
| 20 | |||
| 21 | public DeltaTrackingTree(EntryTree<T> delegate) { | ||
| 22 | this.delegate = delegate; | ||
| 23 | this.deltaReference = new HashEntryTree<>(delegate); | ||
| 24 | } | ||
| 25 | |||
| 26 | public DeltaTrackingTree() { | ||
| 27 | this(new HashEntryTree<>()); | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | public void insert(Entry<?> entry, T value) { | ||
| 32 | trackChange(entry); | ||
| 33 | delegate.insert(entry, value); | ||
| 34 | } | ||
| 35 | |||
| 36 | @Nullable | ||
| 37 | @Override | ||
| 38 | public T remove(Entry<?> entry) { | ||
| 39 | trackChange(entry); | ||
| 40 | return delegate.remove(entry); | ||
| 41 | } | ||
| 42 | |||
| 43 | public void trackChange(Entry<?> entry) { | ||
| 44 | changes.insert(entry, MappingDelta.PLACEHOLDER); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Nullable | ||
| 48 | @Override | ||
| 49 | public T get(Entry<?> entry) { | ||
| 50 | return delegate.get(entry); | ||
| 51 | } | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public Collection<Entry<?>> getChildren(Entry<?> entry) { | ||
| 55 | return delegate.getChildren(entry); | ||
| 56 | } | ||
| 57 | |||
| 58 | @Override | ||
| 59 | public Collection<Entry<?>> getSiblings(Entry<?> entry) { | ||
| 60 | return delegate.getSiblings(entry); | ||
| 61 | } | ||
| 62 | |||
| 63 | @Nullable | ||
| 64 | @Override | ||
| 65 | public EntryTreeNode<T> findNode(Entry<?> entry) { | ||
| 66 | return delegate.findNode(entry); | ||
| 67 | } | ||
| 68 | |||
| 69 | @Override | ||
| 70 | public Stream<EntryTreeNode<T>> getRootNodes() { | ||
| 71 | return delegate.getRootNodes(); | ||
| 72 | } | ||
| 73 | |||
| 74 | @Override | ||
| 75 | public DeltaTrackingTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 76 | DeltaTrackingTree<T> translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings)); | ||
| 77 | translatedTree.changes = changes.translate(translator, resolver, mappings); | ||
| 78 | return translatedTree; | ||
| 79 | } | ||
| 80 | |||
| 81 | @Override | ||
| 82 | public Stream<Entry<?>> getAllEntries() { | ||
| 83 | return delegate.getAllEntries(); | ||
| 84 | } | ||
| 85 | |||
| 86 | @Override | ||
| 87 | public boolean isEmpty() { | ||
| 88 | return delegate.isEmpty(); | ||
| 89 | } | ||
| 90 | |||
| 91 | @Override | ||
| 92 | public Iterator<EntryTreeNode<T>> iterator() { | ||
| 93 | return delegate.iterator(); | ||
| 94 | } | ||
| 95 | |||
| 96 | public MappingDelta<T> takeDelta() { | ||
| 97 | MappingDelta<T> delta = new MappingDelta<>(deltaReference, changes); | ||
| 98 | resetDelta(); | ||
| 99 | return delta; | ||
| 100 | } | ||
| 101 | |||
| 102 | private void resetDelta() { | ||
| 103 | deltaReference = new HashEntryTree<>(delegate); | ||
| 104 | changes = new HashEntryTree<>(); | ||
| 105 | } | ||
| 106 | |||
| 107 | public boolean isDirty() { | ||
| 108 | return !changes.isEmpty(); | ||
| 109 | } | ||
| 110 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java deleted file mode 100644 index daaefcc..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.tree; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translatable; | ||
| 4 | import cuchaz.enigma.translation.Translator; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 9 | |||
| 10 | import javax.annotation.Nullable; | ||
| 11 | import java.util.Collection; | ||
| 12 | import java.util.stream.Stream; | ||
| 13 | |||
| 14 | public interface EntryTree<T> extends EntryMap<T>, Iterable<EntryTreeNode<T>>, Translatable { | ||
| 15 | Collection<Entry<?>> getChildren(Entry<?> entry); | ||
| 16 | |||
| 17 | Collection<Entry<?>> getSiblings(Entry<?> entry); | ||
| 18 | |||
| 19 | @Nullable | ||
| 20 | EntryTreeNode<T> findNode(Entry<?> entry); | ||
| 21 | |||
| 22 | Stream<EntryTreeNode<T>> getRootNodes(); | ||
| 23 | |||
| 24 | @Override | ||
| 25 | EntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings); | ||
| 26 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java deleted file mode 100644 index affcd50..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java +++ /dev/null | |||
| @@ -1,40 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.tree; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | import java.util.ArrayList; | ||
| 7 | import java.util.Collection; | ||
| 8 | import java.util.stream.Collectors; | ||
| 9 | |||
| 10 | public interface EntryTreeNode<T> { | ||
| 11 | @Nullable | ||
| 12 | T getValue(); | ||
| 13 | |||
| 14 | Entry<?> getEntry(); | ||
| 15 | |||
| 16 | boolean isEmpty(); | ||
| 17 | |||
| 18 | Collection<Entry<?>> getChildren(); | ||
| 19 | |||
| 20 | Collection<? extends EntryTreeNode<T>> getChildNodes(); | ||
| 21 | |||
| 22 | default Collection<? extends EntryTreeNode<T>> getNodesRecursively() { | ||
| 23 | Collection<EntryTreeNode<T>> nodes = new ArrayList<>(); | ||
| 24 | nodes.add(this); | ||
| 25 | for (EntryTreeNode<T> node : getChildNodes()) { | ||
| 26 | nodes.addAll(node.getNodesRecursively()); | ||
| 27 | } | ||
| 28 | return nodes; | ||
| 29 | } | ||
| 30 | |||
| 31 | default Collection<Entry<?>> getChildrenRecursively() { | ||
| 32 | return getNodesRecursively().stream() | ||
| 33 | .map(EntryTreeNode::getEntry) | ||
| 34 | .collect(Collectors.toList()); | ||
| 35 | } | ||
| 36 | |||
| 37 | default boolean hasValue() { | ||
| 38 | return getValue() != null; | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java deleted file mode 100644 index 570941c..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java +++ /dev/null | |||
| @@ -1,188 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.tree; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translator; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 8 | |||
| 9 | import javax.annotation.Nullable; | ||
| 10 | import java.util.*; | ||
| 11 | import java.util.function.Function; | ||
| 12 | import java.util.stream.Stream; | ||
| 13 | import java.util.stream.StreamSupport; | ||
| 14 | |||
| 15 | public class HashEntryTree<T> implements EntryTree<T> { | ||
| 16 | private final Map<Entry<?>, HashTreeNode<T>> root = new HashMap<>(); | ||
| 17 | |||
| 18 | public HashEntryTree() { | ||
| 19 | } | ||
| 20 | |||
| 21 | public HashEntryTree(EntryTree<T> tree) { | ||
| 22 | for (EntryTreeNode<T> node : tree) { | ||
| 23 | insert(node.getEntry(), node.getValue()); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void insert(Entry<?> entry, T value) { | ||
| 29 | List<HashTreeNode<T>> path = computePath(entry, true); | ||
| 30 | path.get(path.size() - 1).putValue(value); | ||
| 31 | if (value == null) { | ||
| 32 | removeDeadAlong(path); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | @Override | ||
| 37 | @Nullable | ||
| 38 | public T remove(Entry<?> entry) { | ||
| 39 | List<HashTreeNode<T>> path = computePath(entry, false); | ||
| 40 | if (path.isEmpty()) { | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | |||
| 44 | T value = path.get(path.size() - 1).removeValue(); | ||
| 45 | |||
| 46 | removeDeadAlong(path); | ||
| 47 | |||
| 48 | return value; | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | @Nullable | ||
| 53 | public T get(Entry<?> entry) { | ||
| 54 | HashTreeNode<T> node = findNode(entry); | ||
| 55 | if (node == null) { | ||
| 56 | return null; | ||
| 57 | } | ||
| 58 | return node.getValue(); | ||
| 59 | } | ||
| 60 | |||
| 61 | @Override | ||
| 62 | public boolean contains(Entry<?> entry) { | ||
| 63 | return get(entry) != null; | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public Collection<Entry<?>> getChildren(Entry<?> entry) { | ||
| 68 | HashTreeNode<T> leaf = findNode(entry); | ||
| 69 | if (leaf == null) { | ||
| 70 | return Collections.emptyList(); | ||
| 71 | } | ||
| 72 | return leaf.getChildren(); | ||
| 73 | } | ||
| 74 | |||
| 75 | @Override | ||
| 76 | public Collection<Entry<?>> getSiblings(Entry<?> entry) { | ||
| 77 | Entry<?> parent = entry.getParent(); | ||
| 78 | if (parent == null) { | ||
| 79 | return getSiblings(entry, root.keySet()); | ||
| 80 | } | ||
| 81 | return getSiblings(entry, getChildren(parent)); | ||
| 82 | } | ||
| 83 | |||
| 84 | private Collection<Entry<?>> getSiblings(Entry<?> entry, Collection<Entry<?>> generation) { | ||
| 85 | Set<Entry<?>> siblings = new HashSet<>(generation); | ||
| 86 | siblings.remove(entry); | ||
| 87 | return siblings; | ||
| 88 | } | ||
| 89 | |||
| 90 | @Override | ||
| 91 | @Nullable | ||
| 92 | public HashTreeNode<T> findNode(Entry<?> target) { | ||
| 93 | List<Entry<?>> parentChain = target.getAncestry(); | ||
| 94 | if (parentChain.isEmpty()) { | ||
| 95 | return null; | ||
| 96 | } | ||
| 97 | |||
| 98 | HashTreeNode<T> node = root.get(parentChain.get(0)); | ||
| 99 | for (int i = 1; i < parentChain.size(); i++) { | ||
| 100 | if (node == null) { | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | node = node.getChild(parentChain.get(i)); | ||
| 104 | } | ||
| 105 | |||
| 106 | return node; | ||
| 107 | } | ||
| 108 | |||
| 109 | private List<HashTreeNode<T>> computePath(Entry<?> target, boolean make) { | ||
| 110 | List<Entry<?>> ancestry = target.getAncestry(); | ||
| 111 | if (ancestry.isEmpty()) { | ||
| 112 | return Collections.emptyList(); | ||
| 113 | } | ||
| 114 | |||
| 115 | List<HashTreeNode<T>> path = new ArrayList<>(ancestry.size()); | ||
| 116 | |||
| 117 | Entry<?> rootEntry = ancestry.get(0); | ||
| 118 | HashTreeNode<T> node = make ? root.computeIfAbsent(rootEntry, HashTreeNode::new) : root.get(rootEntry); | ||
| 119 | if (node == null) { | ||
| 120 | return Collections.emptyList(); | ||
| 121 | } | ||
| 122 | |||
| 123 | path.add(node); | ||
| 124 | |||
| 125 | for (int i = 1; i < ancestry.size(); i++) { | ||
| 126 | Entry<?> ancestor = ancestry.get(i); | ||
| 127 | node = make ? node.computeChild(ancestor) : node.getChild(ancestor); | ||
| 128 | if (node == null) { | ||
| 129 | return Collections.emptyList(); | ||
| 130 | } | ||
| 131 | |||
| 132 | path.add(node); | ||
| 133 | } | ||
| 134 | |||
| 135 | return path; | ||
| 136 | } | ||
| 137 | |||
| 138 | private void removeDeadAlong(List<HashTreeNode<T>> path) { | ||
| 139 | for (int i = path.size() - 1; i >= 0; i--) { | ||
| 140 | HashTreeNode<T> node = path.get(i); | ||
| 141 | if (node.isEmpty()) { | ||
| 142 | if (i > 0) { | ||
| 143 | HashTreeNode<T> parentNode = path.get(i - 1); | ||
| 144 | parentNode.remove(node.getEntry()); | ||
| 145 | } else { | ||
| 146 | root.remove(node.getEntry()); | ||
| 147 | } | ||
| 148 | } else { | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | @Override | ||
| 155 | public Iterator<EntryTreeNode<T>> iterator() { | ||
| 156 | Collection<EntryTreeNode<T>> nodes = new ArrayList<>(); | ||
| 157 | for (EntryTreeNode<T> node : root.values()) { | ||
| 158 | nodes.addAll(node.getNodesRecursively()); | ||
| 159 | } | ||
| 160 | return nodes.iterator(); | ||
| 161 | } | ||
| 162 | |||
| 163 | @Override | ||
| 164 | public Stream<Entry<?>> getAllEntries() { | ||
| 165 | return StreamSupport.stream(spliterator(), false) | ||
| 166 | .filter(EntryTreeNode::hasValue) | ||
| 167 | .map(EntryTreeNode::getEntry); | ||
| 168 | } | ||
| 169 | |||
| 170 | @Override | ||
| 171 | public Stream<EntryTreeNode<T>> getRootNodes() { | ||
| 172 | return root.values().stream().map(Function.identity()); | ||
| 173 | } | ||
| 174 | |||
| 175 | @Override | ||
| 176 | public boolean isEmpty() { | ||
| 177 | return root.isEmpty(); | ||
| 178 | } | ||
| 179 | |||
| 180 | @Override | ||
| 181 | public HashEntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 182 | HashEntryTree<T> translatedTree = new HashEntryTree<>(); | ||
| 183 | for (EntryTreeNode<T> node : this) { | ||
| 184 | translatedTree.insert(translator.translate(node.getEntry()), node.getValue()); | ||
| 185 | } | ||
| 186 | return translatedTree; | ||
| 187 | } | ||
| 188 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java deleted file mode 100644 index 0a990bd..0000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java +++ /dev/null | |||
| @@ -1,75 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping.tree; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 4 | |||
| 5 | import javax.annotation.Nonnull; | ||
| 6 | import javax.annotation.Nullable; | ||
| 7 | import java.util.Collection; | ||
| 8 | import java.util.HashMap; | ||
| 9 | import java.util.Iterator; | ||
| 10 | import java.util.Map; | ||
| 11 | |||
| 12 | public class HashTreeNode<T> implements EntryTreeNode<T>, Iterable<HashTreeNode<T>> { | ||
| 13 | private final Entry<?> entry; | ||
| 14 | private final Map<Entry<?>, HashTreeNode<T>> children = new HashMap<>(); | ||
| 15 | private T value; | ||
| 16 | |||
| 17 | HashTreeNode(Entry<?> entry) { | ||
| 18 | this.entry = entry; | ||
| 19 | } | ||
| 20 | |||
| 21 | void putValue(T value) { | ||
| 22 | this.value = value; | ||
| 23 | } | ||
| 24 | |||
| 25 | T removeValue() { | ||
| 26 | T value = this.value; | ||
| 27 | this.value = null; | ||
| 28 | return value; | ||
| 29 | } | ||
| 30 | |||
| 31 | @Nullable | ||
| 32 | HashTreeNode<T> getChild(Entry<?> entry) { | ||
| 33 | return children.get(entry); | ||
| 34 | } | ||
| 35 | |||
| 36 | @Nonnull | ||
| 37 | HashTreeNode<T> computeChild(Entry<?> entry) { | ||
| 38 | return children.computeIfAbsent(entry, HashTreeNode::new); | ||
| 39 | } | ||
| 40 | |||
| 41 | void remove(Entry<?> entry) { | ||
| 42 | children.remove(entry); | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | @Nullable | ||
| 47 | public T getValue() { | ||
| 48 | return value; | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public Entry<?> getEntry() { | ||
| 53 | return entry; | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public boolean isEmpty() { | ||
| 58 | return children.isEmpty() && value == null; | ||
| 59 | } | ||
| 60 | |||
| 61 | @Override | ||
| 62 | public Collection<Entry<?>> getChildren() { | ||
| 63 | return children.keySet(); | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public Collection<? extends EntryTreeNode<T>> getChildNodes() { | ||
| 68 | return children.values(); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public Iterator<HashTreeNode<T>> iterator() { | ||
| 73 | return children.values().iterator(); | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java deleted file mode 100644 index b280eef..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java +++ /dev/null | |||
| @@ -1,116 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.representation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.Access; | ||
| 4 | import org.objectweb.asm.Opcodes; | ||
| 5 | |||
| 6 | import java.lang.reflect.Modifier; | ||
| 7 | |||
| 8 | public class AccessFlags { | ||
| 9 | public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); | ||
| 10 | public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); | ||
| 11 | |||
| 12 | private int flags; | ||
| 13 | |||
| 14 | public AccessFlags(int flags) { | ||
| 15 | this.flags = flags; | ||
| 16 | } | ||
| 17 | |||
| 18 | public boolean isPrivate() { | ||
| 19 | return Modifier.isPrivate(this.flags); | ||
| 20 | } | ||
| 21 | |||
| 22 | public boolean isProtected() { | ||
| 23 | return Modifier.isProtected(this.flags); | ||
| 24 | } | ||
| 25 | |||
| 26 | public boolean isPublic() { | ||
| 27 | return Modifier.isPublic(this.flags); | ||
| 28 | } | ||
| 29 | |||
| 30 | public boolean isSynthetic() { | ||
| 31 | return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; | ||
| 32 | } | ||
| 33 | |||
| 34 | public boolean isStatic() { | ||
| 35 | return Modifier.isStatic(this.flags); | ||
| 36 | } | ||
| 37 | |||
| 38 | public boolean isEnum() { | ||
| 39 | return (flags & Opcodes.ACC_ENUM) != 0; | ||
| 40 | } | ||
| 41 | |||
| 42 | public boolean isBridge() { | ||
| 43 | return (flags & Opcodes.ACC_BRIDGE) != 0; | ||
| 44 | } | ||
| 45 | |||
| 46 | public boolean isFinal() { | ||
| 47 | return (flags & Opcodes.ACC_FINAL) != 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | public boolean isInterface() { | ||
| 51 | return (flags & Opcodes.ACC_INTERFACE) != 0; | ||
| 52 | } | ||
| 53 | |||
| 54 | public AccessFlags setPrivate() { | ||
| 55 | this.setVisibility(Opcodes.ACC_PRIVATE); | ||
| 56 | return this; | ||
| 57 | } | ||
| 58 | |||
| 59 | public AccessFlags setProtected() { | ||
| 60 | this.setVisibility(Opcodes.ACC_PROTECTED); | ||
| 61 | return this; | ||
| 62 | } | ||
| 63 | |||
| 64 | public AccessFlags setPublic() { | ||
| 65 | this.setVisibility(Opcodes.ACC_PUBLIC); | ||
| 66 | return this; | ||
| 67 | } | ||
| 68 | |||
| 69 | public AccessFlags setBridge() { | ||
| 70 | flags |= Opcodes.ACC_BRIDGE; | ||
| 71 | return this; | ||
| 72 | } | ||
| 73 | |||
| 74 | @Deprecated | ||
| 75 | public AccessFlags setBridged() { | ||
| 76 | return setBridge(); | ||
| 77 | } | ||
| 78 | |||
| 79 | public void setVisibility(int visibility) { | ||
| 80 | this.resetVisibility(); | ||
| 81 | this.flags |= visibility; | ||
| 82 | } | ||
| 83 | |||
| 84 | private void resetVisibility() { | ||
| 85 | this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); | ||
| 86 | } | ||
| 87 | |||
| 88 | public int getFlags() { | ||
| 89 | return this.flags; | ||
| 90 | } | ||
| 91 | |||
| 92 | @Override | ||
| 93 | public boolean equals(Object obj) { | ||
| 94 | return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; | ||
| 95 | } | ||
| 96 | |||
| 97 | @Override | ||
| 98 | public int hashCode() { | ||
| 99 | return flags; | ||
| 100 | } | ||
| 101 | |||
| 102 | @Override | ||
| 103 | public String toString() { | ||
| 104 | StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase()); | ||
| 105 | if (isStatic()) { | ||
| 106 | builder.append(" static"); | ||
| 107 | } | ||
| 108 | if (isSynthetic()) { | ||
| 109 | builder.append(" synthetic"); | ||
| 110 | } | ||
| 111 | if (isBridge()) { | ||
| 112 | builder.append(" bridge"); | ||
| 113 | } | ||
| 114 | return builder.toString(); | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java deleted file mode 100644 index 63eb563..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java +++ /dev/null | |||
| @@ -1,105 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.representation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translatable; | ||
| 4 | import cuchaz.enigma.translation.Translator; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 8 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ParentedEntry; | ||
| 12 | |||
| 13 | import java.util.Objects; | ||
| 14 | |||
| 15 | public class Lambda implements Translatable { | ||
| 16 | private final String invokedName; | ||
| 17 | private final MethodDescriptor invokedType; | ||
| 18 | private final MethodDescriptor samMethodType; | ||
| 19 | private final ParentedEntry<?> implMethod; | ||
| 20 | private final MethodDescriptor instantiatedMethodType; | ||
| 21 | |||
| 22 | public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry<?> implMethod, MethodDescriptor instantiatedMethodType) { | ||
| 23 | this.invokedName = invokedName; | ||
| 24 | this.invokedType = invokedType; | ||
| 25 | this.samMethodType = samMethodType; | ||
| 26 | this.implMethod = implMethod; | ||
| 27 | this.instantiatedMethodType = instantiatedMethodType; | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | public Lambda translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 32 | MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType); | ||
| 33 | EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); | ||
| 34 | |||
| 35 | return new Lambda( | ||
| 36 | samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, | ||
| 37 | invokedType.translate(translator, resolver, mappings), | ||
| 38 | samMethodType.translate(translator, resolver, mappings), | ||
| 39 | implMethod.translate(translator, resolver, mappings), | ||
| 40 | instantiatedMethodType.translate(translator, resolver, mappings) | ||
| 41 | ); | ||
| 42 | } | ||
| 43 | |||
| 44 | private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings, MethodEntry methodEntry) { | ||
| 45 | for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) { | ||
| 46 | EntryMapping mapping = mappings.get(entry); | ||
| 47 | if (mapping != null) { | ||
| 48 | return mapping; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | return null; | ||
| 52 | } | ||
| 53 | |||
| 54 | public ClassEntry getInterface() { | ||
| 55 | return invokedType.getReturnDesc().getTypeEntry(); | ||
| 56 | } | ||
| 57 | |||
| 58 | public String getInvokedName() { | ||
| 59 | return invokedName; | ||
| 60 | } | ||
| 61 | |||
| 62 | public MethodDescriptor getInvokedType() { | ||
| 63 | return invokedType; | ||
| 64 | } | ||
| 65 | |||
| 66 | public MethodDescriptor getSamMethodType() { | ||
| 67 | return samMethodType; | ||
| 68 | } | ||
| 69 | |||
| 70 | public ParentedEntry<?> getImplMethod() { | ||
| 71 | return implMethod; | ||
| 72 | } | ||
| 73 | |||
| 74 | public MethodDescriptor getInstantiatedMethodType() { | ||
| 75 | return instantiatedMethodType; | ||
| 76 | } | ||
| 77 | |||
| 78 | @Override | ||
| 79 | public boolean equals(Object o) { | ||
| 80 | if (this == o) return true; | ||
| 81 | if (o == null || getClass() != o.getClass()) return false; | ||
| 82 | Lambda lambda = (Lambda) o; | ||
| 83 | return Objects.equals(invokedName, lambda.invokedName) && | ||
| 84 | Objects.equals(invokedType, lambda.invokedType) && | ||
| 85 | Objects.equals(samMethodType, lambda.samMethodType) && | ||
| 86 | Objects.equals(implMethod, lambda.implMethod) && | ||
| 87 | Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType); | ||
| 88 | } | ||
| 89 | |||
| 90 | @Override | ||
| 91 | public int hashCode() { | ||
| 92 | return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); | ||
| 93 | } | ||
| 94 | |||
| 95 | @Override | ||
| 96 | public String toString() { | ||
| 97 | return "Lambda{" + | ||
| 98 | "invokedName='" + invokedName + '\'' + | ||
| 99 | ", invokedType=" + invokedType + | ||
| 100 | ", samMethodType=" + samMethodType + | ||
| 101 | ", implMethod=" + implMethod + | ||
| 102 | ", instantiatedMethodType=" + instantiatedMethodType + | ||
| 103 | '}'; | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java deleted file mode 100644 index 37a7014..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java +++ /dev/null | |||
| @@ -1,132 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import cuchaz.enigma.translation.Translatable; | ||
| 16 | import cuchaz.enigma.translation.Translator; | ||
| 17 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 18 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 19 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 21 | import cuchaz.enigma.utils.Utils; | ||
| 22 | |||
| 23 | import java.util.ArrayList; | ||
| 24 | import java.util.List; | ||
| 25 | import java.util.function.Function; | ||
| 26 | |||
| 27 | public class MethodDescriptor implements Translatable { | ||
| 28 | |||
| 29 | private List<TypeDescriptor> argumentDescs; | ||
| 30 | private TypeDescriptor returnDesc; | ||
| 31 | |||
| 32 | public MethodDescriptor(String desc) { | ||
| 33 | try { | ||
| 34 | this.argumentDescs = Lists.newArrayList(); | ||
| 35 | int i = 0; | ||
| 36 | while (i < desc.length()) { | ||
| 37 | char c = desc.charAt(i); | ||
| 38 | if (c == '(') { | ||
| 39 | assert (this.argumentDescs.isEmpty()); | ||
| 40 | assert (this.returnDesc == null); | ||
| 41 | i++; | ||
| 42 | } else if (c == ')') { | ||
| 43 | i++; | ||
| 44 | break; | ||
| 45 | } else { | ||
| 46 | String type = TypeDescriptor.parseFirst(desc.substring(i)); | ||
| 47 | this.argumentDescs.add(new TypeDescriptor(type)); | ||
| 48 | i += type.length(); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); | ||
| 52 | } catch (Exception ex) { | ||
| 53 | throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | public MethodDescriptor(List<TypeDescriptor> argumentDescs, TypeDescriptor returnDesc) { | ||
| 58 | this.argumentDescs = argumentDescs; | ||
| 59 | this.returnDesc = returnDesc; | ||
| 60 | } | ||
| 61 | |||
| 62 | public List<TypeDescriptor> getArgumentDescs() { | ||
| 63 | return this.argumentDescs; | ||
| 64 | } | ||
| 65 | |||
| 66 | public TypeDescriptor getReturnDesc() { | ||
| 67 | return this.returnDesc; | ||
| 68 | } | ||
| 69 | |||
| 70 | @Override | ||
| 71 | public String toString() { | ||
| 72 | StringBuilder buf = new StringBuilder(); | ||
| 73 | buf.append("("); | ||
| 74 | for (TypeDescriptor desc : this.argumentDescs) { | ||
| 75 | buf.append(desc); | ||
| 76 | } | ||
| 77 | buf.append(")"); | ||
| 78 | buf.append(this.returnDesc); | ||
| 79 | return buf.toString(); | ||
| 80 | } | ||
| 81 | |||
| 82 | public Iterable<TypeDescriptor> types() { | ||
| 83 | List<TypeDescriptor> descs = Lists.newArrayList(); | ||
| 84 | descs.addAll(this.argumentDescs); | ||
| 85 | descs.add(this.returnDesc); | ||
| 86 | return descs; | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public boolean equals(Object other) { | ||
| 91 | return other instanceof MethodDescriptor && equals((MethodDescriptor) other); | ||
| 92 | } | ||
| 93 | |||
| 94 | public boolean equals(MethodDescriptor other) { | ||
| 95 | return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); | ||
| 96 | } | ||
| 97 | |||
| 98 | @Override | ||
| 99 | public int hashCode() { | ||
| 100 | return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); | ||
| 101 | } | ||
| 102 | |||
| 103 | public boolean hasClass(ClassEntry classEntry) { | ||
| 104 | for (TypeDescriptor desc : types()) { | ||
| 105 | if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | return false; | ||
| 110 | } | ||
| 111 | |||
| 112 | public MethodDescriptor remap(Function<String, String> remapper) { | ||
| 113 | List<TypeDescriptor> argumentDescs = new ArrayList<>(this.argumentDescs.size()); | ||
| 114 | for (TypeDescriptor desc : this.argumentDescs) { | ||
| 115 | argumentDescs.add(desc.remap(remapper)); | ||
| 116 | } | ||
| 117 | return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); | ||
| 118 | } | ||
| 119 | |||
| 120 | @Override | ||
| 121 | public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 122 | List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size()); | ||
| 123 | for (TypeDescriptor argument : argumentDescs) { | ||
| 124 | translatedArguments.add(translator.translate(argument)); | ||
| 125 | } | ||
| 126 | return new MethodDescriptor(translatedArguments, translator.translate(returnDesc)); | ||
| 127 | } | ||
| 128 | |||
| 129 | public boolean canConflictWith(MethodDescriptor descriptor) { | ||
| 130 | return descriptor.argumentDescs.equals(argumentDescs); | ||
| 131 | } | ||
| 132 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java deleted file mode 100644 index 424088a..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/Signature.java +++ /dev/null | |||
| @@ -1,98 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.representation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; | ||
| 4 | import cuchaz.enigma.translation.Translatable; | ||
| 5 | import cuchaz.enigma.translation.Translator; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 8 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 10 | import org.objectweb.asm.signature.SignatureReader; | ||
| 11 | import org.objectweb.asm.signature.SignatureVisitor; | ||
| 12 | import org.objectweb.asm.signature.SignatureWriter; | ||
| 13 | |||
| 14 | import java.util.function.Function; | ||
| 15 | import java.util.regex.Pattern; | ||
| 16 | |||
| 17 | public class Signature implements Translatable { | ||
| 18 | private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); | ||
| 19 | |||
| 20 | private final String signature; | ||
| 21 | private final boolean isType; | ||
| 22 | |||
| 23 | private Signature(String signature, boolean isType) { | ||
| 24 | if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { | ||
| 25 | signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); | ||
| 26 | } | ||
| 27 | |||
| 28 | this.signature = signature; | ||
| 29 | this.isType = isType; | ||
| 30 | } | ||
| 31 | |||
| 32 | public static Signature createTypedSignature(String signature) { | ||
| 33 | if (signature != null && !signature.isEmpty()) { | ||
| 34 | return new Signature(signature, true); | ||
| 35 | } | ||
| 36 | return new Signature(null, true); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static Signature createSignature(String signature) { | ||
| 40 | if (signature != null && !signature.isEmpty()) { | ||
| 41 | return new Signature(signature, false); | ||
| 42 | } | ||
| 43 | return new Signature(null, false); | ||
| 44 | } | ||
| 45 | |||
| 46 | public String getSignature() { | ||
| 47 | return signature; | ||
| 48 | } | ||
| 49 | |||
| 50 | public boolean isType() { | ||
| 51 | return isType; | ||
| 52 | } | ||
| 53 | |||
| 54 | public Signature remap(Function<String, String> remapper) { | ||
| 55 | if (signature == null) { | ||
| 56 | return this; | ||
| 57 | } | ||
| 58 | SignatureWriter writer = new SignatureWriter(); | ||
| 59 | SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); | ||
| 60 | if (isType) { | ||
| 61 | new SignatureReader(signature).acceptType(visitor); | ||
| 62 | } else { | ||
| 63 | new SignatureReader(signature).accept(visitor); | ||
| 64 | } | ||
| 65 | return new Signature(writer.toString(), isType); | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public boolean equals(Object obj) { | ||
| 70 | if (obj instanceof Signature) { | ||
| 71 | Signature other = (Signature) obj; | ||
| 72 | return (other.signature == null && signature == null || other.signature != null | ||
| 73 | && signature != null && other.signature.equals(signature)) | ||
| 74 | && other.isType == this.isType; | ||
| 75 | } | ||
| 76 | return false; | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public int hashCode() { | ||
| 81 | int hash = (isType ? 1 : 0) << 16; | ||
| 82 | if (signature != null) { | ||
| 83 | hash |= signature.hashCode(); | ||
| 84 | } | ||
| 85 | |||
| 86 | return hash; | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public String toString() { | ||
| 91 | return signature; | ||
| 92 | } | ||
| 93 | |||
| 94 | @Override | ||
| 95 | public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 96 | return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); | ||
| 97 | } | ||
| 98 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java deleted file mode 100644 index f7ba849..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java +++ /dev/null | |||
| @@ -1,268 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import com.google.common.collect.Maps; | ||
| 16 | import cuchaz.enigma.translation.Translatable; | ||
| 17 | import cuchaz.enigma.translation.Translator; | ||
| 18 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 19 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 20 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 22 | |||
| 23 | import java.util.Map; | ||
| 24 | import java.util.function.Function; | ||
| 25 | |||
| 26 | public class TypeDescriptor implements Translatable { | ||
| 27 | |||
| 28 | protected final String desc; | ||
| 29 | |||
| 30 | public TypeDescriptor(String desc) { | ||
| 31 | Preconditions.checkNotNull(desc, "Desc cannot be null"); | ||
| 32 | |||
| 33 | // don't deal with generics | ||
| 34 | // this is just for raw jvm types | ||
| 35 | if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { | ||
| 36 | throw new IllegalArgumentException("don't use with generic types or templates: " + desc); | ||
| 37 | } | ||
| 38 | |||
| 39 | this.desc = desc; | ||
| 40 | } | ||
| 41 | |||
| 42 | public static String parseFirst(String in) { | ||
| 43 | |||
| 44 | if (in == null || in.length() <= 0) { | ||
| 45 | throw new IllegalArgumentException("No desc to parse, input is empty!"); | ||
| 46 | } | ||
| 47 | |||
| 48 | // read one desc from the input | ||
| 49 | |||
| 50 | char c = in.charAt(0); | ||
| 51 | |||
| 52 | // first check for void | ||
| 53 | if (c == 'V') { | ||
| 54 | return "V"; | ||
| 55 | } | ||
| 56 | |||
| 57 | // then check for primitives | ||
| 58 | Primitive primitive = Primitive.get(c); | ||
| 59 | if (primitive != null) { | ||
| 60 | return in.substring(0, 1); | ||
| 61 | } | ||
| 62 | |||
| 63 | // then check for classes | ||
| 64 | if (c == 'L') { | ||
| 65 | return readClass(in); | ||
| 66 | } | ||
| 67 | |||
| 68 | // then check for templates | ||
| 69 | if (c == 'T') { | ||
| 70 | return readClass(in); | ||
| 71 | } | ||
| 72 | |||
| 73 | // then check for arrays | ||
| 74 | int dim = countArrayDimension(in); | ||
| 75 | if (dim > 0) { | ||
| 76 | String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); | ||
| 77 | return in.substring(0, dim + arrayType.length()); | ||
| 78 | } | ||
| 79 | |||
| 80 | throw new IllegalArgumentException("don't know how to parse: " + in); | ||
| 81 | } | ||
| 82 | |||
| 83 | private static int countArrayDimension(String in) { | ||
| 84 | int i = 0; | ||
| 85 | while (i < in.length() && in.charAt(i) == '[') | ||
| 86 | i++; | ||
| 87 | return i; | ||
| 88 | } | ||
| 89 | |||
| 90 | private static String readClass(String in) { | ||
| 91 | // read all the characters in the buffer until we hit a ';' | ||
| 92 | // include the parameters too | ||
| 93 | StringBuilder buf = new StringBuilder(); | ||
| 94 | int depth = 0; | ||
| 95 | for (int i = 0; i < in.length(); i++) { | ||
| 96 | char c = in.charAt(i); | ||
| 97 | buf.append(c); | ||
| 98 | |||
| 99 | if (c == '<') { | ||
| 100 | depth++; | ||
| 101 | } else if (c == '>') { | ||
| 102 | depth--; | ||
| 103 | } else if (depth == 0 && c == ';') { | ||
| 104 | return buf.toString(); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | return null; | ||
| 108 | } | ||
| 109 | |||
| 110 | public static TypeDescriptor of(String name) { | ||
| 111 | return new TypeDescriptor("L" + name + ";"); | ||
| 112 | } | ||
| 113 | |||
| 114 | @Override | ||
| 115 | public String toString() { | ||
| 116 | return this.desc; | ||
| 117 | } | ||
| 118 | |||
| 119 | public boolean isVoid() { | ||
| 120 | return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; | ||
| 121 | } | ||
| 122 | |||
| 123 | public boolean isPrimitive() { | ||
| 124 | return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; | ||
| 125 | } | ||
| 126 | |||
| 127 | public Primitive getPrimitive() { | ||
| 128 | if (!isPrimitive()) { | ||
| 129 | throw new IllegalStateException("not a primitive"); | ||
| 130 | } | ||
| 131 | return Primitive.get(this.desc.charAt(0)); | ||
| 132 | } | ||
| 133 | |||
| 134 | public boolean isType() { | ||
| 135 | return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; | ||
| 136 | } | ||
| 137 | |||
| 138 | public ClassEntry getTypeEntry() { | ||
| 139 | if (isType()) { | ||
| 140 | String name = this.desc.substring(1, this.desc.length() - 1); | ||
| 141 | |||
| 142 | int pos = name.indexOf('<'); | ||
| 143 | if (pos >= 0) { | ||
| 144 | // remove the parameters from the class name | ||
| 145 | name = name.substring(0, pos); | ||
| 146 | } | ||
| 147 | |||
| 148 | return new ClassEntry(name); | ||
| 149 | |||
| 150 | } else if (isArray() && getArrayType().isType()) { | ||
| 151 | return getArrayType().getTypeEntry(); | ||
| 152 | } else { | ||
| 153 | throw new IllegalStateException("desc doesn't have a class"); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | public boolean isArray() { | ||
| 158 | return this.desc.charAt(0) == '['; | ||
| 159 | } | ||
| 160 | |||
| 161 | public int getArrayDimension() { | ||
| 162 | if (!isArray()) { | ||
| 163 | throw new IllegalStateException("not an array"); | ||
| 164 | } | ||
| 165 | return countArrayDimension(this.desc); | ||
| 166 | } | ||
| 167 | |||
| 168 | public TypeDescriptor getArrayType() { | ||
| 169 | if (!isArray()) { | ||
| 170 | throw new IllegalStateException("not an array"); | ||
| 171 | } | ||
| 172 | return new TypeDescriptor(this.desc.substring(getArrayDimension())); | ||
| 173 | } | ||
| 174 | |||
| 175 | public boolean containsType() { | ||
| 176 | return isType() || (isArray() && getArrayType().containsType()); | ||
| 177 | } | ||
| 178 | |||
| 179 | @Override | ||
| 180 | public boolean equals(Object other) { | ||
| 181 | return other instanceof TypeDescriptor && equals((TypeDescriptor) other); | ||
| 182 | } | ||
| 183 | |||
| 184 | public boolean equals(TypeDescriptor other) { | ||
| 185 | return this.desc.equals(other.desc); | ||
| 186 | } | ||
| 187 | |||
| 188 | @Override | ||
| 189 | public int hashCode() { | ||
| 190 | return this.desc.hashCode(); | ||
| 191 | } | ||
| 192 | |||
| 193 | public TypeDescriptor remap(Function<String, String> remapper) { | ||
| 194 | String desc = this.desc; | ||
| 195 | if (isType() || (isArray() && containsType())) { | ||
| 196 | String replacedName = remapper.apply(this.getTypeEntry().getFullName()); | ||
| 197 | if (replacedName != null) { | ||
| 198 | if (this.isType()) { | ||
| 199 | desc = "L" + replacedName + ";"; | ||
| 200 | } else { | ||
| 201 | desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | return new TypeDescriptor(desc); | ||
| 206 | } | ||
| 207 | |||
| 208 | private static String getArrayPrefix(int dimension) { | ||
| 209 | StringBuilder buf = new StringBuilder(); | ||
| 210 | for (int i = 0; i < dimension; i++) { | ||
| 211 | buf.append("["); | ||
| 212 | } | ||
| 213 | return buf.toString(); | ||
| 214 | } | ||
| 215 | |||
| 216 | public int getSize() { | ||
| 217 | switch (desc.charAt(0)) { | ||
| 218 | case 'J': | ||
| 219 | case 'D': | ||
| 220 | if (desc.length() == 1) { | ||
| 221 | return 2; | ||
| 222 | } else { | ||
| 223 | return 1; | ||
| 224 | } | ||
| 225 | default: | ||
| 226 | return 1; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | @Override | ||
| 231 | public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 232 | return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); | ||
| 233 | } | ||
| 234 | |||
| 235 | public enum Primitive { | ||
| 236 | BYTE('B'), | ||
| 237 | CHARACTER('C'), | ||
| 238 | SHORT('S'), | ||
| 239 | INTEGER('I'), | ||
| 240 | LONG('J'), | ||
| 241 | FLOAT('F'), | ||
| 242 | DOUBLE('D'), | ||
| 243 | BOOLEAN('Z'); | ||
| 244 | |||
| 245 | private static final Map<Character, Primitive> lookup; | ||
| 246 | |||
| 247 | static { | ||
| 248 | lookup = Maps.newTreeMap(); | ||
| 249 | for (Primitive val : values()) { | ||
| 250 | lookup.put(val.getCode(), val); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | private char code; | ||
| 255 | |||
| 256 | Primitive(char code) { | ||
| 257 | this.code = code; | ||
| 258 | } | ||
| 259 | |||
| 260 | public static Primitive get(char code) { | ||
| 261 | return lookup.get(code); | ||
| 262 | } | ||
| 263 | |||
| 264 | public char getCode() { | ||
| 265 | return this.code; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java deleted file mode 100644 index 6930765..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java +++ /dev/null | |||
| @@ -1,93 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 18 | import cuchaz.enigma.translation.representation.Signature; | ||
| 19 | |||
| 20 | import javax.annotation.Nullable; | ||
| 21 | import java.util.Arrays; | ||
| 22 | |||
| 23 | public class ClassDefEntry extends ClassEntry implements DefEntry<ClassEntry> { | ||
| 24 | private final AccessFlags access; | ||
| 25 | private final Signature signature; | ||
| 26 | private final ClassEntry superClass; | ||
| 27 | private final ClassEntry[] interfaces; | ||
| 28 | |||
| 29 | public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { | ||
| 30 | this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces, null); | ||
| 31 | } | ||
| 32 | |||
| 33 | public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { | ||
| 34 | this(parent, className, signature, access, superClass, interfaces, null); | ||
| 35 | } | ||
| 36 | |||
| 37 | public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, | ||
| 38 | ClassEntry[] interfaces, String javadocs) { | ||
| 39 | super(parent, className, javadocs); | ||
| 40 | Preconditions.checkNotNull(signature, "Class signature cannot be null"); | ||
| 41 | Preconditions.checkNotNull(access, "Class access cannot be null"); | ||
| 42 | |||
| 43 | this.signature = signature; | ||
| 44 | this.access = access; | ||
| 45 | this.superClass = superClass; | ||
| 46 | this.interfaces = interfaces != null ? interfaces : new ClassEntry[0]; | ||
| 47 | } | ||
| 48 | |||
| 49 | public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) { | ||
| 50 | ClassEntry superClass = superName != null ? new ClassEntry(superName) : null; | ||
| 51 | ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new); | ||
| 52 | return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses); | ||
| 53 | } | ||
| 54 | |||
| 55 | public Signature getSignature() { | ||
| 56 | return signature; | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public AccessFlags getAccess() { | ||
| 61 | return access; | ||
| 62 | } | ||
| 63 | |||
| 64 | @Nullable | ||
| 65 | public ClassEntry getSuperClass() { | ||
| 66 | return superClass; | ||
| 67 | } | ||
| 68 | |||
| 69 | public ClassEntry[] getInterfaces() { | ||
| 70 | return interfaces; | ||
| 71 | } | ||
| 72 | |||
| 73 | @Override | ||
| 74 | public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 75 | Signature translatedSignature = translator.translate(signature); | ||
| 76 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 77 | AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; | ||
| 78 | ClassEntry translatedSuper = translator.translate(superClass); | ||
| 79 | ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); | ||
| 80 | String docs = mapping != null ? mapping.getJavadoc() : null; | ||
| 81 | return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public ClassDefEntry withName(String name) { | ||
| 86 | return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public ClassDefEntry withParent(ClassEntry parent) { | ||
| 91 | return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs); | ||
| 92 | } | ||
| 93 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java deleted file mode 100644 index d6171f1..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ /dev/null | |||
| @@ -1,214 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.mapping.NameValidator; | ||
| 18 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 19 | |||
| 20 | import javax.annotation.Nonnull; | ||
| 21 | import javax.annotation.Nullable; | ||
| 22 | import java.util.List; | ||
| 23 | import java.util.Objects; | ||
| 24 | |||
| 25 | public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> { | ||
| 26 | private final String fullName; | ||
| 27 | |||
| 28 | public ClassEntry(String className) { | ||
| 29 | this(getOuterClass(className), getInnerName(className), null); | ||
| 30 | } | ||
| 31 | |||
| 32 | public ClassEntry(@Nullable ClassEntry parent, String className) { | ||
| 33 | this(parent, className, null); | ||
| 34 | } | ||
| 35 | |||
| 36 | public ClassEntry(@Nullable ClassEntry parent, String className, @Nullable String javadocs) { | ||
| 37 | super(parent, className, javadocs); | ||
| 38 | if (parent != null) { | ||
| 39 | fullName = parent.getFullName() + "$" + name; | ||
| 40 | } else { | ||
| 41 | fullName = name; | ||
| 42 | } | ||
| 43 | |||
| 44 | if (parent == null && className.indexOf('.') >= 0) { | ||
| 45 | throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | public Class<ClassEntry> getParentType() { | ||
| 51 | return ClassEntry.class; | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public String getName() { | ||
| 56 | return this.name; | ||
| 57 | } | ||
| 58 | |||
| 59 | public String getFullName() { | ||
| 60 | return fullName; | ||
| 61 | } | ||
| 62 | |||
| 63 | @Override | ||
| 64 | public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 65 | if (name.charAt(0) == '[') { | ||
| 66 | String translatedName = translator.translate(new TypeDescriptor(name)).toString(); | ||
| 67 | return new ClassEntry(parent, translatedName); | ||
| 68 | } | ||
| 69 | |||
| 70 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 71 | String docs = mapping != null ? mapping.getJavadoc() : null; | ||
| 72 | return new ClassEntry(parent, translatedName, docs); | ||
| 73 | } | ||
| 74 | |||
| 75 | @Override | ||
| 76 | public ClassEntry getContainingClass() { | ||
| 77 | return this; | ||
| 78 | } | ||
| 79 | |||
| 80 | @Override | ||
| 81 | public int hashCode() { | ||
| 82 | return fullName.hashCode(); | ||
| 83 | } | ||
| 84 | |||
| 85 | @Override | ||
| 86 | public boolean equals(Object other) { | ||
| 87 | return other instanceof ClassEntry && equals((ClassEntry) other); | ||
| 88 | } | ||
| 89 | |||
| 90 | public boolean equals(ClassEntry other) { | ||
| 91 | return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name); | ||
| 92 | } | ||
| 93 | |||
| 94 | @Override | ||
| 95 | public boolean canConflictWith(Entry<?> entry) { | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | |||
| 99 | @Override | ||
| 100 | public void validateName(String name) throws IllegalNameException { | ||
| 101 | NameValidator.validateClassName(name); | ||
| 102 | } | ||
| 103 | |||
| 104 | @Override | ||
| 105 | public ClassEntry withName(String name) { | ||
| 106 | return new ClassEntry(parent, name, javadocs); | ||
| 107 | } | ||
| 108 | |||
| 109 | @Override | ||
| 110 | public ClassEntry withParent(ClassEntry parent) { | ||
| 111 | return new ClassEntry(parent, name, javadocs); | ||
| 112 | } | ||
| 113 | |||
| 114 | @Override | ||
| 115 | public String toString() { | ||
| 116 | return getFullName(); | ||
| 117 | } | ||
| 118 | |||
| 119 | public String getPackageName() { | ||
| 120 | return getPackageName(fullName); | ||
| 121 | } | ||
| 122 | |||
| 123 | public String getSimpleName() { | ||
| 124 | int packagePos = name.lastIndexOf('/'); | ||
| 125 | if (packagePos > 0) { | ||
| 126 | return name.substring(packagePos + 1); | ||
| 127 | } | ||
| 128 | return name; | ||
| 129 | } | ||
| 130 | |||
| 131 | public boolean isInnerClass() { | ||
| 132 | return parent != null; | ||
| 133 | } | ||
| 134 | |||
| 135 | @Nullable | ||
| 136 | public ClassEntry getOuterClass() { | ||
| 137 | return parent; | ||
| 138 | } | ||
| 139 | |||
| 140 | @Nonnull | ||
| 141 | public ClassEntry getOutermostClass() { | ||
| 142 | if (parent == null) { | ||
| 143 | return this; | ||
| 144 | } | ||
| 145 | return parent.getOutermostClass(); | ||
| 146 | } | ||
| 147 | |||
| 148 | public ClassEntry buildClassEntry(List<ClassEntry> classChain) { | ||
| 149 | assert (classChain.contains(this)); | ||
| 150 | StringBuilder buf = new StringBuilder(); | ||
| 151 | for (ClassEntry chainEntry : classChain) { | ||
| 152 | if (buf.length() == 0) { | ||
| 153 | buf.append(chainEntry.getFullName()); | ||
| 154 | } else { | ||
| 155 | buf.append("$"); | ||
| 156 | buf.append(chainEntry.getSimpleName()); | ||
| 157 | } | ||
| 158 | |||
| 159 | if (chainEntry == this) { | ||
| 160 | break; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | return new ClassEntry(buf.toString()); | ||
| 164 | } | ||
| 165 | |||
| 166 | public boolean isJre() { | ||
| 167 | String packageName = getPackageName(); | ||
| 168 | return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); | ||
| 169 | } | ||
| 170 | |||
| 171 | public static String getPackageName(String name) { | ||
| 172 | int pos = name.lastIndexOf('/'); | ||
| 173 | if (pos > 0) { | ||
| 174 | return name.substring(0, pos); | ||
| 175 | } | ||
| 176 | return null; | ||
| 177 | } | ||
| 178 | |||
| 179 | @Nullable | ||
| 180 | public static ClassEntry getOuterClass(String name) { | ||
| 181 | int index = name.lastIndexOf('$'); | ||
| 182 | if (index >= 0) { | ||
| 183 | return new ClassEntry(name.substring(0, index)); | ||
| 184 | } | ||
| 185 | return null; | ||
| 186 | } | ||
| 187 | |||
| 188 | public static String getInnerName(String name) { | ||
| 189 | int innerClassPos = name.lastIndexOf('$'); | ||
| 190 | if (innerClassPos > 0) { | ||
| 191 | return name.substring(innerClassPos + 1); | ||
| 192 | } | ||
| 193 | return name; | ||
| 194 | } | ||
| 195 | |||
| 196 | @Override | ||
| 197 | public String getSourceRemapName() { | ||
| 198 | ClassEntry outerClass = getOuterClass(); | ||
| 199 | if (outerClass != null) { | ||
| 200 | return outerClass.getSourceRemapName() + "." + name; | ||
| 201 | } | ||
| 202 | return getSimpleName(); | ||
| 203 | } | ||
| 204 | |||
| 205 | @Override | ||
| 206 | public int compareTo(ClassEntry entry) { | ||
| 207 | String fullName = getFullName(); | ||
| 208 | String otherFullName = entry.getFullName(); | ||
| 209 | if (fullName.length() != otherFullName.length()) { | ||
| 210 | return fullName.length() - otherFullName.length(); | ||
| 211 | } | ||
| 212 | return fullName.compareTo(otherFullName); | ||
| 213 | } | ||
| 214 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java deleted file mode 100644 index 82536c7..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.representation.entry; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 4 | |||
| 5 | public interface DefEntry<P extends Entry<?>> extends Entry<P> { | ||
| 6 | AccessFlags getAccess(); | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java deleted file mode 100644 index 72b0391..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java +++ /dev/null | |||
| @@ -1,107 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import cuchaz.enigma.throwables.IllegalNameException; | ||
| 15 | import cuchaz.enigma.translation.Translatable; | ||
| 16 | import cuchaz.enigma.translation.mapping.NameValidator; | ||
| 17 | |||
| 18 | import javax.annotation.Nullable; | ||
| 19 | import java.util.ArrayList; | ||
| 20 | import java.util.List; | ||
| 21 | |||
| 22 | public interface Entry<P extends Entry<?>> extends Translatable { | ||
| 23 | String getName(); | ||
| 24 | |||
| 25 | String getJavadocs(); | ||
| 26 | |||
| 27 | default String getSourceRemapName() { | ||
| 28 | return getName(); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Nullable | ||
| 32 | P getParent(); | ||
| 33 | |||
| 34 | Class<P> getParentType(); | ||
| 35 | |||
| 36 | Entry<P> withName(String name); | ||
| 37 | |||
| 38 | Entry<P> withParent(P parent); | ||
| 39 | |||
| 40 | boolean canConflictWith(Entry<?> entry); | ||
| 41 | |||
| 42 | @Nullable | ||
| 43 | default ClassEntry getContainingClass() { | ||
| 44 | P parent = getParent(); | ||
| 45 | if (parent == null) { | ||
| 46 | return null; | ||
| 47 | } | ||
| 48 | if (parent instanceof ClassEntry) { | ||
| 49 | return (ClassEntry) parent; | ||
| 50 | } | ||
| 51 | return parent.getContainingClass(); | ||
| 52 | } | ||
| 53 | |||
| 54 | default List<Entry<?>> getAncestry() { | ||
| 55 | P parent = getParent(); | ||
| 56 | List<Entry<?>> entries = new ArrayList<>(); | ||
| 57 | if (parent != null) { | ||
| 58 | entries.addAll(parent.getAncestry()); | ||
| 59 | } | ||
| 60 | entries.add(this); | ||
| 61 | return entries; | ||
| 62 | } | ||
| 63 | |||
| 64 | @Nullable | ||
| 65 | @SuppressWarnings("unchecked") | ||
| 66 | default <E extends Entry<?>> E findAncestor(Class<E> type) { | ||
| 67 | List<Entry<?>> ancestry = getAncestry(); | ||
| 68 | for (int i = ancestry.size() - 1; i >= 0; i--) { | ||
| 69 | Entry<?> ancestor = ancestry.get(i); | ||
| 70 | if (type.isAssignableFrom(ancestor.getClass())) { | ||
| 71 | return (E) ancestor; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | return null; | ||
| 75 | } | ||
| 76 | |||
| 77 | @SuppressWarnings("unchecked") | ||
| 78 | default <E extends Entry<?>> Entry<P> replaceAncestor(E target, E replacement) { | ||
| 79 | if (replacement.equals(target)) { | ||
| 80 | return this; | ||
| 81 | } | ||
| 82 | |||
| 83 | if (equals(target)) { | ||
| 84 | return (Entry<P>) replacement; | ||
| 85 | } | ||
| 86 | |||
| 87 | P parent = getParent(); | ||
| 88 | if (parent == null) { | ||
| 89 | return this; | ||
| 90 | } | ||
| 91 | |||
| 92 | return withParent((P) parent.replaceAncestor(target, replacement)); | ||
| 93 | } | ||
| 94 | |||
| 95 | default void validateName(String name) throws IllegalNameException { | ||
| 96 | NameValidator.validateIdentifier(name); | ||
| 97 | } | ||
| 98 | |||
| 99 | @SuppressWarnings("unchecked") | ||
| 100 | @Nullable | ||
| 101 | default <C extends Entry<?>> Entry<C> castParent(Class<C> parentType) { | ||
| 102 | if (parentType.equals(getParentType())) { | ||
| 103 | return (Entry<C>) this; | ||
| 104 | } | ||
| 105 | return null; | ||
| 106 | } | ||
| 107 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java deleted file mode 100644 index f9282b2..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java +++ /dev/null | |||
| @@ -1,71 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 18 | import cuchaz.enigma.translation.representation.Signature; | ||
| 19 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 20 | |||
| 21 | import javax.annotation.Nullable; | ||
| 22 | |||
| 23 | public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> { | ||
| 24 | private final AccessFlags access; | ||
| 25 | private final Signature signature; | ||
| 26 | |||
| 27 | public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { | ||
| 28 | this(owner, name, desc, signature, access, null); | ||
| 29 | } | ||
| 30 | |||
| 31 | public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access, String javadocs) { | ||
| 32 | super(owner, name, desc, javadocs); | ||
| 33 | Preconditions.checkNotNull(access, "Field access cannot be null"); | ||
| 34 | Preconditions.checkNotNull(signature, "Field signature cannot be null"); | ||
| 35 | this.access = access; | ||
| 36 | this.signature = signature; | ||
| 37 | } | ||
| 38 | |||
| 39 | public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { | ||
| 40 | return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access), null); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public AccessFlags getAccess() { | ||
| 45 | return access; | ||
| 46 | } | ||
| 47 | |||
| 48 | public Signature getSignature() { | ||
| 49 | return signature; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 54 | TypeDescriptor translatedDesc = translator.translate(desc); | ||
| 55 | Signature translatedSignature = translator.translate(signature); | ||
| 56 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 57 | AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; | ||
| 58 | String docs = mapping != null ? mapping.getJavadoc() : null; | ||
| 59 | return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs); | ||
| 60 | } | ||
| 61 | |||
| 62 | @Override | ||
| 63 | public FieldDefEntry withName(String name) { | ||
| 64 | return new FieldDefEntry(parent, name, desc, signature, access, javadocs); | ||
| 65 | } | ||
| 66 | |||
| 67 | @Override | ||
| 68 | public FieldDefEntry withParent(ClassEntry owner) { | ||
| 69 | return new FieldDefEntry(owner, this.name, this.desc, signature, access, javadocs); | ||
| 70 | } | ||
| 71 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java deleted file mode 100644 index bef0edf..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java +++ /dev/null | |||
| @@ -1,96 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 18 | import cuchaz.enigma.utils.Utils; | ||
| 19 | |||
| 20 | import javax.annotation.Nullable; | ||
| 21 | |||
| 22 | public class FieldEntry extends ParentedEntry<ClassEntry> implements Comparable<FieldEntry> { | ||
| 23 | protected final TypeDescriptor desc; | ||
| 24 | |||
| 25 | public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) { | ||
| 26 | this(parent, name, desc, null); | ||
| 27 | } | ||
| 28 | |||
| 29 | public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc, String javadocs) { | ||
| 30 | super(parent, name, javadocs); | ||
| 31 | |||
| 32 | Preconditions.checkNotNull(parent, "Owner cannot be null"); | ||
| 33 | Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); | ||
| 34 | |||
| 35 | this.desc = desc; | ||
| 36 | } | ||
| 37 | |||
| 38 | public static FieldEntry parse(String owner, String name, String desc) { | ||
| 39 | return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc), null); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public Class<ClassEntry> getParentType() { | ||
| 44 | return ClassEntry.class; | ||
| 45 | } | ||
| 46 | |||
| 47 | public TypeDescriptor getDesc() { | ||
| 48 | return this.desc; | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public FieldEntry withName(String name) { | ||
| 53 | return new FieldEntry(parent, name, desc, null); | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public FieldEntry withParent(ClassEntry parent) { | ||
| 58 | return new FieldEntry(parent, this.name, this.desc, null); | ||
| 59 | } | ||
| 60 | |||
| 61 | @Override | ||
| 62 | protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 63 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 64 | String docs = mapping != null ? mapping.getJavadoc() : null; | ||
| 65 | return new FieldEntry(parent, translatedName, translator.translate(desc), docs); | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public int hashCode() { | ||
| 70 | return Utils.combineHashesOrdered(this.parent, this.name, this.desc); | ||
| 71 | } | ||
| 72 | |||
| 73 | @Override | ||
| 74 | public boolean equals(Object other) { | ||
| 75 | return other instanceof FieldEntry && equals((FieldEntry) other); | ||
| 76 | } | ||
| 77 | |||
| 78 | public boolean equals(FieldEntry other) { | ||
| 79 | return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc); | ||
| 80 | } | ||
| 81 | |||
| 82 | @Override | ||
| 83 | public boolean canConflictWith(Entry<?> entry) { | ||
| 84 | return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public String toString() { | ||
| 89 | return this.parent.getFullName() + "." + this.name + ":" + this.desc; | ||
| 90 | } | ||
| 91 | |||
| 92 | @Override | ||
| 93 | public int compareTo(FieldEntry entry) { | ||
| 94 | return (name + desc.toString()).compareTo(entry.name + entry.desc.toString()); | ||
| 95 | } | ||
| 96 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java deleted file mode 100644 index aad4236..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java +++ /dev/null | |||
| @@ -1,51 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.representation.entry; | ||
| 2 | |||
| 3 | import com.google.common.base.Preconditions; | ||
| 4 | import cuchaz.enigma.translation.Translator; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 7 | |||
| 8 | import javax.annotation.Nullable; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * TypeDescriptor... | ||
| 12 | * Created by Thog | ||
| 13 | * 19/10/2016 | ||
| 14 | */ | ||
| 15 | public class LocalVariableDefEntry extends LocalVariableEntry { | ||
| 16 | protected final TypeDescriptor desc; | ||
| 17 | |||
| 18 | public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc, String javadoc) { | ||
| 19 | super(ownerEntry, index, name, parameter, javadoc); | ||
| 20 | Preconditions.checkNotNull(desc, "Variable desc cannot be null"); | ||
| 21 | |||
| 22 | this.desc = desc; | ||
| 23 | } | ||
| 24 | |||
| 25 | public TypeDescriptor getDesc() { | ||
| 26 | return desc; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 31 | TypeDescriptor translatedDesc = translator.translate(desc); | ||
| 32 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 33 | String javadoc = mapping != null ? mapping.getJavadoc() : javadocs; | ||
| 34 | return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc); | ||
| 35 | } | ||
| 36 | |||
| 37 | @Override | ||
| 38 | public LocalVariableDefEntry withName(String name) { | ||
| 39 | return new LocalVariableDefEntry(parent, index, name, parameter, desc, javadocs); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public LocalVariableDefEntry withParent(MethodEntry entry) { | ||
| 44 | return new LocalVariableDefEntry(entry, index, name, parameter, desc, javadocs); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public String toString() { | ||
| 49 | return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java deleted file mode 100644 index 3ccb1fa..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java +++ /dev/null | |||
| @@ -1,93 +0,0 @@ | |||
| 1 | package cuchaz.enigma.translation.representation.entry; | ||
| 2 | |||
| 3 | import com.google.common.base.Preconditions; | ||
| 4 | import cuchaz.enigma.translation.Translator; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.utils.Utils; | ||
| 7 | |||
| 8 | import javax.annotation.Nullable; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * TypeDescriptor... | ||
| 12 | * Created by Thog | ||
| 13 | * 19/10/2016 | ||
| 14 | */ | ||
| 15 | public class LocalVariableEntry extends ParentedEntry<MethodEntry> implements Comparable<LocalVariableEntry> { | ||
| 16 | |||
| 17 | protected final int index; | ||
| 18 | protected final boolean parameter; | ||
| 19 | |||
| 20 | public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter, String javadoc) { | ||
| 21 | super(parent, name, javadoc); | ||
| 22 | |||
| 23 | Preconditions.checkNotNull(parent, "Variable owner cannot be null"); | ||
| 24 | Preconditions.checkArgument(index >= 0, "Index must be positive"); | ||
| 25 | |||
| 26 | this.index = index; | ||
| 27 | this.parameter = parameter; | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | public Class<MethodEntry> getParentType() { | ||
| 32 | return MethodEntry.class; | ||
| 33 | } | ||
| 34 | |||
| 35 | public boolean isArgument() { | ||
| 36 | return this.parameter; | ||
| 37 | } | ||
| 38 | |||
| 39 | public int getIndex() { | ||
| 40 | return index; | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public String getName() { | ||
| 45 | return this.name; | ||
| 46 | } | ||
| 47 | |||
| 48 | @Override | ||
| 49 | public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 50 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 51 | String javadoc = mapping != null ? mapping.getJavadoc() : null; | ||
| 52 | return new LocalVariableEntry(parent, index, translatedName, parameter, javadoc); | ||
| 53 | } | ||
| 54 | |||
| 55 | @Override | ||
| 56 | public LocalVariableEntry withName(String name) { | ||
| 57 | return new LocalVariableEntry(parent, index, name, parameter, javadocs); | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public LocalVariableEntry withParent(MethodEntry parent) { | ||
| 62 | return new LocalVariableEntry(parent, index, name, parameter, javadocs); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Override | ||
| 66 | public int hashCode() { | ||
| 67 | return Utils.combineHashesOrdered(this.parent, this.index); | ||
| 68 | } | ||
| 69 | |||
| 70 | @Override | ||
| 71 | public boolean equals(Object other) { | ||
| 72 | return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); | ||
| 73 | } | ||
| 74 | |||
| 75 | public boolean equals(LocalVariableEntry other) { | ||
| 76 | return this.parent.equals(other.parent) && this.index == other.index; | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public boolean canConflictWith(Entry<?> entry) { | ||
| 81 | return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public String toString() { | ||
| 86 | return this.parent + "(" + this.index + ":" + this.name + ")"; | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public int compareTo(LocalVariableEntry entry) { | ||
| 91 | return Integer.compare(index, entry.index); | ||
| 92 | } | ||
| 93 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java deleted file mode 100644 index 4e75a5c..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java +++ /dev/null | |||
| @@ -1,71 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 18 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 19 | import cuchaz.enigma.translation.representation.Signature; | ||
| 20 | |||
| 21 | import javax.annotation.Nullable; | ||
| 22 | |||
| 23 | public class MethodDefEntry extends MethodEntry implements DefEntry<ClassEntry> { | ||
| 24 | private final AccessFlags access; | ||
| 25 | private final Signature signature; | ||
| 26 | |||
| 27 | public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { | ||
| 28 | this(owner, name, descriptor, signature, access, null); | ||
| 29 | } | ||
| 30 | |||
| 31 | public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access, String docs) { | ||
| 32 | super(owner, name, descriptor, docs); | ||
| 33 | Preconditions.checkNotNull(access, "Method access cannot be null"); | ||
| 34 | Preconditions.checkNotNull(signature, "Method signature cannot be null"); | ||
| 35 | this.access = access; | ||
| 36 | this.signature = signature; | ||
| 37 | } | ||
| 38 | |||
| 39 | public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { | ||
| 40 | return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access), null); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public AccessFlags getAccess() { | ||
| 45 | return access; | ||
| 46 | } | ||
| 47 | |||
| 48 | public Signature getSignature() { | ||
| 49 | return signature; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 54 | MethodDescriptor translatedDesc = translator.translate(descriptor); | ||
| 55 | Signature translatedSignature = translator.translate(signature); | ||
| 56 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 57 | AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; | ||
| 58 | String docs = mapping != null ? mapping.getJavadoc() : null; | ||
| 59 | return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs); | ||
| 60 | } | ||
| 61 | |||
| 62 | @Override | ||
| 63 | public MethodDefEntry withName(String name) { | ||
| 64 | return new MethodDefEntry(parent, name, descriptor, signature, access, javadocs); | ||
| 65 | } | ||
| 66 | |||
| 67 | @Override | ||
| 68 | public MethodDefEntry withParent(ClassEntry parent) { | ||
| 69 | return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access, javadocs); | ||
| 70 | } | ||
| 71 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java deleted file mode 100644 index e1ffad3..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java +++ /dev/null | |||
| @@ -1,105 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import cuchaz.enigma.translation.Translator; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 18 | import cuchaz.enigma.utils.Utils; | ||
| 19 | |||
| 20 | import javax.annotation.Nullable; | ||
| 21 | |||
| 22 | public class MethodEntry extends ParentedEntry<ClassEntry> implements Comparable<MethodEntry> { | ||
| 23 | |||
| 24 | protected final MethodDescriptor descriptor; | ||
| 25 | |||
| 26 | public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) { | ||
| 27 | this(parent, name, descriptor, null); | ||
| 28 | } | ||
| 29 | |||
| 30 | public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor, String javadocs) { | ||
| 31 | super(parent, name, javadocs); | ||
| 32 | |||
| 33 | Preconditions.checkNotNull(parent, "Parent cannot be null"); | ||
| 34 | Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); | ||
| 35 | |||
| 36 | this.descriptor = descriptor; | ||
| 37 | } | ||
| 38 | |||
| 39 | public static MethodEntry parse(String owner, String name, String desc) { | ||
| 40 | return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc), null); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public Class<ClassEntry> getParentType() { | ||
| 45 | return ClassEntry.class; | ||
| 46 | } | ||
| 47 | |||
| 48 | public MethodDescriptor getDesc() { | ||
| 49 | return this.descriptor; | ||
| 50 | } | ||
| 51 | |||
| 52 | public boolean isConstructor() { | ||
| 53 | return name.equals("<init>") || name.equals("<clinit>"); | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) { | ||
| 58 | String translatedName = mapping != null ? mapping.getTargetName() : name; | ||
| 59 | String docs = mapping != null ? mapping.getJavadoc() : null; | ||
| 60 | return new MethodEntry(parent, translatedName, translator.translate(descriptor), docs); | ||
| 61 | } | ||
| 62 | |||
| 63 | @Override | ||
| 64 | public MethodEntry withName(String name) { | ||
| 65 | return new MethodEntry(parent, name, descriptor, javadocs); | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public MethodEntry withParent(ClassEntry parent) { | ||
| 70 | return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor, javadocs); | ||
| 71 | } | ||
| 72 | |||
| 73 | @Override | ||
| 74 | public int hashCode() { | ||
| 75 | return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor); | ||
| 76 | } | ||
| 77 | |||
| 78 | @Override | ||
| 79 | public boolean equals(Object other) { | ||
| 80 | return other instanceof MethodEntry && equals((MethodEntry) other); | ||
| 81 | } | ||
| 82 | |||
| 83 | public boolean equals(MethodEntry other) { | ||
| 84 | return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public boolean canConflictWith(Entry<?> entry) { | ||
| 89 | if (entry instanceof MethodEntry) { | ||
| 90 | MethodEntry methodEntry = (MethodEntry) entry; | ||
| 91 | return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor); | ||
| 92 | } | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | |||
| 96 | @Override | ||
| 97 | public String toString() { | ||
| 98 | return this.parent.getFullName() + "." + this.name + this.descriptor; | ||
| 99 | } | ||
| 100 | |||
| 101 | @Override | ||
| 102 | public int compareTo(MethodEntry entry) { | ||
| 103 | return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString()); | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java deleted file mode 100644 index cbc5faf..0000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java +++ /dev/null | |||
| @@ -1,82 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.representation.entry; | ||
| 13 | |||
| 14 | import com.google.common.base.Preconditions; | ||
| 15 | import cuchaz.enigma.translation.Translatable; | ||
| 16 | import cuchaz.enigma.translation.Translator; | ||
| 17 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 18 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 19 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 20 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 21 | |||
| 22 | import javax.annotation.Nullable; | ||
| 23 | |||
| 24 | public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> { | ||
| 25 | protected final P parent; | ||
| 26 | protected final String name; | ||
| 27 | protected final @Nullable String javadocs; | ||
| 28 | |||
| 29 | protected ParentedEntry(P parent, String name, String javadocs) { | ||
| 30 | this.parent = parent; | ||
| 31 | this.name = name; | ||
| 32 | this.javadocs = javadocs; | ||
| 33 | |||
| 34 | Preconditions.checkNotNull(name, "Name cannot be null"); | ||
| 35 | } | ||
| 36 | |||
| 37 | @Override | ||
| 38 | public abstract ParentedEntry<P> withParent(P parent); | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public abstract ParentedEntry<P> withName(String name); | ||
| 42 | |||
| 43 | protected abstract ParentedEntry<P> translate(Translator translator, @Nullable EntryMapping mapping); | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public String getName() { | ||
| 47 | return name; | ||
| 48 | } | ||
| 49 | |||
| 50 | @Override | ||
| 51 | @Nullable | ||
| 52 | public P getParent() { | ||
| 53 | return parent; | ||
| 54 | } | ||
| 55 | |||
| 56 | @Nullable | ||
| 57 | @Override | ||
| 58 | public String getJavadocs() { | ||
| 59 | return javadocs; | ||
| 60 | } | ||
| 61 | |||
| 62 | @Override | ||
| 63 | public ParentedEntry<P> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 64 | P parent = getParent(); | ||
| 65 | EntryMapping mapping = resolveMapping(resolver, mappings); | ||
| 66 | if (parent == null) { | ||
| 67 | return translate(translator, mapping); | ||
| 68 | } | ||
| 69 | P translatedParent = translator.translate(parent); | ||
| 70 | return withParent(translatedParent).translate(translator, mapping); | ||
| 71 | } | ||
| 72 | |||
| 73 | private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 74 | for (ParentedEntry<P> entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) { | ||
| 75 | EntryMapping mapping = mappings.get(entry); | ||
| 76 | if (mapping != null) { | ||
| 77 | return mapping; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | return null; | ||
| 81 | } | ||
| 82 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/I18n.java b/src/main/java/cuchaz/enigma/utils/I18n.java deleted file mode 100644 index f91c916..0000000 --- a/src/main/java/cuchaz/enigma/utils/I18n.java +++ /dev/null | |||
| @@ -1,102 +0,0 @@ | |||
| 1 | package cuchaz.enigma.utils; | ||
| 2 | |||
| 3 | import java.io.BufferedReader; | ||
| 4 | import java.io.IOException; | ||
| 5 | import java.io.InputStream; | ||
| 6 | import java.io.InputStreamReader; | ||
| 7 | import java.nio.charset.StandardCharsets; | ||
| 8 | import java.util.ArrayList; | ||
| 9 | import java.util.Collections; | ||
| 10 | import java.util.Map; | ||
| 11 | import java.util.stream.Stream; | ||
| 12 | |||
| 13 | import com.google.common.collect.ImmutableList; | ||
| 14 | import com.google.common.collect.Maps; | ||
| 15 | import com.google.common.reflect.ClassPath; | ||
| 16 | import com.google.common.reflect.ClassPath.ResourceInfo; | ||
| 17 | import com.google.gson.Gson; | ||
| 18 | |||
| 19 | import cuchaz.enigma.config.Config; | ||
| 20 | |||
| 21 | public class I18n { | ||
| 22 | public static final String DEFAULT_LANGUAGE = "en_us"; | ||
| 23 | private static final Gson GSON = new Gson(); | ||
| 24 | private static Map<String, String> translations = Maps.newHashMap(); | ||
| 25 | private static Map<String, String> defaultTranslations = Maps.newHashMap(); | ||
| 26 | private static Map<String, String> languageNames = Maps.newHashMap(); | ||
| 27 | |||
| 28 | static { | ||
| 29 | translations = load(Config.getInstance().language); | ||
| 30 | defaultTranslations = load(DEFAULT_LANGUAGE); | ||
| 31 | } | ||
| 32 | |||
| 33 | @SuppressWarnings("unchecked") | ||
| 34 | public static Map<String, String> load(String language) { | ||
| 35 | try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) { | ||
| 36 | if (inputStream != null) { | ||
| 37 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { | ||
| 38 | return GSON.fromJson(reader, Map.class); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | } catch (IOException e) { | ||
| 42 | e.printStackTrace(); | ||
| 43 | } | ||
| 44 | return Collections.emptyMap(); | ||
| 45 | } | ||
| 46 | |||
| 47 | public static String translate(String key) { | ||
| 48 | String value = translations.get(key); | ||
| 49 | if (value != null) { | ||
| 50 | return value; | ||
| 51 | } | ||
| 52 | value = defaultTranslations.get(key); | ||
| 53 | if (value != null) { | ||
| 54 | return value; | ||
| 55 | } | ||
| 56 | return key; | ||
| 57 | } | ||
| 58 | |||
| 59 | public static String getLanguageName(String language) { | ||
| 60 | return languageNames.get(language); | ||
| 61 | } | ||
| 62 | |||
| 63 | public static void setLanguage(String language) { | ||
| 64 | Config.getInstance().language = language; | ||
| 65 | try { | ||
| 66 | Config.getInstance().saveConfig(); | ||
| 67 | } catch (IOException e) { | ||
| 68 | e.printStackTrace(); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | public static ArrayList<String> getAvailableLanguages() { | ||
| 73 | ArrayList<String> list = new ArrayList<String>(); | ||
| 74 | |||
| 75 | try { | ||
| 76 | ImmutableList<ResourceInfo> resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList(); | ||
| 77 | Stream<ResourceInfo> dirStream = resources.stream(); | ||
| 78 | dirStream.forEach(context -> { | ||
| 79 | String file = context.getResourceName(); | ||
| 80 | if (file.startsWith("lang/") && file.endsWith(".json")) { | ||
| 81 | String fileName = file.substring(5, file.length() - 5); | ||
| 82 | list.add(fileName); | ||
| 83 | loadLanguageName(fileName); | ||
| 84 | } | ||
| 85 | }); | ||
| 86 | } catch (IOException e) { | ||
| 87 | e.printStackTrace(); | ||
| 88 | } | ||
| 89 | return list; | ||
| 90 | } | ||
| 91 | |||
| 92 | private static void loadLanguageName(String fileName) { | ||
| 93 | try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) { | ||
| 94 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { | ||
| 95 | Map<?, ?> map = GSON.fromJson(reader, Map.class); | ||
| 96 | languageNames.put(fileName, map.get("language").toString()); | ||
| 97 | } | ||
| 98 | } catch (IOException e) { | ||
| 99 | e.printStackTrace(); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java b/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java deleted file mode 100644 index c12e913..0000000 --- a/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java +++ /dev/null | |||
| @@ -1,16 +0,0 @@ | |||
| 1 | package cuchaz.enigma.utils; | ||
| 2 | |||
| 3 | import java.io.PrintWriter; | ||
| 4 | import java.io.Writer; | ||
| 5 | |||
| 6 | public class LFPrintWriter extends PrintWriter { | ||
| 7 | public LFPrintWriter(Writer out) { | ||
| 8 | super(out); | ||
| 9 | } | ||
| 10 | |||
| 11 | @Override | ||
| 12 | public void println() { | ||
| 13 | // https://stackoverflow.com/a/14749004 | ||
| 14 | write('\n'); | ||
| 15 | } | ||
| 16 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/Message.java b/src/main/java/cuchaz/enigma/utils/Message.java deleted file mode 100644 index d7c5f23..0000000 --- a/src/main/java/cuchaz/enigma/utils/Message.java +++ /dev/null | |||
| @@ -1,392 +0,0 @@ | |||
| 1 | package cuchaz.enigma.utils; | ||
| 2 | |||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | import java.util.Objects; | ||
| 7 | |||
| 8 | import cuchaz.enigma.network.packet.PacketHelper; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | |||
| 11 | public abstract class Message { | ||
| 12 | |||
| 13 | public final String user; | ||
| 14 | |||
| 15 | public static Chat chat(String user, String message) { | ||
| 16 | return new Chat(user, message); | ||
| 17 | } | ||
| 18 | |||
| 19 | public static Connect connect(String user) { | ||
| 20 | return new Connect(user); | ||
| 21 | } | ||
| 22 | |||
| 23 | public static Disconnect disconnect(String user) { | ||
| 24 | return new Disconnect(user); | ||
| 25 | } | ||
| 26 | |||
| 27 | public static EditDocs editDocs(String user, Entry<?> entry) { | ||
| 28 | return new EditDocs(user, entry); | ||
| 29 | } | ||
| 30 | |||
| 31 | public static MarkDeobf markDeobf(String user, Entry<?> entry) { | ||
| 32 | return new MarkDeobf(user, entry); | ||
| 33 | } | ||
| 34 | |||
| 35 | public static RemoveMapping removeMapping(String user, Entry<?> entry) { | ||
| 36 | return new RemoveMapping(user, entry); | ||
| 37 | } | ||
| 38 | |||
| 39 | public static Rename rename(String user, Entry<?> entry, String newName) { | ||
| 40 | return new Rename(user, entry, newName); | ||
| 41 | } | ||
| 42 | |||
| 43 | public abstract String translate(); | ||
| 44 | |||
| 45 | public abstract Type getType(); | ||
| 46 | |||
| 47 | public static Message read(DataInput input) throws IOException { | ||
| 48 | byte typeId = input.readByte(); | ||
| 49 | if (typeId < 0 || typeId >= Type.values().length) { | ||
| 50 | throw new IOException(String.format("Invalid message type ID %d", typeId)); | ||
| 51 | } | ||
| 52 | Type type = Type.values()[typeId]; | ||
| 53 | String user = input.readUTF(); | ||
| 54 | switch (type) { | ||
| 55 | case CHAT: | ||
| 56 | String message = input.readUTF(); | ||
| 57 | return chat(user, message); | ||
| 58 | case CONNECT: | ||
| 59 | return connect(user); | ||
| 60 | case DISCONNECT: | ||
| 61 | return disconnect(user); | ||
| 62 | case EDIT_DOCS: | ||
| 63 | Entry<?> entry = PacketHelper.readEntry(input); | ||
| 64 | return editDocs(user, entry); | ||
| 65 | case MARK_DEOBF: | ||
| 66 | entry = PacketHelper.readEntry(input); | ||
| 67 | return markDeobf(user, entry); | ||
| 68 | case REMOVE_MAPPING: | ||
| 69 | entry = PacketHelper.readEntry(input); | ||
| 70 | return removeMapping(user, entry); | ||
| 71 | case RENAME: | ||
| 72 | entry = PacketHelper.readEntry(input); | ||
| 73 | String newName = input.readUTF(); | ||
| 74 | return rename(user, entry, newName); | ||
| 75 | default: | ||
| 76 | throw new IllegalStateException("unreachable"); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | public void write(DataOutput output) throws IOException { | ||
| 81 | output.writeByte(getType().ordinal()); | ||
| 82 | PacketHelper.writeString(output, user); | ||
| 83 | } | ||
| 84 | |||
| 85 | private Message(String user) { | ||
| 86 | this.user = user; | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public boolean equals(Object o) { | ||
| 91 | if (this == o) return true; | ||
| 92 | if (o == null || getClass() != o.getClass()) return false; | ||
| 93 | Message message = (Message) o; | ||
| 94 | return Objects.equals(user, message.user); | ||
| 95 | } | ||
| 96 | |||
| 97 | @Override | ||
| 98 | public int hashCode() { | ||
| 99 | return Objects.hash(user); | ||
| 100 | } | ||
| 101 | |||
| 102 | public enum Type { | ||
| 103 | CHAT, | ||
| 104 | CONNECT, | ||
| 105 | DISCONNECT, | ||
| 106 | EDIT_DOCS, | ||
| 107 | MARK_DEOBF, | ||
| 108 | REMOVE_MAPPING, | ||
| 109 | RENAME, | ||
| 110 | } | ||
| 111 | |||
| 112 | public static final class Chat extends Message { | ||
| 113 | |||
| 114 | public final String message; | ||
| 115 | |||
| 116 | private Chat(String user, String message) { | ||
| 117 | super(user); | ||
| 118 | this.message = message; | ||
| 119 | } | ||
| 120 | |||
| 121 | @Override | ||
| 122 | public void write(DataOutput output) throws IOException { | ||
| 123 | super.write(output); | ||
| 124 | PacketHelper.writeString(output, message); | ||
| 125 | } | ||
| 126 | |||
| 127 | @Override | ||
| 128 | public String translate() { | ||
| 129 | return String.format(I18n.translate("message.chat.text"), user, message); | ||
| 130 | } | ||
| 131 | |||
| 132 | @Override | ||
| 133 | public Type getType() { | ||
| 134 | return Type.CHAT; | ||
| 135 | } | ||
| 136 | |||
| 137 | @Override | ||
| 138 | public boolean equals(Object o) { | ||
| 139 | if (this == o) return true; | ||
| 140 | if (o == null || getClass() != o.getClass()) return false; | ||
| 141 | if (!super.equals(o)) return false; | ||
| 142 | Chat chat = (Chat) o; | ||
| 143 | return Objects.equals(message, chat.message); | ||
| 144 | } | ||
| 145 | |||
| 146 | @Override | ||
| 147 | public int hashCode() { | ||
| 148 | return Objects.hash(super.hashCode(), message); | ||
| 149 | } | ||
| 150 | |||
| 151 | @Override | ||
| 152 | public String toString() { | ||
| 153 | return String.format("Message.Chat { user: '%s', message: '%s' }", user, message); | ||
| 154 | } | ||
| 155 | |||
| 156 | } | ||
| 157 | |||
| 158 | public static final class Connect extends Message { | ||
| 159 | |||
| 160 | private Connect(String user) { | ||
| 161 | super(user); | ||
| 162 | } | ||
| 163 | |||
| 164 | @Override | ||
| 165 | public String translate() { | ||
| 166 | return String.format(I18n.translate("message.connect.text"), user); | ||
| 167 | } | ||
| 168 | |||
| 169 | @Override | ||
| 170 | public Type getType() { | ||
| 171 | return Type.CONNECT; | ||
| 172 | } | ||
| 173 | |||
| 174 | @Override | ||
| 175 | public String toString() { | ||
| 176 | return String.format("Message.Connect { user: '%s' }", user); | ||
| 177 | } | ||
| 178 | |||
| 179 | } | ||
| 180 | |||
| 181 | public static final class Disconnect extends Message { | ||
| 182 | |||
| 183 | private Disconnect(String user) { | ||
| 184 | super(user); | ||
| 185 | } | ||
| 186 | |||
| 187 | @Override | ||
| 188 | public String translate() { | ||
| 189 | return String.format(I18n.translate("message.disconnect.text"), user); | ||
| 190 | } | ||
| 191 | |||
| 192 | @Override | ||
| 193 | public Type getType() { | ||
| 194 | return Type.DISCONNECT; | ||
| 195 | } | ||
| 196 | |||
| 197 | @Override | ||
| 198 | public String toString() { | ||
| 199 | return String.format("Message.Disconnect { user: '%s' }", user); | ||
| 200 | } | ||
| 201 | |||
| 202 | } | ||
| 203 | |||
| 204 | public static final class EditDocs extends Message { | ||
| 205 | |||
| 206 | public final Entry<?> entry; | ||
| 207 | |||
| 208 | private EditDocs(String user, Entry<?> entry) { | ||
| 209 | super(user); | ||
| 210 | this.entry = entry; | ||
| 211 | } | ||
| 212 | |||
| 213 | @Override | ||
| 214 | public void write(DataOutput output) throws IOException { | ||
| 215 | super.write(output); | ||
| 216 | PacketHelper.writeEntry(output, entry); | ||
| 217 | } | ||
| 218 | |||
| 219 | @Override | ||
| 220 | public String translate() { | ||
| 221 | return String.format(I18n.translate("message.edit_docs.text"), user, entry); | ||
| 222 | } | ||
| 223 | |||
| 224 | @Override | ||
| 225 | public Type getType() { | ||
| 226 | return Type.EDIT_DOCS; | ||
| 227 | } | ||
| 228 | |||
| 229 | @Override | ||
| 230 | public boolean equals(Object o) { | ||
| 231 | if (this == o) return true; | ||
| 232 | if (o == null || getClass() != o.getClass()) return false; | ||
| 233 | if (!super.equals(o)) return false; | ||
| 234 | EditDocs editDocs = (EditDocs) o; | ||
| 235 | return Objects.equals(entry, editDocs.entry); | ||
| 236 | } | ||
| 237 | |||
| 238 | @Override | ||
| 239 | public int hashCode() { | ||
| 240 | return Objects.hash(super.hashCode(), entry); | ||
| 241 | } | ||
| 242 | |||
| 243 | @Override | ||
| 244 | public String toString() { | ||
| 245 | return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry); | ||
| 246 | } | ||
| 247 | |||
| 248 | } | ||
| 249 | |||
| 250 | public static final class MarkDeobf extends Message { | ||
| 251 | |||
| 252 | public final Entry<?> entry; | ||
| 253 | |||
| 254 | private MarkDeobf(String user, Entry<?> entry) { | ||
| 255 | super(user); | ||
| 256 | this.entry = entry; | ||
| 257 | } | ||
| 258 | |||
| 259 | @Override | ||
| 260 | public void write(DataOutput output) throws IOException { | ||
| 261 | super.write(output); | ||
| 262 | PacketHelper.writeEntry(output, entry); | ||
| 263 | } | ||
| 264 | |||
| 265 | @Override | ||
| 266 | public String translate() { | ||
| 267 | return String.format(I18n.translate("message.mark_deobf.text"), user, entry); | ||
| 268 | } | ||
| 269 | |||
| 270 | @Override | ||
| 271 | public Type getType() { | ||
| 272 | return Type.MARK_DEOBF; | ||
| 273 | } | ||
| 274 | |||
| 275 | @Override | ||
| 276 | public boolean equals(Object o) { | ||
| 277 | if (this == o) return true; | ||
| 278 | if (o == null || getClass() != o.getClass()) return false; | ||
| 279 | if (!super.equals(o)) return false; | ||
| 280 | MarkDeobf markDeobf = (MarkDeobf) o; | ||
| 281 | return Objects.equals(entry, markDeobf.entry); | ||
| 282 | } | ||
| 283 | |||
| 284 | @Override | ||
| 285 | public int hashCode() { | ||
| 286 | return Objects.hash(super.hashCode(), entry); | ||
| 287 | } | ||
| 288 | |||
| 289 | @Override | ||
| 290 | public String toString() { | ||
| 291 | return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry); | ||
| 292 | } | ||
| 293 | |||
| 294 | } | ||
| 295 | |||
| 296 | public static final class RemoveMapping extends Message { | ||
| 297 | |||
| 298 | public final Entry<?> entry; | ||
| 299 | |||
| 300 | private RemoveMapping(String user, Entry<?> entry) { | ||
| 301 | super(user); | ||
| 302 | this.entry = entry; | ||
| 303 | } | ||
| 304 | |||
| 305 | @Override | ||
| 306 | public void write(DataOutput output) throws IOException { | ||
| 307 | super.write(output); | ||
| 308 | PacketHelper.writeEntry(output, entry); | ||
| 309 | } | ||
| 310 | |||
| 311 | @Override | ||
| 312 | public String translate() { | ||
| 313 | return String.format(I18n.translate("message.remove_mapping.text"), user, entry); | ||
| 314 | } | ||
| 315 | |||
| 316 | @Override | ||
| 317 | public Type getType() { | ||
| 318 | return Type.REMOVE_MAPPING; | ||
| 319 | } | ||
| 320 | |||
| 321 | @Override | ||
| 322 | public boolean equals(Object o) { | ||
| 323 | if (this == o) return true; | ||
| 324 | if (o == null || getClass() != o.getClass()) return false; | ||
| 325 | if (!super.equals(o)) return false; | ||
| 326 | RemoveMapping that = (RemoveMapping) o; | ||
| 327 | return Objects.equals(entry, that.entry); | ||
| 328 | } | ||
| 329 | |||
| 330 | @Override | ||
| 331 | public int hashCode() { | ||
| 332 | return Objects.hash(super.hashCode(), entry); | ||
| 333 | } | ||
| 334 | |||
| 335 | @Override | ||
| 336 | public String toString() { | ||
| 337 | return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry); | ||
| 338 | } | ||
| 339 | |||
| 340 | } | ||
| 341 | |||
| 342 | public static final class Rename extends Message { | ||
| 343 | |||
| 344 | public final Entry<?> entry; | ||
| 345 | public final String newName; | ||
| 346 | |||
| 347 | private Rename(String user, Entry<?> entry, String newName) { | ||
| 348 | super(user); | ||
| 349 | this.entry = entry; | ||
| 350 | this.newName = newName; | ||
| 351 | } | ||
| 352 | |||
| 353 | @Override | ||
| 354 | public void write(DataOutput output) throws IOException { | ||
| 355 | super.write(output); | ||
| 356 | PacketHelper.writeEntry(output, entry); | ||
| 357 | PacketHelper.writeString(output, newName); | ||
| 358 | } | ||
| 359 | |||
| 360 | @Override | ||
| 361 | public String translate() { | ||
| 362 | return String.format(I18n.translate("message.rename.text"), user, entry, newName); | ||
| 363 | } | ||
| 364 | |||
| 365 | @Override | ||
| 366 | public Type getType() { | ||
| 367 | return Type.RENAME; | ||
| 368 | } | ||
| 369 | |||
| 370 | @Override | ||
| 371 | public boolean equals(Object o) { | ||
| 372 | if (this == o) return true; | ||
| 373 | if (o == null || getClass() != o.getClass()) return false; | ||
| 374 | if (!super.equals(o)) return false; | ||
| 375 | Rename rename = (Rename) o; | ||
| 376 | return Objects.equals(entry, rename.entry) && | ||
| 377 | Objects.equals(newName, rename.newName); | ||
| 378 | } | ||
| 379 | |||
| 380 | @Override | ||
| 381 | public int hashCode() { | ||
| 382 | return Objects.hash(super.hashCode(), entry, newName); | ||
| 383 | } | ||
| 384 | |||
| 385 | @Override | ||
| 386 | public String toString() { | ||
| 387 | return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName); | ||
| 388 | } | ||
| 389 | |||
| 390 | } | ||
| 391 | |||
| 392 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/Pair.java b/src/main/java/cuchaz/enigma/utils/Pair.java deleted file mode 100644 index bf02cef..0000000 --- a/src/main/java/cuchaz/enigma/utils/Pair.java +++ /dev/null | |||
| @@ -1,26 +0,0 @@ | |||
| 1 | package cuchaz.enigma.utils; | ||
| 2 | |||
| 3 | import java.util.Objects; | ||
| 4 | |||
| 5 | public class Pair<A, B> { | ||
| 6 | public final A a; | ||
| 7 | public final B b; | ||
| 8 | |||
| 9 | public Pair(A a, B b) { | ||
| 10 | this.a = a; | ||
| 11 | this.b = b; | ||
| 12 | } | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public int hashCode() { | ||
| 16 | return Objects.hashCode(a) * 31 + | ||
| 17 | Objects.hashCode(b); | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public boolean equals(Object o) { | ||
| 22 | return o instanceof Pair && | ||
| 23 | Objects.equals(a, ((Pair<?, ?>) o).a) && | ||
| 24 | Objects.equals(b, ((Pair<?, ?>) o).b); | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/ReadableToken.java b/src/main/java/cuchaz/enigma/utils/ReadableToken.java deleted file mode 100644 index de152fe..0000000 --- a/src/main/java/cuchaz/enigma/utils/ReadableToken.java +++ /dev/null | |||
| @@ -1,30 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.utils; | ||
| 13 | |||
| 14 | public class ReadableToken { | ||
| 15 | |||
| 16 | public int line; | ||
| 17 | public int startColumn; | ||
| 18 | public int endColumn; | ||
| 19 | |||
| 20 | public ReadableToken(int line, int startColumn, int endColumn) { | ||
| 21 | this.line = line; | ||
| 22 | this.startColumn = startColumn; | ||
| 23 | this.endColumn = endColumn; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public String toString() { | ||
| 28 | return "line " + line + " columns " + startColumn + "-" + endColumn; | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/Utils.java b/src/main/java/cuchaz/enigma/utils/Utils.java deleted file mode 100644 index b45b00d..0000000 --- a/src/main/java/cuchaz/enigma/utils/Utils.java +++ /dev/null | |||
| @@ -1,179 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.utils; | ||
| 13 | |||
| 14 | import com.google.common.io.CharStreams; | ||
| 15 | import org.objectweb.asm.Opcodes; | ||
| 16 | |||
| 17 | import javax.swing.*; | ||
| 18 | import javax.swing.text.BadLocationException; | ||
| 19 | import javax.swing.text.JTextComponent; | ||
| 20 | import java.awt.*; | ||
| 21 | import java.awt.event.MouseEvent; | ||
| 22 | import java.io.IOException; | ||
| 23 | import java.io.InputStream; | ||
| 24 | import java.io.InputStreamReader; | ||
| 25 | import java.net.URI; | ||
| 26 | import java.net.URISyntaxException; | ||
| 27 | import java.nio.charset.StandardCharsets; | ||
| 28 | import java.nio.file.Files; | ||
| 29 | import java.nio.file.Path; | ||
| 30 | import java.security.MessageDigest; | ||
| 31 | import java.security.NoSuchAlgorithmException; | ||
| 32 | import java.util.*; | ||
| 33 | import java.util.List; | ||
| 34 | import java.util.stream.Collectors; | ||
| 35 | import java.util.zip.ZipEntry; | ||
| 36 | import java.util.zip.ZipFile; | ||
| 37 | |||
| 38 | public class Utils { | ||
| 39 | |||
| 40 | public static final int ASM_VERSION = Opcodes.ASM8; | ||
| 41 | |||
| 42 | public static int combineHashesOrdered(Object... objs) { | ||
| 43 | final int prime = 67; | ||
| 44 | int result = 1; | ||
| 45 | for (Object obj : objs) { | ||
| 46 | result *= prime; | ||
| 47 | if (obj != null) { | ||
| 48 | result += obj.hashCode(); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | return result; | ||
| 52 | } | ||
| 53 | |||
| 54 | public static int combineHashesOrdered(List<Object> objs) { | ||
| 55 | final int prime = 67; | ||
| 56 | int result = 1; | ||
| 57 | for (Object obj : objs) { | ||
| 58 | result *= prime; | ||
| 59 | if (obj != null) { | ||
| 60 | result += obj.hashCode(); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | return result; | ||
| 64 | } | ||
| 65 | |||
| 66 | public static String readStreamToString(InputStream in) throws IOException { | ||
| 67 | return CharStreams.toString(new InputStreamReader(in, "UTF-8")); | ||
| 68 | } | ||
| 69 | |||
| 70 | public static String readResourceToString(String path) throws IOException { | ||
| 71 | InputStream in = Utils.class.getResourceAsStream(path); | ||
| 72 | if (in == null) { | ||
| 73 | throw new IllegalArgumentException("Resource not found! " + path); | ||
| 74 | } | ||
| 75 | return readStreamToString(in); | ||
| 76 | } | ||
| 77 | |||
| 78 | public static void openUrl(String url) { | ||
| 79 | if (Desktop.isDesktopSupported()) { | ||
| 80 | Desktop desktop = Desktop.getDesktop(); | ||
| 81 | try { | ||
| 82 | desktop.browse(new URI(url)); | ||
| 83 | } catch (IOException ex) { | ||
| 84 | throw new Error(ex); | ||
| 85 | } catch (URISyntaxException ex) { | ||
| 86 | throw new IllegalArgumentException(ex); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | public static JLabel unboldLabel(JLabel label) { | ||
| 92 | Font font = label.getFont(); | ||
| 93 | label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); | ||
| 94 | return label; | ||
| 95 | } | ||
| 96 | |||
| 97 | public static void showToolTipNow(JComponent component) { | ||
| 98 | // HACKHACK: trick the tooltip manager into showing the tooltip right now | ||
| 99 | ToolTipManager manager = ToolTipManager.sharedInstance(); | ||
| 100 | int oldDelay = manager.getInitialDelay(); | ||
| 101 | manager.setInitialDelay(0); | ||
| 102 | manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); | ||
| 103 | manager.setInitialDelay(oldDelay); | ||
| 104 | } | ||
| 105 | |||
| 106 | public static Rectangle safeModelToView(JTextComponent component, int modelPos) { | ||
| 107 | if (modelPos < 0) { | ||
| 108 | modelPos = 0; | ||
| 109 | } else if (modelPos >= component.getText().length()) { | ||
| 110 | modelPos = component.getText().length(); | ||
| 111 | } | ||
| 112 | try { | ||
| 113 | return component.modelToView(modelPos); | ||
| 114 | } catch (BadLocationException e) { | ||
| 115 | throw new RuntimeException(e); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | public static boolean getSystemPropertyAsBoolean(String property, boolean defValue) { | ||
| 120 | String value = System.getProperty(property); | ||
| 121 | return value == null ? defValue : Boolean.parseBoolean(value); | ||
| 122 | } | ||
| 123 | |||
| 124 | public static void delete(Path path) throws IOException { | ||
| 125 | if (Files.exists(path)) { | ||
| 126 | for (Path p : Files.walk(path).sorted(Comparator.reverseOrder()).collect(Collectors.toList())) { | ||
| 127 | Files.delete(p); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | public static byte[] zipSha1(Path path) throws IOException { | ||
| 133 | MessageDigest digest; | ||
| 134 | try { | ||
| 135 | digest = MessageDigest.getInstance("SHA-1"); | ||
| 136 | } catch (NoSuchAlgorithmException e) { | ||
| 137 | // Algorithm guaranteed to be supported | ||
| 138 | throw new RuntimeException(e); | ||
| 139 | } | ||
| 140 | try (ZipFile zip = new ZipFile(path.toFile())) { | ||
| 141 | List<? extends ZipEntry> entries = Collections.list(zip.entries()); | ||
| 142 | // only compare classes (some implementations may not generate directory entries) | ||
| 143 | entries.removeIf(entry -> !entry.getName().toLowerCase(Locale.ROOT).endsWith(".class")); | ||
| 144 | // different implementations may add zip entries in a different order | ||
| 145 | entries.sort(Comparator.comparing(ZipEntry::getName)); | ||
| 146 | byte[] buffer = new byte[8192]; | ||
| 147 | for (ZipEntry entry : entries) { | ||
| 148 | digest.update(entry.getName().getBytes(StandardCharsets.UTF_8)); | ||
| 149 | try (InputStream in = zip.getInputStream(entry)) { | ||
| 150 | int n; | ||
| 151 | while ((n = in.read(buffer)) != -1) { | ||
| 152 | digest.update(buffer, 0, n); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | return digest.digest(); | ||
| 158 | } | ||
| 159 | |||
| 160 | public static String caplisiseCamelCase(String input){ | ||
| 161 | StringJoiner stringJoiner = new StringJoiner(" "); | ||
| 162 | for (String word : input.toLowerCase(Locale.ROOT).split("_")) { | ||
| 163 | stringJoiner.add(word.substring(0, 1).toUpperCase(Locale.ROOT) + word.substring(1)); | ||
| 164 | } | ||
| 165 | return stringJoiner.toString(); | ||
| 166 | } | ||
| 167 | |||
| 168 | public static boolean isBlank(String input) { | ||
| 169 | if (input == null) { | ||
| 170 | return true; | ||
| 171 | } | ||
| 172 | for (int i = 0; i < input.length(); i++) { | ||
| 173 | if (!Character.isWhitespace(input.charAt(i))) { | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | return true; | ||
| 178 | } | ||
| 179 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java b/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java deleted file mode 100644 index 48b255f..0000000 --- a/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java +++ /dev/null | |||
| @@ -1,17 +0,0 @@ | |||
| 1 | package cuchaz.enigma.utils.search; | ||
| 2 | |||
| 3 | import java.util.List; | ||
| 4 | |||
| 5 | public interface SearchEntry { | ||
| 6 | |||
| 7 | List<String> getSearchableNames(); | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Returns a type that uniquely identifies this search entry across possible changes. | ||
| 11 | * This is used for tracking the amount of times this entry has been selected. | ||
| 12 | * | ||
| 13 | * @return a unique identifier for this search entry | ||
| 14 | */ | ||
| 15 | String getIdentifier(); | ||
| 16 | |||
| 17 | } | ||
diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java b/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java deleted file mode 100644 index a51afbb..0000000 --- a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java +++ /dev/null | |||
| @@ -1,268 +0,0 @@ | |||
| 1 | package cuchaz.enigma.utils.search; | ||
| 2 | |||
| 3 | import java.util.*; | ||
| 4 | import java.util.concurrent.Executor; | ||
| 5 | import java.util.concurrent.Executors; | ||
| 6 | import java.util.concurrent.atomic.AtomicBoolean; | ||
| 7 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 8 | import java.util.concurrent.locks.Lock; | ||
| 9 | import java.util.concurrent.locks.ReentrantLock; | ||
| 10 | import java.util.function.BiFunction; | ||
| 11 | import java.util.stream.Collectors; | ||
| 12 | import java.util.stream.Stream; | ||
| 13 | |||
| 14 | import cuchaz.enigma.utils.Pair; | ||
| 15 | |||
| 16 | public class SearchUtil<T extends SearchEntry> { | ||
| 17 | |||
| 18 | private final Map<T, Entry<T>> entries = new HashMap<>(); | ||
| 19 | private final Map<String, Integer> hitCount = new HashMap<>(); | ||
| 20 | private final Executor searchExecutor = Executors.newWorkStealingPool(); | ||
| 21 | |||
| 22 | public void add(T entry) { | ||
| 23 | Entry<T> e = Entry.from(entry); | ||
| 24 | entries.put(entry, e); | ||
| 25 | } | ||
| 26 | |||
| 27 | public void add(Entry<T> entry) { | ||
| 28 | entries.put(entry.searchEntry, entry); | ||
| 29 | } | ||
| 30 | |||
| 31 | public void addAll(Collection<T> entries) { | ||
| 32 | this.entries.putAll(entries.parallelStream().collect(Collectors.toMap(e -> e, Entry::from))); | ||
| 33 | } | ||
| 34 | |||
| 35 | public void remove(T entry) { | ||
| 36 | entries.remove(entry); | ||
| 37 | } | ||
| 38 | |||
| 39 | public void clear() { | ||
| 40 | entries.clear(); | ||
| 41 | } | ||
| 42 | |||
| 43 | public void clearHits() { | ||
| 44 | hitCount.clear(); | ||
| 45 | } | ||
| 46 | |||
| 47 | public Stream<T> search(String term) { | ||
| 48 | return entries.values().parallelStream() | ||
| 49 | .map(e -> new Pair<>(e, e.getScore(term, hitCount.getOrDefault(e.searchEntry.getIdentifier(), 0)))) | ||
| 50 | .filter(e -> e.b > 0) | ||
| 51 | .sorted(Comparator.comparingDouble(o -> -o.b)) | ||
| 52 | .map(e -> e.a.searchEntry) | ||
| 53 | .sequential(); | ||
| 54 | } | ||
| 55 | |||
| 56 | public SearchControl asyncSearch(String term, SearchResultConsumer<T> consumer) { | ||
| 57 | Map<String, Integer> hitCount = new HashMap<>(this.hitCount); | ||
| 58 | Map<T, Entry<T>> entries = new HashMap<>(this.entries); | ||
| 59 | float[] scores = new float[entries.size()]; | ||
| 60 | Lock scoresLock = new ReentrantLock(); | ||
| 61 | AtomicInteger size = new AtomicInteger(); | ||
| 62 | AtomicBoolean control = new AtomicBoolean(false); | ||
| 63 | AtomicInteger elapsed = new AtomicInteger(); | ||
| 64 | for (Entry<T> value : entries.values()) { | ||
| 65 | searchExecutor.execute(() -> { | ||
| 66 | try { | ||
| 67 | if (control.get()) return; | ||
| 68 | float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0)); | ||
| 69 | if (score <= 0) return; | ||
| 70 | score = -score; // sort descending | ||
| 71 | try { | ||
| 72 | scoresLock.lock(); | ||
| 73 | if (control.get()) return; | ||
| 74 | int dataSize = size.getAndIncrement(); | ||
| 75 | int index = Arrays.binarySearch(scores, 0, dataSize, score); | ||
| 76 | if (index < 0) { | ||
| 77 | index = ~index; | ||
| 78 | } | ||
| 79 | System.arraycopy(scores, index, scores, index + 1, dataSize - index); | ||
| 80 | scores[index] = score; | ||
| 81 | consumer.add(index, value.searchEntry); | ||
| 82 | } finally { | ||
| 83 | scoresLock.unlock(); | ||
| 84 | } | ||
| 85 | } finally { | ||
| 86 | elapsed.incrementAndGet(); | ||
| 87 | } | ||
| 88 | }); | ||
| 89 | } | ||
| 90 | |||
| 91 | return new SearchControl() { | ||
| 92 | @Override | ||
| 93 | public void stop() { | ||
| 94 | control.set(true); | ||
| 95 | } | ||
| 96 | |||
| 97 | @Override | ||
| 98 | public boolean isFinished() { | ||
| 99 | return entries.size() == elapsed.get(); | ||
| 100 | } | ||
| 101 | |||
| 102 | @Override | ||
| 103 | public float getProgress() { | ||
| 104 | return (float) elapsed.get() / entries.size(); | ||
| 105 | } | ||
| 106 | }; | ||
| 107 | } | ||
| 108 | |||
| 109 | public void hit(T entry) { | ||
| 110 | if (entries.containsKey(entry)) { | ||
| 111 | hitCount.compute(entry.getIdentifier(), (_id, i) -> i == null ? 1 : i + 1); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | public static final class Entry<T extends SearchEntry> { | ||
| 116 | |||
| 117 | public final T searchEntry; | ||
| 118 | private final String[][] components; | ||
| 119 | |||
| 120 | private Entry(T searchEntry, String[][] components) { | ||
| 121 | this.searchEntry = searchEntry; | ||
| 122 | this.components = components; | ||
| 123 | } | ||
| 124 | |||
| 125 | public float getScore(String term, int hits) { | ||
| 126 | String ucTerm = term.toUpperCase(Locale.ROOT); | ||
| 127 | float maxScore = (float) Arrays.stream(components) | ||
| 128 | .mapToDouble(name -> getScoreFor(ucTerm, name)) | ||
| 129 | .max().orElse(0.0); | ||
| 130 | return maxScore * (hits + 1); | ||
| 131 | } | ||
| 132 | |||
| 133 | /** | ||
| 134 | * Computes the score for the given <code>name</code> against the given search term. | ||
| 135 | * | ||
| 136 | * @param term the search term (expected to be upper-case) | ||
| 137 | * @param name the entry name, split at word boundaries (see {@link Entry#wordwiseSplit(String)}) | ||
| 138 | * @return the computed score for the entry | ||
| 139 | */ | ||
| 140 | private static float getScoreFor(String term, String[] name) { | ||
| 141 | int totalLength = Arrays.stream(name).mapToInt(String::length).sum(); | ||
| 142 | float scorePerChar = 1f / totalLength; | ||
| 143 | |||
| 144 | // This map contains a snapshot of all the states the search has | ||
| 145 | // been in. The keys are the remaining characters of the search | ||
| 146 | // term, the values are the maximum scores for that remaining | ||
| 147 | // search term part. | ||
| 148 | Map<String, Float> snapshots = new HashMap<>(); | ||
| 149 | snapshots.put(term, 0f); | ||
| 150 | |||
| 151 | // For each component, start at each existing snapshot, searching | ||
| 152 | // for the next longest match, and calculate the new score for each | ||
| 153 | // match length until the maximum. Then the new scores are put back | ||
| 154 | // into the snapshot map. | ||
| 155 | for (int componentIndex = 0; componentIndex < name.length; componentIndex++) { | ||
| 156 | String component = name[componentIndex]; | ||
| 157 | float posMultiplier = (name.length - componentIndex) * 0.3f; | ||
| 158 | Map<String, Float> newSnapshots = new HashMap<>(); | ||
| 159 | for (Map.Entry<String, Float> snapshot : snapshots.entrySet()) { | ||
| 160 | String remaining = snapshot.getKey(); | ||
| 161 | float score = snapshot.getValue(); | ||
| 162 | component = component.toUpperCase(Locale.ROOT); | ||
| 163 | int l = compareEqualLength(remaining, component); | ||
| 164 | for (int i = 1; i <= l; i++) { | ||
| 165 | float baseScore = scorePerChar * i; | ||
| 166 | float chainBonus = (i - 1) * 0.5f; | ||
| 167 | merge(newSnapshots, Collections.singletonMap(remaining.substring(i), score + baseScore * posMultiplier + chainBonus), Math::max); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | merge(snapshots, newSnapshots, Math::max); | ||
| 171 | } | ||
| 172 | |||
| 173 | // Only return the score for when the search term was completely | ||
| 174 | // consumed. | ||
| 175 | return snapshots.getOrDefault("", 0f); | ||
| 176 | } | ||
| 177 | |||
| 178 | private static <K, V> void merge(Map<K, V> self, Map<K, V> source, BiFunction<V, V, V> combiner) { | ||
| 179 | source.forEach((k, v) -> self.compute(k, (_k, v1) -> v1 == null ? v : v == null ? v1 : combiner.apply(v, v1))); | ||
| 180 | } | ||
| 181 | |||
| 182 | public static <T extends SearchEntry> Entry<T> from(T e) { | ||
| 183 | String[][] components = e.getSearchableNames().parallelStream() | ||
| 184 | .map(Entry::wordwiseSplit) | ||
| 185 | .toArray(String[][]::new); | ||
| 186 | return new Entry<>(e, components); | ||
| 187 | } | ||
| 188 | |||
| 189 | private static int compareEqualLength(String s1, String s2) { | ||
| 190 | int len = 0; | ||
| 191 | while (len < s1.length() && len < s2.length() && s1.charAt(len) == s2.charAt(len)) { | ||
| 192 | len += 1; | ||
| 193 | } | ||
| 194 | return len; | ||
| 195 | } | ||
| 196 | |||
| 197 | /** | ||
| 198 | * Splits the given input into components, trying to detect word parts. | ||
| 199 | * <p> | ||
| 200 | * Example of how words get split (using <code>|</code> as seperator): | ||
| 201 | * <p><code>MinecraftClientGame -> Minecraft|Client|Game</code></p> | ||
| 202 | * <p><code>HTTPInputStream -> HTTP|Input|Stream</code></p> | ||
| 203 | * <p><code>class_932 -> class|_|932</code></p> | ||
| 204 | * <p><code>X11FontManager -> X|11|Font|Manager</code></p> | ||
| 205 | * <p><code>openHTTPConnection -> open|HTTP|Connection</code></p> | ||
| 206 | * <p><code>open_http_connection -> open|_|http|_|connection</code></p> | ||
| 207 | * | ||
| 208 | * @param input the input to split | ||
| 209 | * @return the resulting components | ||
| 210 | */ | ||
| 211 | private static String[] wordwiseSplit(String input) { | ||
| 212 | List<String> list = new ArrayList<>(); | ||
| 213 | while (!input.isEmpty()) { | ||
| 214 | int take; | ||
| 215 | if (Character.isLetter(input.charAt(0))) { | ||
| 216 | if (input.length() == 1) { | ||
| 217 | take = 1; | ||
| 218 | } else { | ||
| 219 | boolean nextSegmentIsUppercase = Character.isUpperCase(input.charAt(0)) && Character.isUpperCase(input.charAt(1)); | ||
| 220 | if (nextSegmentIsUppercase) { | ||
| 221 | int nextLowercase = 1; | ||
| 222 | while (Character.isUpperCase(input.charAt(nextLowercase))) { | ||
| 223 | nextLowercase += 1; | ||
| 224 | if (nextLowercase == input.length()) { | ||
| 225 | nextLowercase += 1; | ||
| 226 | break; | ||
| 227 | } | ||
| 228 | } | ||
| 229 | take = nextLowercase - 1; | ||
| 230 | } else { | ||
| 231 | int nextUppercase = 1; | ||
| 232 | while (nextUppercase < input.length() && Character.isLowerCase(input.charAt(nextUppercase))) { | ||
| 233 | nextUppercase += 1; | ||
| 234 | } | ||
| 235 | take = nextUppercase; | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } else if (Character.isDigit(input.charAt(0))) { | ||
| 239 | int nextNonNum = 1; | ||
| 240 | while (nextNonNum < input.length() && Character.isLetter(input.charAt(nextNonNum)) && !Character.isLowerCase(input.charAt(nextNonNum))) { | ||
| 241 | nextNonNum += 1; | ||
| 242 | } | ||
| 243 | take = nextNonNum; | ||
| 244 | } else { | ||
| 245 | take = 1; | ||
| 246 | } | ||
| 247 | list.add(input.substring(0, take)); | ||
| 248 | input = input.substring(take); | ||
| 249 | } | ||
| 250 | return list.toArray(new String[0]); | ||
| 251 | } | ||
| 252 | |||
| 253 | } | ||
| 254 | |||
| 255 | @FunctionalInterface | ||
| 256 | public interface SearchResultConsumer<T extends SearchEntry> { | ||
| 257 | void add(int index, T entry); | ||
| 258 | } | ||
| 259 | |||
| 260 | public interface SearchControl { | ||
| 261 | void stop(); | ||
| 262 | |||
| 263 | boolean isFinished(); | ||
| 264 | |||
| 265 | float getProgress(); | ||
| 266 | } | ||
| 267 | |||
| 268 | } | ||