diff --git a/src/main/java/matcher/Main.java b/src/main/java/matcher/Main.java index e5cbf134..50614ddc 100644 --- a/src/main/java/matcher/Main.java +++ b/src/main/java/matcher/Main.java @@ -7,8 +7,8 @@ public class Main { public static void main(String[] args) { - Config.init(); - PluginLoader.run(); + Config.init(args); + PluginLoader.run(args); Application.launch(Gui.class, args); } } diff --git a/src/main/java/matcher/Matcher.java b/src/main/java/matcher/Matcher.java index 5c99baff..09136564 100644 --- a/src/main/java/matcher/Matcher.java +++ b/src/main/java/matcher/Matcher.java @@ -103,8 +103,15 @@ public void initFromMatches(List inputDirs, List classPathA = resolvePaths(inputDirs, cpFilesA); List classPathB = resolvePaths(inputDirs, cpFilesB); - ProjectConfig config = new ProjectConfig(pathsA, pathsB, classPathA, classPathB, sharedClassPath, false, - nonObfuscatedClassPatternA, nonObfuscatedClassPatternB, nonObfuscatedMemberPatternA, nonObfuscatedMemberPatternB); + ProjectConfig config = new ProjectConfig.Builder(pathsA, pathsB) + .classPathA(new ArrayList<>(classPathA)) + .classPathB(new ArrayList<>(classPathB)) + .sharedClassPath(new ArrayList<>(sharedClassPath)) + .nonObfuscatedClassPatternA(nonObfuscatedClassPatternA) + .nonObfuscatedClassPatternB(nonObfuscatedClassPatternB) + .nonObfuscatedMemberPatternA(nonObfuscatedMemberPatternA) + .nonObfuscatedMemberPatternB(nonObfuscatedMemberPatternB) + .build(); if (!config.isValid()) throw new IOException("invalid config"); Config.setProjectConfig(config); Config.saveAsLast(); diff --git a/src/main/java/matcher/PluginLoader.java b/src/main/java/matcher/PluginLoader.java index 1433d38d..8ca378a4 100644 --- a/src/main/java/matcher/PluginLoader.java +++ b/src/main/java/matcher/PluginLoader.java @@ -8,34 +8,57 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.ServiceLoader; import java.util.stream.Collectors; import java.util.stream.Stream; class PluginLoader { - public static void run() { - Path pluginFolder = Paths.get("plugins"); - URL[] urls = new URL[0]; - - if (Files.isDirectory(pluginFolder)) { - try (Stream stream = Files.list(pluginFolder)) { - urls = stream - .filter(p -> p.getFileName().toString().toLowerCase(Locale.ENGLISH).endsWith(".jar")) - .map(p -> { - try { - return p.toUri().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - }) - .collect(Collectors.toList()).toArray(urls); + public static void run(String[] args) { + List pluginPaths = new ArrayList<>(); + pluginPaths.add(Paths.get("plugins")); + + for (int i = 0; i < args.length; i++) { + switch (args[i]) { + case "--additional-plugins": + while (i+1 < args.length && !args[i+1].startsWith("--")) { + pluginPaths.add(Path.of(args[++i])); + } + + break; + } + } + + List urls = new ArrayList<>(); + + for (Path path : pluginPaths) { + try { + if (Files.isDirectory(path)) { + Stream stream = Files.list(path); + urls.addAll(stream + .filter(p -> p.getFileName().toString().toLowerCase(Locale.ENGLISH).endsWith(".jar")) + .map(p -> { + try { + return p.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList())); + stream.close(); + } else if (path.getFileName().toString().toLowerCase(Locale.ENGLISH).endsWith(".jar")) { + urls.add(path.toUri().toURL()); + } else { + System.err.println("No plugin(s) found at " + path.toFile().getCanonicalPath()); + } } catch (IOException e) { throw new UncheckedIOException(e); } } - URLClassLoader cl = new URLClassLoader(urls); + URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[0])); ServiceLoader pluginLoader = ServiceLoader.load(Plugin.class, cl); diff --git a/src/main/java/matcher/config/Config.java b/src/main/java/matcher/config/Config.java index 1603c523..77d445d8 100644 --- a/src/main/java/matcher/config/Config.java +++ b/src/main/java/matcher/config/Config.java @@ -10,14 +10,14 @@ import java.util.prefs.Preferences; public class Config { - public static void init() { + public static void init(String[] args) { Preferences prefs = Preferences.userRoot(); // in ~/.java/.userPrefs try { if (prefs.nodeExists(userPrefFolder)) { prefs = prefs.node(userPrefFolder); - if (prefs.nodeExists(lastProjectSetupKey)) setProjectConfig(new ProjectConfig(prefs.node(lastProjectSetupKey))); + if (prefs.nodeExists(lastProjectSetupKey)) setProjectConfig(new ProjectConfig.Builder(prefs.node(lastProjectSetupKey)).build()); setInputDirs(loadList(prefs, lastInputDirsKey, Config::deserializePath)); setVerifyInputFiles(prefs.getBoolean(lastVerifyInputFilesKey, true)); setUidConfig(new UidConfig(prefs)); @@ -26,6 +26,22 @@ public static void init() { } catch (BackingStoreException e) { // ignored } + + for (int i = 0; i < args.length; i++) { + switch (args[i]) { + case "--theme": + String themeId = args[++i]; + Theme theme = Theme.getById(themeId); + + if (theme == null) { + System.err.println("Startup arg '--theme' couldn't be applied, as there exists no theme with ID " + themeId + "!"); + } else { + setTheme(theme); + } + + break; + } + } } private Config() { } @@ -78,11 +94,10 @@ public static boolean setUidConfig(UidConfig config) { public static void setTheme(Theme value) { if (value != null) { theme = value; - saveTheme(); } } - private static void saveTheme() { + public static void saveTheme() { Preferences root = Preferences.userRoot().node(userPrefFolder); try { @@ -140,7 +155,7 @@ static Path deserializePath(String path) { private static final String lastVerifyInputFilesKey = "last-verify-input-files"; private static final String themeKey = "theme"; - private static ProjectConfig projectConfig = new ProjectConfig(); + private static ProjectConfig projectConfig = ProjectConfig.EMPTY; private static final List inputDirs = new ArrayList<>(); private static boolean verifyInputFiles = true; private static UidConfig uidConfig = new UidConfig(); diff --git a/src/main/java/matcher/config/ProjectConfig.java b/src/main/java/matcher/config/ProjectConfig.java index dd09c022..11059c36 100644 --- a/src/main/java/matcher/config/ProjectConfig.java +++ b/src/main/java/matcher/config/ProjectConfig.java @@ -9,24 +9,109 @@ import java.util.regex.PatternSyntaxException; public class ProjectConfig { - public ProjectConfig() { - this(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false, "", "", "", ""); - } + public static class Builder { + public Builder(List pathsA, List pathsB) { + this.pathsA = pathsA; + this.pathsB = pathsB; + } + + Builder(Preferences prefs) throws BackingStoreException { + pathsA = Config.loadList(prefs, pathsAKey, Config::deserializePath); + pathsB = Config.loadList(prefs, pathsBKey, Config::deserializePath); + classPathA = Config.loadList(prefs, classPathAKey, Config::deserializePath); + classPathB = Config.loadList(prefs, classPathBKey, Config::deserializePath); + sharedClassPath = Config.loadList(prefs, pathsSharedKey, Config::deserializePath); + inputsBeforeClassPath = prefs.getBoolean(inputsBeforeClassPathKey, false); + + String storedMappingsPathA = prefs.get(mappingsPathAKey, null); + String storedMappingsPathB = prefs.get(mappingsPathBKey, null); + mappingsPathA = storedMappingsPathA == null ? null : Path.of(storedMappingsPathA); + mappingsPathB = storedMappingsPathB == null ? null : Path.of(storedMappingsPathB); + saveUnmappedMatches = prefs.getBoolean(inputsBeforeClassPathKey, false); + + nonObfuscatedClassPatternA = prefs.get(nonObfuscatedClassPatternAKey, ""); + nonObfuscatedClassPatternB = prefs.get(nonObfuscatedClassPatternBKey, ""); + nonObfuscatedMemberPatternA = prefs.get(nonObfuscatedMemberPatternAKey, ""); + nonObfuscatedMemberPatternB = prefs.get(nonObfuscatedMemberPatternBKey, ""); + } + + public Builder classPathA(List classPathA) { + this.classPathA = classPathA; + return this; + } + + public Builder classPathB(List classPathB) { + this.classPathB = classPathB; + return this; + } - ProjectConfig(Preferences prefs) throws BackingStoreException { - this(Config.loadList(prefs, pathsAKey, Config::deserializePath), - Config.loadList(prefs, pathsBKey, Config::deserializePath), - Config.loadList(prefs, classPathAKey, Config::deserializePath), - Config.loadList(prefs, classPathBKey, Config::deserializePath), - Config.loadList(prefs, pathsSharedKey, Config::deserializePath), - prefs.getBoolean(inputsBeforeClassPathKey, false), - prefs.get(nonObfuscatedClassPatternAKey, ""), - prefs.get(nonObfuscatedClassPatternBKey, ""), - prefs.get(nonObfuscatedMemberPatternAKey, ""), - prefs.get(nonObfuscatedMemberPatternBKey, "")); + public Builder sharedClassPath(List sharedClassPath) { + this.sharedClassPath = sharedClassPath; + return this; + } + + public Builder inputsBeforeClassPath(boolean inputsBeforeClassPath) { + this.inputsBeforeClassPath = inputsBeforeClassPath; + return this; + } + + public Builder mappingsPathA(Path mappingsPathA) { + this.mappingsPathA = mappingsPathA; + return this; + } + + public Builder mappingsPathB(Path mappingsPathB) { + this.mappingsPathB = mappingsPathB; + return this; + } + + public Builder saveUnmappedMatches(boolean saveUnmappedMatches) { + this.saveUnmappedMatches = saveUnmappedMatches; + return this; + } + + public Builder nonObfuscatedClassPatternA(String nonObfuscatedClassPatternA) { + this.nonObfuscatedClassPatternA = nonObfuscatedClassPatternA; + return this; + } + + public Builder nonObfuscatedClassPatternB(String nonObfuscatedClassPatternB) { + this.nonObfuscatedClassPatternB = nonObfuscatedClassPatternB; + return this; + } + + public Builder nonObfuscatedMemberPatternA(String nonObfuscatedMemberPatternA) { + this.nonObfuscatedMemberPatternA = nonObfuscatedMemberPatternA; + return this; + } + + public Builder nonObfuscatedMemberPatternB(String nonObfuscatedMemberPatternB) { + this.nonObfuscatedMemberPatternB = nonObfuscatedMemberPatternB; + return this; + } + + public ProjectConfig build() { + return new ProjectConfig(pathsA, pathsB, classPathA, classPathB, sharedClassPath, inputsBeforeClassPath, mappingsPathA, mappingsPathB, saveUnmappedMatches, + nonObfuscatedClassPatternA, nonObfuscatedClassPatternB, nonObfuscatedMemberPatternA, nonObfuscatedMemberPatternB); + } + + protected final List pathsA; + protected final List pathsB; + protected List classPathA; + protected List classPathB; + protected List sharedClassPath; + protected boolean inputsBeforeClassPath; + protected Path mappingsPathA; + protected Path mappingsPathB; + protected boolean saveUnmappedMatches = true; + protected String nonObfuscatedClassPatternA; + protected String nonObfuscatedClassPatternB; + protected String nonObfuscatedMemberPatternA; + protected String nonObfuscatedMemberPatternB; } - public ProjectConfig(List pathsA, List pathsB, List classPathA, List classPathB, List sharedClassPath, boolean inputsBeforeClassPath, + private ProjectConfig(List pathsA, List pathsB, List classPathA, List classPathB, + List sharedClassPath, boolean inputsBeforeClassPath, Path mappingsPathA, Path mappingsPathB, boolean saveUnmappedMatches, String nonObfuscatedClassesPatternA, String nonObfuscatedClassesPatternB, String nonObfuscatedMemberPatternA, String nonObfuscatedMemberPatternB) { this.pathsA = pathsA; this.pathsB = pathsB; @@ -34,6 +119,9 @@ public ProjectConfig(List pathsA, List pathsB, List classPathA this.classPathB = classPathB; this.sharedClassPath = sharedClassPath; this.inputsBeforeClassPath = inputsBeforeClassPath; + this.mappingsPathA = mappingsPathA; + this.mappingsPathB = mappingsPathB; + this.saveUnmappedMatches = saveUnmappedMatches; this.nonObfuscatedClassPatternA = nonObfuscatedClassesPatternA; this.nonObfuscatedClassPatternB = nonObfuscatedClassesPatternB; this.nonObfuscatedMemberPatternA = nonObfuscatedMemberPatternA; @@ -64,6 +152,18 @@ public boolean hasInputsBeforeClassPath() { return inputsBeforeClassPath; } + public Path getMappingsPathA() { + return mappingsPathA; + } + + public Path getMappingsPathB() { + return mappingsPathB; + } + + public boolean isSaveUnmappedMatches() { + return saveUnmappedMatches; + } + public String getNonObfuscatedClassPatternA() { return nonObfuscatedClassPatternA; } @@ -117,18 +217,27 @@ void save(Preferences prefs) throws BackingStoreException { Config.saveList(prefs.node(classPathBKey), classPathB); Config.saveList(prefs.node(pathsSharedKey), sharedClassPath); prefs.putBoolean(inputsBeforeClassPathKey, inputsBeforeClassPath); + if (mappingsPathA != null) prefs.put(mappingsPathAKey, mappingsPathA.toString()); + if (mappingsPathB != null) prefs.put(mappingsPathBKey, mappingsPathB.toString()); + prefs.putBoolean(saveUnmappedMatchesKey, saveUnmappedMatches); prefs.put(nonObfuscatedClassPatternAKey, nonObfuscatedClassPatternA); prefs.put(nonObfuscatedClassPatternBKey, nonObfuscatedClassPatternB); prefs.put(nonObfuscatedMemberPatternAKey, nonObfuscatedMemberPatternA); prefs.put(nonObfuscatedMemberPatternBKey, nonObfuscatedMemberPatternB); } + public static final ProjectConfig EMPTY = new ProjectConfig(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + false, null, null, true, "", "", "", ""); + private static final String pathsAKey = "paths-a"; private static final String pathsBKey = "paths-b"; private static final String classPathAKey = "class-path-a"; private static final String classPathBKey = "class-path-b"; private static final String pathsSharedKey = "paths-shared"; private static final String inputsBeforeClassPathKey = "inputs-before-classpath"; + private static final String mappingsPathAKey = "mappings-path-a"; + private static final String mappingsPathBKey = "mappings-path-b"; + private static final String saveUnmappedMatchesKey = "save-unmapped-matches"; private static final String nonObfuscatedClassPatternAKey = "non-obfuscated-class-pattern-a"; private static final String nonObfuscatedClassPatternBKey = "non-obfuscated-class-pattern-b"; private static final String nonObfuscatedMemberPatternAKey = "non-obfuscated-member-pattern-a"; @@ -139,6 +248,9 @@ void save(Preferences prefs) throws BackingStoreException { private final List classPathA; private final List classPathB; private final List sharedClassPath; + private final Path mappingsPathA; + private final Path mappingsPathB; + private final boolean saveUnmappedMatches; private final boolean inputsBeforeClassPath; private final String nonObfuscatedClassPatternA; private final String nonObfuscatedClassPatternB; diff --git a/src/main/java/matcher/gui/Gui.java b/src/main/java/matcher/gui/Gui.java index 19269ac1..f7b5564e 100644 --- a/src/main/java/matcher/gui/Gui.java +++ b/src/main/java/matcher/gui/Gui.java @@ -1,6 +1,7 @@ package matcher.gui; import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -19,10 +20,12 @@ import javafx.application.Platform; import javafx.concurrent.Task; import javafx.geometry.Insets; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; import javafx.scene.layout.ColumnConstraints; @@ -38,12 +41,18 @@ import javafx.stage.StageStyle; import javafx.stage.Window; +import net.fabricmc.mappingio.MappingReader; + import matcher.Matcher; import matcher.NameType; import matcher.config.Config; +import matcher.config.ProjectConfig; import matcher.config.Theme; import matcher.gui.IGuiComponent.ViewChangeCause; import matcher.gui.menu.MainMenuBar; +import matcher.gui.menu.NewProjectPane; +import matcher.mapping.MappingField; +import matcher.mapping.Mappings; import matcher.srcprocess.BuiltinDecompiler; import matcher.type.ClassEnvironment; import matcher.type.MatchType; @@ -97,6 +106,7 @@ public void start(Stage stage) { stage.show(); border.requestFocus(); + handleStartupArgs(getParameters().getRaw()); } @Override @@ -104,6 +114,195 @@ public void stop() throws Exception { threadPool.shutdown(); } + private void handleStartupArgs(List args) { + List inputsA = new ArrayList<>(); + List inputsB = new ArrayList<>(); + List classPathA = new ArrayList<>(); + List classPathB = new ArrayList<>(); + List sharedClassPath = new ArrayList<>(); + boolean inputsBeforeClassPath = false; + Path mappingsPathA = null; + Path mappingsPathB = null; + boolean saveUnmappedMatches = true; + String nonObfuscatedClassPatternA = ""; + String nonObfuscatedClassPatternB = ""; + String nonObfuscatedMemberPatternA = ""; + String nonObfuscatedMemberPatternB = ""; + boolean validProjectConfigArgPresent = false; + + for (int i = 0; i < args.size(); i++) { + switch (args.get(i)) { + // ProjectConfig args + + case "--inputs-a": + while (i+1 < args.size() && !args.get(i+1).startsWith("--")) { + inputsA.add(Path.of(args.get(++i))); + validProjectConfigArgPresent = true; + } + + break; + case "--inputs-b": + while (i+1 < args.size() && !args.get(i+1).startsWith("--")) { + inputsB.add(Path.of(args.get(++i))); + validProjectConfigArgPresent = true; + } + + break; + case "--classpath-a": + while (i+1 < args.size() && !args.get(i+1).startsWith("--")) { + classPathA.add(Path.of(args.get(++i))); + validProjectConfigArgPresent = true; + } + + break; + case "--classpath-b": + while (i+1 < args.size() && !args.get(i+1).startsWith("--")) { + classPathB.add(Path.of(args.get(++i))); + validProjectConfigArgPresent = true; + } + + break; + case "--shared-classpath": + while (i+1 < args.size() && !args.get(i+1).startsWith("--")) { + sharedClassPath.add(Path.of(args.get(++i))); + validProjectConfigArgPresent = true; + } + + break; + case "--mappings-a": + mappingsPathA = Path.of(args.get(++i)); + validProjectConfigArgPresent = true; + break; + case "--mappings-b": + mappingsPathB = Path.of(args.get(++i)); + validProjectConfigArgPresent = true; + break; + case "--dont-save-unmapped-matches": + saveUnmappedMatches = false; + validProjectConfigArgPresent = true; + break; + case "--inputs-before-classpath": + inputsBeforeClassPath = true; + validProjectConfigArgPresent = true; + break; + case "--non-obfuscated-class-pattern-a": + nonObfuscatedClassPatternA = args.get(++i); + validProjectConfigArgPresent = true; + break; + case "--non-obfuscated-class-pattern-b": + nonObfuscatedClassPatternB = args.get(++i); + validProjectConfigArgPresent = true; + break; + case "--non-obfuscated-member-pattern-a": + nonObfuscatedMemberPatternA = args.get(++i); + validProjectConfigArgPresent = true; + break; + case "--non-obfuscated-member-pattern-b": + nonObfuscatedMemberPatternB = args.get(++i); + validProjectConfigArgPresent = true; + break; + + // GUI args + + case "--hide-unmapped-a": + hideUnmappedA = true; + break; + } + } + + if (!validProjectConfigArgPresent) return; + + ProjectConfig config = new ProjectConfig.Builder(inputsA, inputsB) + .classPathA(new ArrayList<>(classPathA)) + .classPathB(new ArrayList<>(classPathB)) + .sharedClassPath(new ArrayList<>(sharedClassPath)) + .inputsBeforeClassPath(inputsBeforeClassPath) + .mappingsPathA(mappingsPathA) + .mappingsPathB(mappingsPathB) + .saveUnmappedMatches(saveUnmappedMatches) + .nonObfuscatedClassPatternA(nonObfuscatedClassPatternA) + .nonObfuscatedClassPatternB(nonObfuscatedClassPatternB) + .nonObfuscatedMemberPatternA(nonObfuscatedMemberPatternA) + .nonObfuscatedMemberPatternB(nonObfuscatedMemberPatternB) + .build(); + + newProject(config, inputsA.isEmpty() || inputsB.isEmpty()); + } + + public CompletableFuture newProject(ProjectConfig config, boolean showConfigDialog) { + ProjectConfig newConfig; + + if (showConfigDialog) { + Dialog dialog = new Dialog<>(); + //dialog.initModality(Modality.APPLICATION_MODAL); + dialog.setResizable(true); + dialog.setTitle("Project configuration"); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + Node okButton = dialog.getDialogPane().lookupButton(ButtonType.OK); + NewProjectPane content = new NewProjectPane(config, dialog.getOwner(), okButton); + + dialog.getDialogPane().setContent(content); + dialog.setResultConverter(button -> button == ButtonType.OK ? content.createConfig() : null); + + newConfig = dialog.showAndWait().orElse(null); + if (newConfig == null || !newConfig.isValid()) return CompletableFuture.completedFuture(false); + } else { + newConfig = config; + } + + Config.setProjectConfig(newConfig); + Config.saveAsLast(); + + matcher.reset(); + onProjectChange(); + + CompletableFuture ret = new CompletableFuture<>(); + + runProgressTask("Initializing files...", + progressReceiver -> { + matcher.init(newConfig, progressReceiver); + ret.complete(true); + }, + () -> { + if (newConfig.getMappingsPathA() != null) { + Path mappingsPath = newConfig.getMappingsPathA(); + + try { + List namespaces = MappingReader.getNamespaces(mappingsPath, null); + Mappings.load(mappingsPath, null, + namespaces.get(0), namespaces.get(1), + MappingField.PLAIN, MappingField.MAPPED, + env.getEnvA(), true); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (newConfig.getMappingsPathB() != null) { + Path mappingsPath = newConfig.getMappingsPathB(); + + try { + List namespaces = MappingReader.getNamespaces(mappingsPath, null); + Mappings.load(mappingsPath, null, + namespaces.get(0), namespaces.get(1), + MappingField.PLAIN, MappingField.MAPPED, + env.getEnvB(), true); + } catch (IOException e) { + e.printStackTrace(); + } + } + + onProjectChange(); + }, + exc -> { + exc.printStackTrace(); + ret.completeExceptionally(exc); + }); + + return ret; + } + public ClassEnvironment getEnv() { return env; } @@ -189,7 +388,21 @@ public void setShowNonInputs(boolean showNonInputs) { this.showNonInputs = showNonInputs; for (IGuiComponent c : components) { - c.onViewChange(ViewChangeCause.SHOW_NON_INPUTS_TOGGLED); + c.onViewChange(ViewChangeCause.DISPLAY_CLASSES_CHANGED); + } + } + + public boolean isHideUnmappedA() { + return hideUnmappedA; + } + + public void setHideUnmappedA(boolean hideUnmappedA) { + if (this.hideUnmappedA == hideUnmappedA) return; + + this.hideUnmappedA = hideUnmappedA; + + for (IGuiComponent c : components) { + c.onViewChange(ViewChangeCause.DISPLAY_CLASSES_CHANGED); } } @@ -445,6 +658,7 @@ public enum SortKey { private boolean sortMatchesAlphabetically; private boolean useClassTreeView; private boolean showNonInputs; + private boolean hideUnmappedA; private boolean useDiffColors; private Theme lastSwitchedToTheme; diff --git a/src/main/java/matcher/gui/IGuiComponent.java b/src/main/java/matcher/gui/IGuiComponent.java index aedba952..807e0dc6 100644 --- a/src/main/java/matcher/gui/IGuiComponent.java +++ b/src/main/java/matcher/gui/IGuiComponent.java @@ -26,7 +26,7 @@ default void onMatchListRefresh() { } enum ViewChangeCause { SORTING_CHANGED, CLASS_TREE_VIEW_TOGGLED, - SHOW_NON_INPUTS_TOGGLED, + DISPLAY_CLASSES_CHANGED, THEME_CHANGED, DIFF_COLORS_TOGGLED, NAME_TYPE_CHANGED, diff --git a/src/main/java/matcher/gui/MatchPaneDst.java b/src/main/java/matcher/gui/MatchPaneDst.java index ed810a45..a6b34fdc 100644 --- a/src/main/java/matcher/gui/MatchPaneDst.java +++ b/src/main/java/matcher/gui/MatchPaneDst.java @@ -276,7 +276,7 @@ public void onProjectChange() { @Override public void onViewChange(ViewChangeCause cause) { switch (cause) { - case SHOW_NON_INPUTS_TOGGLED: + case DISPLAY_CLASSES_CHANGED: cmpClasses = gui.getEnv().getDisplayClassesB(!gui.isShowNonInputs()); break; diff --git a/src/main/java/matcher/gui/MatchPaneSrc.java b/src/main/java/matcher/gui/MatchPaneSrc.java index 341fe41c..18fc6124 100644 --- a/src/main/java/matcher/gui/MatchPaneSrc.java +++ b/src/main/java/matcher/gui/MatchPaneSrc.java @@ -368,12 +368,20 @@ public void onClassSelect(ClassInstance cls) { items.clear(); if (cls != null) { - for (MethodInstance m : cls.getMethods()) { - if (m.isReal()) items.add(m); + for (MethodInstance mth : cls.getMethods()) { + if (!mth.isReal() || (gui.isHideUnmappedA() && !mth.hasNonInheritedMappedName() && !mth.hasMappedChildren())) { + continue; + } + + items.add(mth); } - for (FieldInstance m : cls.getFields()) { - if (m.isReal()) items.add(m); + for (FieldInstance fld : cls.getFields()) { + if (!fld.isReal() || (gui.isHideUnmappedA() && !fld.hasMappedName())) { + continue; + } + + items.add(fld); } items.sort(getMemberComparator()); @@ -388,12 +396,20 @@ public void onMethodSelect(MethodInstance method) { items.clear(); if (method != null) { - for (MethodVarInstance m : method.getArgs()) { - items.add(m); + for (MethodVarInstance arg : method.getArgs()) { + if (gui.isHideUnmappedA() && !arg.hasMappedName()) { + continue; + } + + items.add(arg); } - for (MethodVarInstance m : method.getVars()) { - items.add(m); + for (MethodVarInstance var : method.getVars()) { + if (gui.isHideUnmappedA() && !var.hasMappedName()) { + continue; + } + + items.add(var); } items.sort(getVarComparator()); @@ -480,7 +496,7 @@ public void onViewChange(ViewChangeCause cause) { updateLists(true, true); break; - case SHOW_NON_INPUTS_TOGGLED: + case DISPLAY_CLASSES_CHANGED: updateLists(true, false); break; @@ -506,7 +522,7 @@ private void updateLists(boolean updateContents, boolean resortMembers) { suppressChangeEvents = true; - List classes = updateContents ? gui.getEnv().getDisplayClassesA(!gui.isShowNonInputs()) : null; + List classes = updateContents ? gui.getEnv().getDisplayClassesA(!gui.isShowNonInputs(), gui.isHideUnmappedA()) : null; if (useClassTree) { updateClassTree(classes, clsComparator, selClass); diff --git a/src/main/java/matcher/gui/menu/FileMenu.java b/src/main/java/matcher/gui/menu/FileMenu.java index 8a1f93dc..c64541c3 100644 --- a/src/main/java/matcher/gui/menu/FileMenu.java +++ b/src/main/java/matcher/gui/menu/FileMenu.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Locale; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import javafx.application.Platform; @@ -30,7 +29,6 @@ import matcher.Util; import matcher.config.Config; -import matcher.config.ProjectConfig; import matcher.gui.Gui; import matcher.gui.Gui.SelectedFile; import matcher.gui.menu.LoadMappingsPane.MappingsLoadSettings; @@ -102,51 +100,7 @@ private void init() { } private void newProject() { - newProject(Config.getProjectConfig(), true); - } - - public CompletableFuture newProject(ProjectConfig config, boolean showConfigDialog) { - ProjectConfig newConfig; - - if (showConfigDialog) { - Dialog dialog = new Dialog<>(); - //dialog.initModality(Modality.APPLICATION_MODAL); - dialog.setResizable(true); - dialog.setTitle("Project configuration"); - dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); - - Node okButton = dialog.getDialogPane().lookupButton(ButtonType.OK); - NewProjectPane content = new NewProjectPane(config, dialog.getOwner(), okButton); - - dialog.getDialogPane().setContent(content); - dialog.setResultConverter(button -> button == ButtonType.OK ? content.createConfig() : null); - - newConfig = dialog.showAndWait().orElse(null); - if (newConfig == null || !newConfig.isValid()) return CompletableFuture.completedFuture(false); - } else { - newConfig = config; - } - - Config.setProjectConfig(newConfig); - Config.saveAsLast(); - - gui.getMatcher().reset(); - gui.onProjectChange(); - - CompletableFuture ret = new CompletableFuture<>(); - - gui.runProgressTask("Initializing files...", - progressReceiver -> { - gui.getMatcher().init(newConfig, progressReceiver); - ret.complete(true); - }, - () -> gui.onProjectChange(), - exc -> { - exc.printStackTrace(); - ret.completeExceptionally(exc); - }); - - return ret; + gui.newProject(Config.getProjectConfig(), true); } private void loadProject() { diff --git a/src/main/java/matcher/gui/menu/NewProjectPane.java b/src/main/java/matcher/gui/menu/NewProjectPane.java index 229bd0a7..518761d4 100644 --- a/src/main/java/matcher/gui/menu/NewProjectPane.java +++ b/src/main/java/matcher/gui/menu/NewProjectPane.java @@ -41,7 +41,7 @@ import matcher.gui.GuiUtil; public class NewProjectPane extends GridPane { - NewProjectPane(ProjectConfig config, Window window, Node okButton) { + public NewProjectPane(ProjectConfig config, Window window, Node okButton) { this.window = window; this.okButton = okButton; @@ -310,16 +310,16 @@ private Node createMiscPane() { } public ProjectConfig createConfig() { - return new ProjectConfig(new ArrayList<>(pathsA), - new ArrayList<>(pathsB), - new ArrayList<>(classPathA), - new ArrayList<>(classPathB), - new ArrayList<>(sharedClassPath), - inputsBeforeClassPath, - nonObfuscatedClassPatternA.getText(), - nonObfuscatedClassPatternB.getText(), - nonObfuscatedMemberPatternA.getText(), - nonObfuscatedMemberPatternB.getText()); + return new ProjectConfig.Builder(new ArrayList<>(pathsA), new ArrayList<>(pathsB)) + .classPathA(new ArrayList<>(classPathA)) + .classPathB(new ArrayList<>(classPathB)) + .sharedClassPath(new ArrayList<>(sharedClassPath)) + .inputsBeforeClassPath(inputsBeforeClassPath) + .nonObfuscatedClassPatternA(nonObfuscatedClassPatternA.getText()) + .nonObfuscatedClassPatternB(nonObfuscatedClassPatternB.getText()) + .nonObfuscatedMemberPatternA(nonObfuscatedMemberPatternA.getText()) + .nonObfuscatedMemberPatternB(nonObfuscatedMemberPatternB.getText()) + .build(); } private final Window window; diff --git a/src/main/java/matcher/gui/menu/ViewMenu.java b/src/main/java/matcher/gui/menu/ViewMenu.java index 50935888..c8c33676 100644 --- a/src/main/java/matcher/gui/menu/ViewMenu.java +++ b/src/main/java/matcher/gui/menu/ViewMenu.java @@ -129,6 +129,7 @@ private void init() { radioMenuItem.setOnAction(event -> { Config.setTheme(theme); + Config.saveTheme(); gui.updateCss(); }); } diff --git a/src/main/java/matcher/mapping/Mappings.java b/src/main/java/matcher/mapping/Mappings.java index 0194a08f..6556d781 100644 --- a/src/main/java/matcher/mapping/Mappings.java +++ b/src/main/java/matcher/mapping/Mappings.java @@ -1,17 +1,13 @@ package matcher.mapping; import java.io.IOException; -import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.objectweb.asm.tree.MethodNode; - import net.fabricmc.mappingio.FlatMappingVisitor; import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingReader; @@ -827,62 +823,10 @@ private static boolean shouldExportAny(FieldInstance[] fields, MappingFormat for private static boolean shouldExportName(MethodInstance method, MappingsExportVerbosity verbosity, boolean forAnyInput, Set> exportedHierarchies) { return verbosity == MappingsExportVerbosity.FULL || method.getAllHierarchyMembers().size() == 1 - || (method.getParents().isEmpty() || forAnyInput && isAnyInputRoot(method)) + || (method.getParents().isEmpty() || forAnyInput && method.isAnyInputRoot()) && (verbosity == MappingsExportVerbosity.ROOTS || !exportedHierarchies.contains(method.getAllHierarchyMembers())); // FIXME: forAnyInput + minimal needs to use an exportedHierarchies set per origin } - private static boolean isAnyInputRoot(MethodInstance method) { - ClassInstance cls = method.getCls(); - String name = method.getName(); - String desc = method.getDesc(); - - // check if each origin that supplies this method has a parent within the same origin - - for (int i = 0; i < cls.getAsmNodes().length; i++) { - for (MethodNode m : cls.getAsmNodes()[i].methods) { - if (m.name.equals(method.getName()) - && m.desc.equals(method.getDesc())) { - if (!hasParentMethod(name, desc, method.getParents(), cls.getAsmNodeOrigin(i))) { - return true; - } else { - break; - } - } - } - } - - return false; - } - - private static boolean hasParentMethod(String name, String desc, Collection parents, URI reqOrigin) { - // check direct parents (must supply the method from the required origin) - - for (MethodInstance parent : parents) { - ClassInstance parentCls = parent.getCls(); - - for (int i = 0; i < parentCls.getAsmNodes().length; i++) { - if (parentCls.getAsmNodeOrigin(i).equals(reqOrigin)) { - for (MethodNode m : parentCls.getAsmNodes()[i].methods) { - if (m.name.equals(name) - && m.desc.equals(desc)) { - return true; - } - } - } - } - } - - // check indirect parents recursively - - for (MethodInstance parent : parents) { - if (!parent.getParents().isEmpty() && hasParentMethod(name, desc, parent.getParents(), reqOrigin)) { - return true; - } - } - - return false; - } - public static void clear(ClassEnv env) { for (ClassInstance cls : env.getClasses()) { if (!cls.isReal()) continue; diff --git a/src/main/java/matcher/serdes/MatchesIo.java b/src/main/java/matcher/serdes/MatchesIo.java index 92043b43..eeba90a7 100644 --- a/src/main/java/matcher/serdes/MatchesIo.java +++ b/src/main/java/matcher/serdes/MatchesIo.java @@ -16,6 +16,7 @@ import java.util.function.DoubleConsumer; import matcher.Matcher; +import matcher.config.Config; import matcher.type.ClassEnvironment; import matcher.type.ClassInstance; import matcher.type.FieldInstance; @@ -338,13 +339,15 @@ public static boolean write(Matcher matcher, Path path) throws IOException { List classes = new ArrayList<>(); for (ClassInstance cls : env.getClassesA()) { - if (cls.isReal() && (cls.hasMatch() || !cls.isMatchable())) { + if (cls.isReal() && (cls.hasMatch() || !cls.isMatchable()) + && (Config.getProjectConfig().isSaveUnmappedMatches() || cls.hasMappedName() || cls.hasMappedChildren())) { classes.add(cls); } } for (ClassInstance cls : env.getClassesB()) { - if (cls.isReal() && !cls.isMatchable()) { + if (cls.isReal() && !cls.isMatchable() + && (Config.getProjectConfig().isSaveUnmappedMatches() || cls.hasMappedName() || cls.hasMappedChildren())) { classes.add(cls); } } @@ -434,26 +437,32 @@ private static void writeClass(ClassInstance cls, char side, Writer out) throws out.write(cls.getMatch().getId()); out.write('\n'); + boolean saveUnmapped = Config.getProjectConfig().isSaveUnmappedMatches(); + for (MethodInstance method : cls.getMethods()) { - if (method.hasMatch() || !method.isMatchable()) { + if ((method.hasMatch() || !method.isMatchable()) + && (saveUnmapped || method.hasNonInheritedMappedName() || method.hasMappedChildren())) { writeMethod(method, 'a', out); } } for (FieldInstance field : cls.getFields()) { - if (field.hasMatch() || !field.isMatchable()) { + if ((field.hasMatch() || !field.isMatchable()) + && (saveUnmapped || field.hasMappedName())) { writeMemberMain(field, 'a', out); } } for (MethodInstance method : cls.getMatch().getMethods()) { - if (!method.isMatchable()) { + if (!method.isMatchable() + && (saveUnmapped || method.hasNonInheritedMappedName() || method.hasMappedChildren())) { writeMethod(method, 'b', out); } } for (FieldInstance field : cls.getMatch().getFields()) { - if (!field.isMatchable()) { + if (!field.isMatchable() + && (saveUnmapped || field.hasMappedName())) { writeMemberMain(field, 'b', out); } } @@ -471,27 +480,31 @@ private static void writeClass(ClassInstance cls, char side, Writer out) throws private static void writeMethod(MethodInstance method, char side, Writer out) throws IOException { writeMemberMain(method, side, out); + boolean saveUnmapped = Config.getProjectConfig().isSaveUnmappedMatches(); + if (method.hasMatch()) { for (MethodVarInstance arg : method.getArgs()) { - if (arg.hasMatch() || !arg.isMatchable()) { + if ((arg.hasMatch() || !arg.isMatchable()) + && (saveUnmapped || arg.hasMappedName())) { writeVar(arg, 'a', out); } } for (MethodVarInstance var : method.getVars()) { - if (var.hasMatch() || !var.isMatchable()) { + if ((var.hasMatch() || !var.isMatchable()) + && (saveUnmapped || var.hasMappedName())) { writeVar(var, 'a', out); } } for (MethodVarInstance arg : method.getMatch().getArgs()) { - if (!arg.isMatchable()) { + if (!arg.isMatchable() && (saveUnmapped || arg.hasMappedName())) { writeVar(arg, 'b', out); } } for (MethodVarInstance var : method.getMatch().getVars()) { - if (!var.isMatchable()) { + if (!var.isMatchable() && (saveUnmapped || var.hasMappedName())) { writeVar(var, 'b', out); } } diff --git a/src/main/java/matcher/type/ClassEnvironment.java b/src/main/java/matcher/type/ClassEnvironment.java index 7bb0cd9b..a6e2ae0b 100644 --- a/src/main/java/matcher/type/ClassEnvironment.java +++ b/src/main/java/matcher/type/ClassEnvironment.java @@ -267,19 +267,23 @@ public Collection getClassesB() { return extractorB.getClasses(); } - public List getDisplayClassesA(boolean inputsOnly) { - return getDisplayClasses(extractorA, inputsOnly); + public List getDisplayClassesA(boolean inputsOnly, boolean mappedOnly) { + return getDisplayClasses(extractorA, inputsOnly, mappedOnly); } public List getDisplayClassesB(boolean inputsOnly) { - return getDisplayClasses(extractorB, inputsOnly); + return getDisplayClasses(extractorB, inputsOnly, false); } - private static List getDisplayClasses(ClassFeatureExtractor extractor, boolean inputsOnly) { + private static List getDisplayClasses(ClassFeatureExtractor extractor, boolean inputsOnly, boolean mappedOnly) { List ret = new ArrayList<>(); for (ClassInstance cls : extractor.getClasses()) { - if (!cls.isReal() || inputsOnly && !cls.isInput()) continue; + if (!cls.isReal() + || (inputsOnly && !cls.isInput()) + || (mappedOnly && !cls.hasMappedName() && !cls.hasMappedChildren())) { + continue; + } ret.add(cls); } diff --git a/src/main/java/matcher/type/ClassInstance.java b/src/main/java/matcher/type/ClassInstance.java index 9ff8e480..30ce62db 100644 --- a/src/main/java/matcher/type/ClassInstance.java +++ b/src/main/java/matcher/type/ClassInstance.java @@ -27,7 +27,7 @@ import matcher.classifier.ClassifierUtil; import matcher.type.Signature.ClassSignature; -public final class ClassInstance implements Matchable { +public final class ClassInstance implements ParentInstance, Matchable { /** * Create a shared unknown class. */ @@ -917,6 +917,19 @@ public void setMappedName(String mappedName) { this.mappedName = mappedName; } + @Override + public boolean hasMappedChildren() { + for (MethodInstance mth : methods) { + if (mth.hasNonInheritedMappedName() || mth.hasMappedChildren()) return true; + } + + for (FieldInstance fld : fields) { + if (fld.hasMappedName()) return true; + } + + return false; + } + @Override public String getMappedComment() { if (mappedComment != null) { diff --git a/src/main/java/matcher/type/MethodInstance.java b/src/main/java/matcher/type/MethodInstance.java index bed14aaa..8b74f04b 100644 --- a/src/main/java/matcher/type/MethodInstance.java +++ b/src/main/java/matcher/type/MethodInstance.java @@ -1,6 +1,8 @@ package matcher.type; +import java.net.URI; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -18,7 +20,7 @@ import matcher.classifier.ClassifierUtil; import matcher.type.Signature.MethodSignature; -public final class MethodInstance extends MemberInstance { +public final class MethodInstance extends MemberInstance implements ParentInstance { /** * Create a shared unknown method. */ @@ -341,6 +343,94 @@ public boolean hasAllArgsMapped() { return true; } + @Override + public boolean hasMappedChildren() { + for (MethodVarInstance arg : args) { + if (arg.hasMappedName()) return true; + } + + for (MethodVarInstance var : vars) { + if (var.hasMappedName()) return true; + } + + return false; + } + + public boolean hasNonInheritedMappedName() { + return !hasParentMethod() && hasMappedName(); + } + + public boolean hasParentMethod() { + if (hasParentMethod != null) return hasParentMethod; + + return hasParentMethod = checkAncestry(AncestryCheck.HAS_PARENT_METHOD); + } + + public boolean isAnyInputRoot() { + if (anyInputRoot != null) return anyInputRoot; + + return anyInputRoot = checkAncestry(AncestryCheck.IS_ANY_INPUT_ROOT); + } + + enum AncestryCheck { + HAS_PARENT_METHOD, + IS_ANY_INPUT_ROOT + } + + private boolean checkAncestry(AncestryCheck checkType) { + // check if each origin that supplies this method has a parent within the same origin + + for (int i = 0; i < cls.getAsmNodes().length; i++) { + for (MethodNode m : cls.getAsmNodes()[i].methods) { + if (m.name.equals(getName()) && m.desc.equals(getDesc())) { + boolean parentFound = hasParentMethod(getParents(), cls.getAsmNodeOrigin(i)); + + if (parentFound && checkType == AncestryCheck.HAS_PARENT_METHOD) { + return true; + } else if (parentFound && checkType == AncestryCheck.IS_ANY_INPUT_ROOT) { + break; + } else if (!parentFound && checkType == AncestryCheck.IS_ANY_INPUT_ROOT) { + return true; + } + } + } + } + + return false; + } + + private boolean hasParentMethod(Collection parents, URI reqOrigin) { + // check direct parents (must supply the method from the required origin) + + for (MethodInstance parent : parents) { + ClassInstance parentCls = parent.getCls(); + + if (parentCls.getAsmNodes() == null) { + continue; + } + + for (int i = 0; i < parentCls.getAsmNodes().length; i++) { + if (parentCls.getAsmNodeOrigin(i).equals(reqOrigin)) { + for (MethodNode m : parentCls.getAsmNodes()[i].methods) { + if (m.name.equals(getName()) && m.desc.equals(getDesc())) { + return true; + } + } + } + } + } + + // check indirect parents recursively + + for (MethodInstance parent : parents) { + if (!parent.getParents().isEmpty() && hasParentMethod(parent.getParents(), reqOrigin)) { + return true; + } + } + + return false; + } + public ClassInstance getRetType() { return retType; } @@ -490,6 +580,8 @@ public static String getId(String name, String desc) { final MethodSignature signature; private final MethodNode asmNode; + Boolean hasParentMethod; + Boolean anyInputRoot; MethodType type = MethodType.UNKNOWN; final Set refsIn = Util.newIdentityHashSet(); diff --git a/src/main/java/matcher/type/ParentInstance.java b/src/main/java/matcher/type/ParentInstance.java new file mode 100644 index 00000000..ee24eb83 --- /dev/null +++ b/src/main/java/matcher/type/ParentInstance.java @@ -0,0 +1,5 @@ +package matcher.type; + +public interface ParentInstance { + boolean hasMappedChildren(); +}