diff --git a/.travis.yml b/.travis.yml index a9b8e47..7e8beb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ script: ./gradlew build notifications: email: false +before_install: + - git config --global user.name "Lapislazuli" + - git config --global user.email "lapislazuli@lapis.blue" + before_script: - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..5ace950 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,29 @@ +node { + def win = System.properties['os.name'].startsWith('Windows') + + stage 'Stage Checkout' + checkout scm + if (win) { + bat 'git submodule update --init' + } else { + sh 'git submodule update --init' + } + + stage 'Stage Build' + def ver = version() + echo "Building version ${ver} on branch ${env.BRANCH_NAME}" + if (win) { + bat "./gradlew -PBUILD_NUMBER=${env.BUILD_NUMBER}" + } else { + sh "./gradlew -PBUILD_NUMBER=${env.BUILD_NUMBER}" + } + + stage 'Stage Archive' + step([$class: 'ArtifactArchiver', artifacts: 'build/libs/*.jar', excludes: 'build/libs/*-base.jar', + fingerprint: true]) +} + +def version() { + def matcher = readFile('build.gradle') =~ 'version = \'(.+)\'' + matcher ? matcher[0][1] : null +} diff --git a/build.gradle b/build.gradle index b9ce9d5..9cd35b7 100644 --- a/build.gradle +++ b/build.gradle @@ -5,9 +5,9 @@ plugins { id 'idea' id 'checkstyle' - id 'net.minecrell.gitpatcher' version '0.8' + id 'net.minecrell.gitpatcher' version '0.8.1' - id 'net.minecrell.licenser' version '0.1.5' + id 'net.minecrell.licenser' version '0.2.1' id 'com.github.johnrengelman.shadow' version '1.2.3' } @@ -16,7 +16,7 @@ defaultTasks 'clean', 'licenseFormat', 'build' // Project information allprojects { group = 'blue.lapis.nocturne' - version = '1.0.5' + version = '1.1.0' sourceCompatibility = '1.8' targetCompatibility = '1.8' @@ -125,5 +125,5 @@ artifacts { } task wrapper(type: Wrapper) { - gradleVersion = '2.12' + gradleVersion = '3.0' } diff --git a/etc/checkstyle-suppressions.xml b/etc/checkstyle-suppressions.xml index 29d0b1e..76f481a 100644 --- a/etc/checkstyle-suppressions.xml +++ b/etc/checkstyle-suppressions.xml @@ -4,7 +4,7 @@ - + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9411448..d3b8398 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8d4e470..a40b0e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Mar 27 23:56:52 BST 2016 +#Tue Aug 16 18:34:06 EDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip diff --git a/gradlew b/gradlew index 9d82f78..27309d9 100755 --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..832fdb6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args diff --git a/src/main/java/blue/lapis/nocturne/Main.java b/src/main/java/blue/lapis/nocturne/Main.java index 7ff3857..8f016d4 100644 --- a/src/main/java/blue/lapis/nocturne/Main.java +++ b/src/main/java/blue/lapis/nocturne/Main.java @@ -29,6 +29,7 @@ import blue.lapis.nocturne.gui.scene.control.WebLink; import blue.lapis.nocturne.jar.model.ClassSet; import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.mapping.io.writer.MappingWriterType; import blue.lapis.nocturne.util.helper.PropertiesHelper; import blue.lapis.nocturne.util.helper.SceneHelper; @@ -86,6 +87,7 @@ public class Main extends Application { private final MappingContext mappingContext = new MappingContext(); private Path currentMappingsPath; + private MappingWriterType currentWriterType; private ClassSet loadedJar; static { @@ -248,6 +250,14 @@ public static void setCurrentMappingsPath(Path path) { getInstance().currentMappingsPath = path; } + public static MappingWriterType getCurrentWriterType() { + return getInstance().currentWriterType; + } + + public static void setCurrentWriterType(MappingWriterType currentWriterType) { + getInstance().currentWriterType = currentWriterType; + } + public static ClassSet getLoadedJar() { return getInstance().loadedJar; } diff --git a/src/main/java/blue/lapis/nocturne/gui/MainController.java b/src/main/java/blue/lapis/nocturne/gui/MainController.java index fd64b60..d8b32a9 100644 --- a/src/main/java/blue/lapis/nocturne/gui/MainController.java +++ b/src/main/java/blue/lapis/nocturne/gui/MainController.java @@ -31,8 +31,8 @@ import blue.lapis.nocturne.gui.io.jar.JarDialogHelper; import blue.lapis.nocturne.gui.io.mappings.MappingsOpenDialogHelper; import blue.lapis.nocturne.gui.io.mappings.MappingsSaveDialogHelper; -import blue.lapis.nocturne.gui.scene.control.ClassTreeItem; import blue.lapis.nocturne.gui.scene.control.CodeTab; +import blue.lapis.nocturne.gui.scene.control.IdentifiableTreeItem; import blue.lapis.nocturne.gui.scene.text.SelectableMember; import blue.lapis.nocturne.jar.model.JarClassEntry; import blue.lapis.nocturne.jar.model.hierarchy.Hierarchy; @@ -41,6 +41,7 @@ import blue.lapis.nocturne.util.Constants; import blue.lapis.nocturne.util.helper.PropertiesHelper; import blue.lapis.nocturne.util.helper.SceneHelper; + import javafx.event.ActionEvent; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; @@ -62,9 +63,14 @@ import java.io.IOException; import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; +import java.util.Set; import java.util.function.BiConsumer; +import java.util.stream.Collectors; /** * The main JavaFX controller. @@ -78,6 +84,7 @@ public class MainController implements Initializable { public MenuItem openJarButton; public MenuItem closeJarButton; public MenuItem loadMappingsButton; + public MenuItem mergeMappingsButton; public MenuItem saveMappingsButton; public MenuItem saveMappingsAsButton; public MenuItem closeButton; @@ -101,6 +108,7 @@ public MainController() { public void initialize(URL location, ResourceBundle resources) { closeJarButton.setDisable(Main.getLoadedJar() == null); loadMappingsButton.setDisable(Main.getLoadedJar() == null); + mergeMappingsButton.setDisable(Main.getLoadedJar() == null); saveMappingsButton.setDisable(Main.getLoadedJar() == null); saveMappingsAsButton.setDisable(Main.getLoadedJar() == null); resetMappingsButton.setDisable(Main.getLoadedJar() == null); @@ -131,8 +139,8 @@ private void initTreeViews() { return; } - if (selected instanceof ClassTreeItem) { - String className = ((ClassTreeItem) selected).getId(); + if (selected.getChildren().isEmpty()) { + String className = ((IdentifiableTreeItem) selected).getId().substring(1); if (Main.getLoadedJar() != null) { openTab(className, selected.getValue()); } @@ -160,6 +168,8 @@ private void initTreeViews() { private void setAccelerators() { openJarButton.setAccelerator(new KeyCodeCombination(KeyCode.O, KeyCombination.CONTROL_DOWN)); loadMappingsButton.setAccelerator(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN)); + mergeMappingsButton.setAccelerator(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, + KeyCombination.ALT_DOWN)); saveMappingsButton.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)); saveMappingsAsButton.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN, KeyCombination.ALT_DOWN)); @@ -182,6 +192,7 @@ public void closeJar(ActionEvent actionEvent) throws IOException { closeJarButton.setDisable(true); loadMappingsButton.setDisable(true); + mergeMappingsButton.setDisable(true); saveMappingsButton.setDisable(true); saveMappingsAsButton.setDisable(true); resetMappingsButton.setDisable(true); @@ -193,7 +204,19 @@ public void closeJar(ActionEvent actionEvent) throws IOException { } public void loadMappings(ActionEvent actionEvent) throws IOException { - MappingsOpenDialogHelper.openMappings(); + try { + if (MappingsSaveDialogHelper.doDirtyConfirmation()) { + return; + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + MappingsOpenDialogHelper.openMappings(false); + updateClassViews(); + } + + public void mergeMappings(ActionEvent actionEvent) throws IOException { + MappingsOpenDialogHelper.openMappings(true); updateClassViews(); } @@ -205,11 +228,27 @@ public void resetMappings(ActionEvent actionEvent) { } catch (IOException ex) { throw new RuntimeException(ex); } + Main.getMappingContext().getMappings().values().forEach(cm -> { + Main.getLoadedJar().getCurrentNames().put(cm.getObfuscatedName(), cm.getObfuscatedName()); + JarClassEntry jce = Main.getLoadedJar().getClass(cm.getObfuscatedName()).orElse(null); + if (jce == null) { + return; + } + cm.getInnerClassMappings().values() + .forEach(im -> jce.getCurrentInnerClassNames().put(im.getObfuscatedName(), im.getObfuscatedName())); + cm.getFieldMappings().values() + .forEach(fm -> jce.getCurrentFields().put(fm.getSignature(), fm.getSignature())); + cm.getMethodMappings().values() + .forEach(mm -> jce.getCurrentMethods().put(mm.getSignature(), mm.getSignature())); + }); Main.getMappingContext().clear(); Main.getLoadedJar().getClasses().forEach(jce -> jce.setDeobfuscated(false)); CodeTab.CODE_TABS.values().forEach(CodeTab::resetClassName); SelectableMember.MEMBERS.values() - .forEach(list -> list.forEach(member -> member.setAndProcessText(member.getName()))); + .forEach(list -> list.forEach(member -> { + member.setAndProcessText(member.getName()); + member.setDeobfuscated(false); + })); updateClassViews(); } @@ -263,7 +302,8 @@ public void onLanguageSelect(ActionEvent actionEvent) throws IOException { public void updateObfuscatedClassListView() { if (Main.getLoadedJar() != null) { - TreeItem root = generateTreeItem(Main.getLoadedJar().getObfuscatedHierarchy(), obfTree.getRoot()); + TreeItem root = generateTreeItem(Main.getLoadedJar().getObfuscatedHierarchy(), + getExpandedIds((IdentifiableTreeItem) obfTree.getRoot())); root.setExpanded(true); obfTree.setRoot(root); } else { @@ -274,7 +314,8 @@ public void updateObfuscatedClassListView() { public void updateDeobfuscatedClassListView() { if (Main.getLoadedJar() != null) { - TreeItem root = generateTreeItem(Main.getLoadedJar().getDeobfuscatedHierarchy(), deobfTree.getRoot()); + TreeItem root = generateTreeItem(Main.getLoadedJar().getDeobfuscatedHierarchy(), + getExpandedIds((IdentifiableTreeItem) deobfTree.getRoot())); root.setExpanded(true); deobfTree.setRoot(root); } else { @@ -282,39 +323,23 @@ public void updateDeobfuscatedClassListView() { } } - public TreeItem generateTreeItem(HierarchyElement element, TreeItem oldTreeItem) { - TreeItem treeItem; + public TreeItem generateTreeItem(HierarchyElement element, Set expanded) { + IdentifiableTreeItem treeItem; if (element instanceof HierarchyNode) { HierarchyNode node = (HierarchyNode) element; - if (node.isTerminal()) { - treeItem = new ClassTreeItem(node.getId(), node.getDisplayName()); - } else { - treeItem = new TreeItem<>(node.getDisplayName()); - } + treeItem = new IdentifiableTreeItem((node.isTerminal() ? "C" : "P") + node.getId(), node.getDisplayName()); } else { - treeItem = new TreeItem<>("(root)"); + treeItem = new IdentifiableTreeItem("//root", "(root)"); } - if (oldTreeItem != null) { - treeItem.setExpanded(oldTreeItem.isExpanded()); + + if (expanded.contains(treeItem.getId())) { + treeItem.setExpanded(true); } + if (element instanceof Hierarchy || (element instanceof HierarchyNode && !((HierarchyNode) element).isTerminal())) { - for (HierarchyNode node : element.getChildren()) { - if (oldTreeItem != null) { - boolean added = false; - for (TreeItem child : oldTreeItem.getChildren()) { - if (node.getDisplayName().equalsIgnoreCase(child.getValue())) { - treeItem.getChildren().add(this.generateTreeItem(node, child)); - added = true; - } - } - if (!added) { - treeItem.getChildren().add(this.generateTreeItem(node, null)); - } - } else { - treeItem.getChildren().add(this.generateTreeItem(node, null)); - } - } + treeItem.getChildren().addAll(element.getChildren().stream() + .map(e -> this.generateTreeItem(e, expanded)).collect(Collectors.toList())); } treeItem.getChildren().setAll(treeItem.getChildren().sorted((t1, t2) -> { boolean c1 = t1.getChildren().size() > 0; @@ -370,4 +395,26 @@ public static boolean isInitialized() { return INSTANCE != null; } + private static Map flatten(IdentifiableTreeItem tree) { + Map map = new HashMap<>(); + map.put(tree.getId(), tree); + if (tree.getChildren().isEmpty()) { + return map; + } + + for (TreeItem child : tree.getChildren()) { + map.putAll(flatten((IdentifiableTreeItem) child)); + } + return map; + } + + private static Set getExpandedIds(IdentifiableTreeItem tree) { + if (tree == null) { + return Collections.emptySet(); + } + + return flatten(tree).entrySet().stream().filter(e -> e.getValue().isExpanded()).map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + } diff --git a/src/main/java/blue/lapis/nocturne/gui/io/jar/JarDialogHelper.java b/src/main/java/blue/lapis/nocturne/gui/io/jar/JarDialogHelper.java index bd1faea..2d3d4e9 100644 --- a/src/main/java/blue/lapis/nocturne/gui/io/jar/JarDialogHelper.java +++ b/src/main/java/blue/lapis/nocturne/gui/io/jar/JarDialogHelper.java @@ -89,6 +89,7 @@ public static void openJar(MainController controller) throws IOException { if (classSet != null) { controller.closeJarButton.setDisable(false); controller.loadMappingsButton.setDisable(false); + controller.mergeMappingsButton.setDisable(false); controller.saveMappingsAsButton.setDisable(false); controller.resetMappingsButton.setDisable(false); } diff --git a/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsOpenDialogHelper.java b/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsOpenDialogHelper.java index 24c5f1f..10c04e8 100644 --- a/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsOpenDialogHelper.java +++ b/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsOpenDialogHelper.java @@ -38,7 +38,6 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -50,11 +49,16 @@ public final class MappingsOpenDialogHelper { private MappingsOpenDialogHelper() { } - public static void openMappings() throws IOException { + public static void openMappings(boolean merge) throws IOException { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(Main.getResourceBundle().getString("filechooser.open_mapping")); - Arrays.asList(MappingReaderType.values()) - .forEach(t -> fileChooser.getExtensionFilters().add(t.getExtensionFilter())); + Arrays.asList(MappingReaderType.values()).forEach(t -> { + fileChooser.getExtensionFilters().add(t.getExtensionFilter()); + if (Main.getPropertiesHelper().getProperty(PropertiesHelper.Key.LAST_MAPPING_LOAD_FORMAT) + .equals(t.name())) { + fileChooser.setSelectedExtensionFilter(t.getExtensionFilter()); + } + }); String lastDir = Main.getPropertiesHelper().getProperty(PropertiesHelper.Key.LAST_MAPPINGS_DIRECTORY); if (!lastDir.isEmpty()) { @@ -72,17 +76,20 @@ public static void openMappings() throws IOException { Path selectedPath = selectedFile.toPath(); - if (Files.exists(selectedPath)) { //TODO: isn't this redundant? - try (MappingsReader reader = MappingReaderType.fromExtensionFilter(fileChooser.getSelectedExtensionFilter()) - .constructReader(new BufferedReader(new FileReader(selectedFile)))) { - MappingContext context = reader.read(); - Main.getMappingContext().assimilate(context); - MainController.INSTANCE.updateClassViews(); - Main.getMappingContext().setDirty(false); + MappingReaderType type = MappingReaderType.fromExtensionFilter(fileChooser.getSelectedExtensionFilter()); + Main.getPropertiesHelper() + .setProperty(PropertiesHelper.Key.LAST_MAPPING_LOAD_FORMAT, type.getFormatType().name()); + try (MappingsReader reader = type.constructReader(new BufferedReader(new FileReader(selectedFile)))) { + MappingContext context = reader.read(); + if (!merge) { + Main.getMappingContext().clear(); } - - Main.setCurrentMappingsPath(selectedPath); + Main.getMappingContext().assimilate(context); + MainController.INSTANCE.updateClassViews(); + Main.getMappingContext().setDirty(false); } + + Main.setCurrentMappingsPath(selectedPath); } } diff --git a/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsSaveDialogHelper.java b/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsSaveDialogHelper.java index 0218f86..4c5a40f 100644 --- a/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsSaveDialogHelper.java +++ b/src/main/java/blue/lapis/nocturne/gui/io/mappings/MappingsSaveDialogHelper.java @@ -26,7 +26,8 @@ package blue.lapis.nocturne.gui.io.mappings; import blue.lapis.nocturne.Main; -import blue.lapis.nocturne.mapping.io.writer.SrgWriter; +import blue.lapis.nocturne.mapping.io.writer.MappingWriterType; +import blue.lapis.nocturne.mapping.io.writer.MappingsWriter; import blue.lapis.nocturne.util.helper.PropertiesHelper; import javafx.scene.control.Alert; @@ -38,6 +39,7 @@ import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; /** * Static utility class for dialogs for saving mappings. @@ -53,16 +55,19 @@ public static void saveMappings() throws IOException { return; } - saveMappings0(); + saveMappings0(Main.getCurrentWriterType()); } public static boolean saveMappingsAs() throws IOException { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(Main.getResourceBundle().getString("filechooser.save_mapping")); - fileChooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter(Main.getResourceBundle().getString("filechooser.type_srg"), "*.srg"), - new FileChooser.ExtensionFilter(Main.getResourceBundle().getString("filechooser.type_all"), "*.*") - ); + Arrays.asList(MappingWriterType.values()).forEach(t -> { + fileChooser.getExtensionFilters().add(t.getExtensionFilter()); + if (Main.getPropertiesHelper().getProperty(PropertiesHelper.Key.LAST_MAPPING_SAVE_FORMAT) + .equals(t.name())) { + fileChooser.setSelectedExtensionFilter(t.getExtensionFilter()); + } + }); String lastDir = Main.getPropertiesHelper().getProperty(PropertiesHelper.Key.LAST_MAPPINGS_DIRECTORY); if (!lastDir.isEmpty()) { @@ -88,15 +93,31 @@ public static boolean saveMappingsAs() throws IOException { Main.getMappingContext().setDirty(true); } + final MappingWriterType writerType + = MappingWriterType.fromExtensionFilter(fileChooser.getSelectedExtensionFilter()); + + if (writerType == null) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(Main.getResourceBundle().getString("filechooser.no_extension.title")); + alert.setContentText(Main.getResourceBundle().getString("filechooser.no_extension")); + + alert.showAndWait(); + return false; + } + Main.setCurrentMappingsPath(selectedFile.toPath()); + Main.setCurrentWriterType(writerType); - saveMappings0(); + saveMappings0(writerType); + Main.getPropertiesHelper() + .setProperty(PropertiesHelper.Key.LAST_MAPPING_SAVE_FORMAT, writerType.getFormatType().name()); return true; } - private static void saveMappings0() throws IOException { + private static void saveMappings0(MappingWriterType writerType) throws IOException { if (Main.getMappingContext().isDirty()) { - try (SrgWriter writer = new SrgWriter(new PrintWriter(Main.getCurrentMappingsPath().toFile()))) { + try (MappingsWriter writer + = writerType.constructWriter(new PrintWriter(Main.getCurrentMappingsPath().toFile()))) { writer.write(Main.getMappingContext()); } diff --git a/src/main/java/blue/lapis/nocturne/gui/scene/control/CodeTab.java b/src/main/java/blue/lapis/nocturne/gui/scene/control/CodeTab.java index 7e6188d..7f89190 100644 --- a/src/main/java/blue/lapis/nocturne/gui/scene/control/CodeTab.java +++ b/src/main/java/blue/lapis/nocturne/gui/scene/control/CodeTab.java @@ -161,7 +161,9 @@ public void setCode(String code) { public enum SelectableMemberType { FIELD("codetab.identifier.field", "codetab.identifier.type"), METHOD("codetab.identifier.method", "codetab.identifier.descriptor"), - CLASS("codetab.identifier.class"); + ARG("codetab.identifier.param", "codetab.identifier.type"), + CLASS("codetab.identifier.class"), + ; private final String identifierLabel; private final String infoLabel; @@ -216,6 +218,8 @@ public static SelectableMemberType fromMemberType(MemberType type) { return SelectableMemberType.FIELD; case METHOD: return SelectableMemberType.METHOD; + case ARG: + return SelectableMemberType.ARG; default: throw new AssertionError(); } diff --git a/src/main/java/blue/lapis/nocturne/gui/scene/control/ClassTreeItem.java b/src/main/java/blue/lapis/nocturne/gui/scene/control/IdentifiableTreeItem.java similarity index 91% rename from src/main/java/blue/lapis/nocturne/gui/scene/control/ClassTreeItem.java rename to src/main/java/blue/lapis/nocturne/gui/scene/control/IdentifiableTreeItem.java index 1eae157..3911752 100644 --- a/src/main/java/blue/lapis/nocturne/gui/scene/control/ClassTreeItem.java +++ b/src/main/java/blue/lapis/nocturne/gui/scene/control/IdentifiableTreeItem.java @@ -27,11 +27,11 @@ import javafx.scene.control.TreeItem; -public class ClassTreeItem extends TreeItem { +public class IdentifiableTreeItem extends TreeItem { private final String id; - public ClassTreeItem(String id, String displayName) { + public IdentifiableTreeItem(String id, String displayName) { this.setValue(displayName); this.id = id; } diff --git a/src/main/java/blue/lapis/nocturne/gui/scene/text/SelectableMember.java b/src/main/java/blue/lapis/nocturne/gui/scene/text/SelectableMember.java index 4463bd4..5f981eb 100644 --- a/src/main/java/blue/lapis/nocturne/gui/scene/text/SelectableMember.java +++ b/src/main/java/blue/lapis/nocturne/gui/scene/text/SelectableMember.java @@ -29,17 +29,24 @@ import static blue.lapis.nocturne.util.Constants.DOT_PATTERN; import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_CHAR; import static blue.lapis.nocturne.util.Constants.Processing.CLASS_PREFIX; +import static blue.lapis.nocturne.util.helper.MappingsHelper.genMethodMapping; +import static blue.lapis.nocturne.util.helper.MappingsHelper.getOrCreateClassMapping; import blue.lapis.nocturne.Main; import blue.lapis.nocturne.gui.MainController; import blue.lapis.nocturne.gui.scene.control.CodeTab; import blue.lapis.nocturne.jar.model.JarClassEntry; import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.jar.model.attribute.Type; import blue.lapis.nocturne.mapping.model.ClassMapping; +import blue.lapis.nocturne.mapping.model.FieldMapping; import blue.lapis.nocturne.mapping.model.Mapping; import blue.lapis.nocturne.mapping.model.MemberMapping; +import blue.lapis.nocturne.mapping.model.MethodParameterMapping; import blue.lapis.nocturne.processor.index.model.IndexedClass; -import blue.lapis.nocturne.processor.index.model.IndexedMethod; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MemberSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.util.MemberType; import blue.lapis.nocturne.util.helper.HierarchyHelper; import blue.lapis.nocturne.util.helper.MappingsHelper; @@ -75,12 +82,15 @@ public class SelectableMember extends Text { private final CodeTab codeTab; private final MemberType type; + private final MemberKey key; + private final StringProperty nameProperty = new SimpleStringProperty(this, "name"); private final StringProperty descriptorProperty = new SimpleStringProperty(this, "descriptor"); private final StringProperty parentClassProperty = new SimpleStringProperty(this, "parentClass"); - private final MethodDescriptor desc; - private final IndexedMethod.Signature sig; + private final MemberSignature sig; + + private boolean deobfuscated; private String fullName = null; // only used for classes @@ -94,14 +104,24 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de this.type = type; this.nameProperty.set(name); this.descriptorProperty.set(descriptor); - this.desc = type == MemberType.METHOD ? MethodDescriptor.fromString(descriptor) : null; - this.sig = type == MemberType.METHOD ? new IndexedMethod.Signature(name, desc) : null; + + if (type == MemberType.FIELD) { + this.sig = new FieldSignature(name, Type.fromString(descriptor)); + } else if (type == MemberType.METHOD) { + this.sig = new MethodSignature(name, MethodDescriptor.fromString(descriptor)); + } else { + this.sig = null; + } + this.parentClassProperty.set(parentClass); if (type == MemberType.CLASS) { fullName = getName(); } + this.key = new MemberKey(type, getQualifiedName(), + type == MemberType.FIELD || type == MemberType.METHOD ? descriptor : null); + this.setOnMouseClicked(event1 -> { if (event1.getButton() == MouseButton.PRIMARY) { this.updateCodeTab(); @@ -140,12 +160,28 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de MenuItem resetItem = new MenuItem(Main.getResourceBundle().getString("member.contextmenu.reset")); resetItem.setOnAction(event -> { if (getText().equals(getName())) { - return; + Optional mapping = getMapping(); + if (mapping.isPresent()) { + mapping.get().setAdHoc(false); + if (mapping.get() instanceof MemberMapping) { + ClassMapping parent = ((MemberMapping) mapping.get()).getParent(); + if (mapping.get() instanceof FieldMapping) { + //noinspection ConstantConditions + parent.removeFieldMapping((FieldSignature) sig); + } else { + //noinspection ConstantConditions + parent.removeMethodMapping((MethodSignature) sig); + } + } else if (mapping.get() instanceof MethodParameterMapping) { + ((MethodParameterMapping) mapping.get()).getParent() + .removeParamMapping(mapping.get().getObfuscatedName()); + } + } + MEMBERS.get(key).forEach(sm -> sm.setDeobfuscated(false)); } switch (getType()) { case CLASS: { - Optional mapping - = MappingsHelper.getClassMapping(Main.getMappingContext(), getName()); + Optional mapping = getMapping(); if (mapping.isPresent() && !mapping.get().getObfuscatedName().equals(mapping.get().getDeobfuscatedName())) { if ((!isInnerClass() && !checkClassDupe(mapping.get().getObfuscatedName())) @@ -153,6 +189,8 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de break; } mapping.get().setDeobfuscatedName(mapping.get().getObfuscatedName()); + mapping.get().setAdHoc(false); + setDeobfuscated(false); } fullName = getName(); break; @@ -162,14 +200,22 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de Optional parent = MappingsHelper.getClassMapping(Main.getMappingContext(), getParentClass()); if (parent.isPresent()) { - MemberMapping mapping = getType() == MemberType.FIELD - ? parent.get().getFieldMappings().get(getName()) - : parent.get().getMethodMappings().get(getName() + getDescriptor()); - if (mapping != null) { - if (!checkMemberDupe(mapping.getObfuscatedName())) { + Optional mapping = getMapping(); + if (mapping.isPresent()) { + if (!checkMemberDupe(mapping.get().getObfuscatedName())) { return; } - mapping.setDeobfuscatedName(mapping.getObfuscatedName()); + if (getType() == MemberType.FIELD) { + //noinspection ConstantConditions + parent.get().removeFieldMapping((FieldSignature) sig); + } else { + //noinspection ConstantConditions + parent.get().removeMethodMapping((MethodSignature) sig); + } + MEMBERS.get(key).forEach(sm -> { + sm.setDeobfuscated(false); + sm.updateText(); + }); } } break; @@ -182,13 +228,17 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de this.updateCodeTab(); }); + MenuItem toggleDeobf = new MenuItem(Main.getResourceBundle().getString("member.contextmenu.toggleDeobf")); + toggleDeobf.setOnAction(event -> { + // I know this is gross but it's a hell of a lot easier than fixing the problem the "proper" way + boolean shouldDeobf = !this.deobfuscated; + genMapping().setAdHoc(!this.deobfuscated); // set as ad hoc if we need to mark it as deobfuscated + MEMBERS.get(key).forEach(sm -> sm.setDeobfuscated(shouldDeobf)); + }); + MenuItem jumpToDefItem = new MenuItem(Main.getResourceBundle().getString("member.contextmenu.jumpToDef")); jumpToDefItem.setOnAction(event -> { - String className = getType() == MemberType.CLASS ? getName() : getParentClass(); - if (className.contains(INNER_CLASS_SEPARATOR_CHAR + "")) { - className = className.substring(0, className.indexOf(INNER_CLASS_SEPARATOR_CHAR)); - } - + String className = getClassName(); Optional cm = MappingsHelper.getClassMapping(Main.getMappingContext(), className); MainController.INSTANCE.openTab(className, cm.isPresent() ? cm.get().getDeobfuscatedName() : className); }); @@ -196,45 +246,18 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de ContextMenu contextMenu = new ContextMenu(); contextMenu.getItems().add(renameItem); contextMenu.getItems().add(resetItem); + contextMenu.getItems().add(toggleDeobf); contextMenu.getItems().add(jumpToDefItem); - this.setOnContextMenuRequested(event -> - contextMenu.show(SelectableMember.this, event.getScreenX(), event.getScreenY())); + this.setOnContextMenuRequested(event -> { + Optional mapping = getMapping(); + toggleDeobf.setDisable(mapping.isPresent() + && !mapping.get().getObfuscatedName().equals(mapping.get().getDeobfuscatedName())); + + contextMenu.show(SelectableMember.this, event.getScreenX(), event.getScreenY()); + + }); - String qualName; - IndexedClass ic = IndexedClass.INDEXED_CLASSES.get(getParentClass()); - switch (type) { - case CLASS: - qualName = name; - break; - case FIELD: - if (!ic.getFields().contains(getName())) { - throw new IllegalArgumentException(); - } - qualName = getParentClass() + CLASS_PATH_SEPARATOR_CHAR + name; - break; - case METHOD: - String parent = null; - if (ic.getMethods().containsKey(sig)) { - parent = getParentClass(); - } else { - for (IndexedClass hc : ic.getHierarchy()) { - if (hc.getMethods().containsKey(sig)) { - parent = hc.getName(); - break; - } - } - } - if (parent == null) { - throw new IllegalArgumentException(); //TODO - } - qualName = parent + CLASS_PATH_SEPARATOR_CHAR + name; - break; - default: - throw new AssertionError(); - } - //TODO: we're ignoring field descriptors for now since SRG doesn't support them - MemberKey key = new MemberKey(type, qualName, type == MemberType.METHOD ? descriptor : null); if (!MEMBERS.containsKey(key)) { MEMBERS.put(key, new ArrayList<>()); } @@ -242,7 +265,16 @@ public SelectableMember(CodeTab codeTab, MemberType type, String name, String de updateText(); - setDeobfuscated(!getName().equals(getText())); + Optional mapping = getMapping(); + setDeobfuscated(!getName().equals(fullName) || (mapping.isPresent() && mapping.get().isAdHoc())); + } + + private String getClassName() { + String className = getType() == MemberType.CLASS ? getName() : getParentClass(); + if (className.contains(INNER_CLASS_SEPARATOR_CHAR + "")) { + className = className.substring(0, className.indexOf(INNER_CLASS_SEPARATOR_CHAR)); + } + return className; } private boolean checkClassDupe(String newName) { @@ -273,7 +305,9 @@ private boolean checkMemberDupe(String newName) { } case FIELD: { JarClassEntry jce = Main.getLoadedJar().getClass(getParentClass()).get(); - if (jce.getCurrentFieldNames().containsValue(newName)) { + FieldSignature newSig = new FieldSignature(newName, + ((FieldSignature) sig).getType()); + if (jce.getCurrentFields().containsValue(newSig)) { showDupeAlert(false); return false; } else { @@ -281,12 +315,14 @@ private boolean checkMemberDupe(String newName) { } } case METHOD: { - Set hierarchy = HierarchyHelper.getClassesInHierarchy(getParentClass(), sig).stream() - .filter(c -> Main.getLoadedJar().getClass(c).isPresent()) + Set hierarchy = HierarchyHelper.getClassesInHierarchy(getParentClass(), + (MethodSignature) sig) + .stream().filter(c -> Main.getLoadedJar().getClass(c).isPresent()) .map(c -> Main.getLoadedJar().getClass(c).get()).collect(Collectors.toSet()); for (JarClassEntry jce : hierarchy) { - IndexedMethod.Signature newSig = new IndexedMethod.Signature(newName, this.desc); - if (jce.getCurrentMethodNames().containsValue(newSig)) { + MethodSignature newSig + = new MethodSignature(newName, ((MethodSignature) sig).getDescriptor()); + if (jce.getCurrentMethods().containsValue(newSig)) { showDupeAlert(!jce.getName().equals(getName())); return false; } @@ -328,7 +364,8 @@ public void setMapping(String mapping) { break; } case FIELD: { - MappingsHelper.genFieldMapping(Main.getMappingContext(), getParentClass(), getName(), mapping); + MappingsHelper.genFieldMapping(Main.getMappingContext(), getParentClass(), (FieldSignature) sig, + mapping); break; } case METHOD: { @@ -337,9 +374,9 @@ public void setMapping(String mapping) { classes.add(clazz); for (IndexedClass ic : classes) { + //noinspection SuspiciousMethodCalls: sig must be a MethodSignature object if (ic.getMethods().containsKey(sig)) { - MappingsHelper.genMethodMapping(Main.getMappingContext(), ic.getName(), getName(), mapping, - getDescriptor()); + genMethodMapping(Main.getMappingContext(), ic.getName(), (MethodSignature) sig, mapping, false); } } break; @@ -403,10 +440,12 @@ private void updateText() { Optional classMapping = MappingsHelper.getClassMapping(Main.getMappingContext(), getParentClass()); if (classMapping.isPresent()) { - Map mappings = getType() == MemberType.FIELD + Map mappings = getType() == MemberType.FIELD ? classMapping.get().getFieldMappings() : classMapping.get().getMethodMappings(); - Mapping mapping = mappings.get(getName() + (getType() == MemberType.METHOD ? getDescriptor() : "")); + Mapping mapping = mappings.get(getType() == MemberType.METHOD + ? new MethodSignature(getName(), MethodDescriptor.fromString(getDescriptor())) + : new FieldSignature(getName(), Type.fromString(getDescriptor()))); if (mapping != null) { deobf = mapping.getDeobfuscatedName(); } @@ -422,7 +461,7 @@ private void updateText() { public static SelectableMember fromMatcher(CodeTab codeTab, Matcher matcher) { MemberType type = matcher.group().startsWith(CLASS_PREFIX) ? MemberType.CLASS - : MemberType.fromString(matcher.group(1)); + : MemberType.valueOf(matcher.group(1)); if (type == MemberType.CLASS) { return new SelectableMember(codeTab, type, matcher.group(1)); @@ -479,6 +518,7 @@ public boolean isInnerClass() { } public void setDeobfuscated(boolean deobfuscated) { + this.deobfuscated = deobfuscated; getStyleClass().clear(); if (deobfuscated) { getStyleClass().add("deobfuscated"); @@ -487,4 +527,85 @@ public void setDeobfuscated(boolean deobfuscated) { } } + private Mapping genMapping() { + switch (getType()) { + case CLASS: { + return getOrCreateClassMapping(Main.getMappingContext(), getClassName()); + } + case FIELD: { + return MappingsHelper.genFieldMapping(Main.getMappingContext(), getClassName(), (FieldSignature) sig, + getName()); + } + case METHOD: { + return MappingsHelper.genMethodMapping(Main.getMappingContext(), getClassName(), (MethodSignature) sig, + getName(), false); + } + default: { + throw new AssertionError(); + } + } + } + + @SuppressWarnings("SuspiciousMethodCalls") + private Optional getMapping() { + Optional classMapping = MappingsHelper.getClassMapping(Main.getMappingContext(), getClassName()); + if (!classMapping.isPresent()) { + return classMapping; + } + switch (getType()) { + case CLASS: { + return classMapping; + } + case FIELD: { + return Optional.ofNullable(classMapping.get().getFieldMappings().get(sig)); + } + case METHOD: { + return Optional.ofNullable(classMapping.get().getMethodMappings().get(sig)); + } + default: { + throw new AssertionError(); + } + } + } + + private String getQualifiedName() { + String qualName; + IndexedClass ic = IndexedClass.INDEXED_CLASSES.get(getParentClass()); + switch (type) { + case CLASS: + qualName = getName(); + break; + case FIELD: + //noinspection SuspiciousMethodCalls: sig must be a FieldSignature object + if (!ic.getFields().containsKey(sig)) { + throw new IllegalArgumentException(); + } + qualName = getParentClass() + CLASS_PATH_SEPARATOR_CHAR + getName(); + break; + case METHOD: + String parent = null; + //noinspection SuspiciousMethodCalls: sig must be a MethodSignature object + if (ic.getMethods().containsKey(sig)) { + parent = getParentClass(); + } else { + for (IndexedClass hc : ic.getHierarchy()) { + //noinspection SuspiciousMethodCalls: sig must be a MethodSignature object + if (hc.getMethods().containsKey(sig)) { + parent = hc.getName(); + break; + } + } + } + if (parent == null) { + throw new IllegalArgumentException(); //TODO + } + qualName = parent + CLASS_PATH_SEPARATOR_CHAR + getName(); + break; + default: + throw new AssertionError(); + } + + return qualName; + } + } diff --git a/src/main/java/blue/lapis/nocturne/jar/io/JarLoader.java b/src/main/java/blue/lapis/nocturne/jar/io/JarLoader.java index 7d0396d..651910d 100644 --- a/src/main/java/blue/lapis/nocturne/jar/io/JarLoader.java +++ b/src/main/java/blue/lapis/nocturne/jar/io/JarLoader.java @@ -120,9 +120,9 @@ public static ClassSet loadJar(String name, InputStream jarFile) throws IOExcept jar.close(); // release the resource ClassSet cs = new ClassSet(name, classes); Main.setLoadedJar(cs); - cs.getClasses().stream().forEach(JarClassEntry::index); + cs.getClasses().forEach(JarClassEntry::index); new ClassHierarchyBuilder(Sets.newHashSet(INDEXED_CLASSES.values())).buildHierarchies(); - cs.getClasses().stream().forEach(JarClassEntry::process); + cs.getClasses().forEach(JarClassEntry::process); INDEXED_CLASSES.values().forEach(IndexedClass::clearPool); return cs; } diff --git a/src/main/java/blue/lapis/nocturne/jar/model/JarClassEntry.java b/src/main/java/blue/lapis/nocturne/jar/model/JarClassEntry.java index a7b13a5..ad28f49 100644 --- a/src/main/java/blue/lapis/nocturne/jar/model/JarClassEntry.java +++ b/src/main/java/blue/lapis/nocturne/jar/model/JarClassEntry.java @@ -35,7 +35,8 @@ import blue.lapis.nocturne.decompile.SimpleBytecodeProvider; import blue.lapis.nocturne.decompile.SimpleFernflowerLogger; import blue.lapis.nocturne.processor.index.ClassIndexer; -import blue.lapis.nocturne.processor.index.model.IndexedMethod; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.processor.transform.ClassTransformer; import blue.lapis.nocturne.util.MemberType; import blue.lapis.nocturne.util.helper.StringHelper; @@ -58,6 +59,14 @@ public class JarClassEntry { private static Dialog decompileDialog; + private final String name; + private byte[] content; + private boolean deobfuscated; + + private final Map classNames = new HashMap<>(); + private final Map fields = new HashMap<>(); + private final Map methods = new HashMap<>(); + static { if (!Main.getInstance().testingEnv) { decompileDialog = new Dialog<>(); @@ -70,14 +79,6 @@ public class JarClassEntry { } } - private final String name; - private byte[] content; - private boolean deobfuscated; - - private final Map classNames = new HashMap<>(); - private final Map fieldNames = new HashMap<>(); - private final Map methodNames = new HashMap<>(); - /** * Constructs a new {@link JarClassEntry} with the given name and byte * content. @@ -193,12 +194,12 @@ public Map getCurrentInnerClassNames() { return classNames; } - public Map getCurrentFieldNames() { - return fieldNames; + public Map getCurrentFields() { + return fields; } - public Map getCurrentMethodNames() { - return methodNames; + public Map getCurrentMethods() { + return methods; } @Override diff --git a/src/main/java/blue/lapis/nocturne/jar/model/hierarchy/Hierarchy.java b/src/main/java/blue/lapis/nocturne/jar/model/hierarchy/Hierarchy.java index 408c175..00b6c04 100644 --- a/src/main/java/blue/lapis/nocturne/jar/model/hierarchy/Hierarchy.java +++ b/src/main/java/blue/lapis/nocturne/jar/model/hierarchy/Hierarchy.java @@ -53,11 +53,13 @@ public static Hierarchy fromSet(Set entries, boolean deobfuscated String[] arr = CLASS_PATH_SEPARATOR_PATTERN.split(fullName); HierarchyElement parent = root; + StringBuilder qual = new StringBuilder(); for (int i = 0; i < arr.length - 1; i++) { - if (parent != null && parent.getChild(arr[i], false).isPresent()) { + qual.append(arr[i]); + if (parent.getChild(arr[i], false).isPresent()) { parent = parent.getChild(arr[i], false).get(); } else { - parent = new HierarchyNode(id, arr[i], false, parent); + parent = new HierarchyNode(qual.toString(), arr[i], false, parent); } } new HierarchyNode(id, arr[arr.length - 1], true, parent); diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/MappingFormatType.java b/src/main/java/blue/lapis/nocturne/mapping/io/MappingFormatType.java new file mode 100644 index 0000000..77f15b6 --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/mapping/io/MappingFormatType.java @@ -0,0 +1,42 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.mapping.io; + +public enum MappingFormatType { + SRG("srg"), + JAM("jam"), + ENIGMA("*"); + + private final String extension; + + MappingFormatType(String extension) { + this.extension = extension; + } + + public String getFileExtension() { + return this.extension; + } +} diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/reader/EnigmaReader.java b/src/main/java/blue/lapis/nocturne/mapping/io/reader/EnigmaReader.java index d4f6619..668f26f 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/io/reader/EnigmaReader.java +++ b/src/main/java/blue/lapis/nocturne/mapping/io/reader/EnigmaReader.java @@ -25,11 +25,22 @@ package blue.lapis.nocturne.mapping.io.reader; +import static blue.lapis.nocturne.util.Constants.CLASS_PATH_SEPARATOR_PATTERN; +import static blue.lapis.nocturne.util.Constants.ENIGMA_ROOT_PACKAGE_PREFIX; +import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_CHAR; + import blue.lapis.nocturne.Main; +import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.jar.model.attribute.Type; import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.mapping.model.ClassMapping; +import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.util.helper.MappingsHelper; import java.io.BufferedReader; +import java.util.Stack; import java.util.stream.Collectors; /** @@ -37,6 +48,11 @@ */ public class EnigmaReader extends MappingsReader { + private static final String CLASS_MAPPING_KEY = "CLASS"; + private static final String FIELD_MAPPING_KEY = "FIELD"; + private static final String METHOD_MAPPING_KEY = "METHOD"; + private static final String ARG_MAPPING_KEY = "ARG"; + public EnigmaReader(BufferedReader reader) { super(reader); } @@ -45,75 +61,165 @@ public EnigmaReader(BufferedReader reader) { public MappingContext read() { MappingContext mappings = new MappingContext(); - String currentClass = null; + Stack classStack = new Stack<>(); + MethodMapping currentMethod = null; int lineNum = 0; + int lastIndentLevel = -1; + for (String line : reader.lines().collect(Collectors.toList())) { lineNum++; - String[] arr = line.trim().split(" "); + + // Remove comments + final int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } + + final String[] arr = line.trim().split(" "); + + // Skip empty lines if (arr.length == 0) { continue; } + + // The indentation level of the line + int indentLevel = 0; + for (int i = 0; i < line.length(); i++) { + // Check if the char is a tab + if (line.charAt(i) != '\t') { + break; + } + indentLevel++; + } + + if (lastIndentLevel != -1 && indentLevel < lastIndentLevel) { + classStack.pop(); + } + switch (arr[0]) { - case "CLASS": { + case CLASS_MAPPING_KEY: { if (arr.length < 2 || arr.length > 3) { - System.err.println("Cannot parse file: malformed class mapping on line " + lineNum); - System.exit(1); + throw new IllegalArgumentException("Cannot parse file: malformed class mapping on line " + + lineNum); } - String obf = arr[1].replace("none/", ""); - String deobf = arr.length == 3 ? arr[2] : obf; - //TODO: handle inner classes - MappingsHelper.genClassMapping(Main.getMappingContext(), obf, deobf, false); - currentClass = obf; + String obf = removeNonePrefix(arr[1]); + String deobf = arr.length == 3 ? removeNonePrefix(arr[2]) : obf; + + if (lastIndentLevel != -1 && indentLevel > lastIndentLevel) { + deobf = classStack.peek().getFullDeobfuscatedName() + INNER_CLASS_SEPARATOR_CHAR + deobf; + } + classStack.push(MappingsHelper.genClassMapping(mappings, obf, deobf, false)); + currentMethod = null; break; } - case "FIELD": { + case FIELD_MAPPING_KEY: { + if (classStack.peek() == null) { + continue; + } + if (arr.length != 4) { - System.err.println("Cannot parse file: malformed field mapping on line " + lineNum); - System.exit(1); + throw new IllegalArgumentException("Cannot parse file: malformed field mapping on line " + + lineNum); } - if (currentClass == null) { + if (classStack.isEmpty()) { throw new IllegalArgumentException("Cannot parse file: found field mapping before initial " + "class mapping on line " + lineNum); } String obf = arr[1]; String deobf = arr[2]; - String type = arr[3]; - MappingsHelper.genFieldMapping(Main.getMappingContext(), currentClass, obf, deobf); // TODO: type + Type type = removeNonePrefix(Type.fromString(arr[3])); + MappingsHelper.genFieldMapping(mappings, classStack.peek().getFullObfuscatedName(), + new FieldSignature(obf, type), deobf); + currentMethod = null; break; } - case "METHOD": { - if (arr.length == 3) { + case METHOD_MAPPING_KEY: { + if (classStack.peek() == null) { continue; } - if (arr.length != 4) { - throw new IllegalArgumentException("Cannot parse file: malformed method mapping on line " - + lineNum); - } - - if (currentClass == null) { + if (classStack.isEmpty()) { throw new IllegalArgumentException("Cannot parse file: found method mapping before initial " + "class mapping on line " + lineNum); } String obf = arr[1]; - String deobf = arr[2]; - String sig = arr[3]; - MappingsHelper.genMethodMapping(Main.getMappingContext(), currentClass, obf, deobf, sig); + String deobf; + String descStr; + if (arr.length == 3) { + deobf = obf; + descStr = arr[2]; + } else if (arr.length == 4) { + deobf = arr[2]; + descStr = arr[3]; + } else { + throw new IllegalArgumentException("Cannot parse file: malformed method mapping on line " + + lineNum); + } + + MethodDescriptor desc = removeNonePrefixes(MethodDescriptor.fromString(descStr)); + + currentMethod = MappingsHelper.genMethodMapping(mappings, classStack.peek().getFullObfuscatedName(), + new MethodSignature(obf, desc), deobf, true); break; } - case "ARG": { + case ARG_MAPPING_KEY: { + if (classStack.peek() == null) { + continue; + } + + if (arr.length != 3) { + throw new IllegalArgumentException("Cannot parse file: malformed argument mapping on line " + + lineNum); + } + + if (currentMethod == null) { + throw new IllegalArgumentException("Cannot parse file: found argument mapping before initial " + + "method mapping on line " + lineNum); + } + + int index = Integer.parseInt(arr[1]); + String deobf = arr[2]; + + MappingsHelper.genArgumentMapping(mappings, currentMethod, index, deobf); break; } default: { Main.getLogger().warning("Unrecognized mapping on line " + lineNum); } } + lastIndentLevel = indentLevel; } return mappings; } + + private String removeNonePrefix(String str) { + if (str.length() < 6) { + return str; + } + String substr = str.substring(5); + if (str.startsWith(ENIGMA_ROOT_PACKAGE_PREFIX) + && !CLASS_PATH_SEPARATOR_PATTERN.matcher(str.substring(5)).find()) { + return substr; + } + return str; + } + + private Type removeNonePrefix(Type type) { + return type.isPrimitive() ? type : Type.fromString("L" + removeNonePrefix(type.getClassName()) + ";"); + } + + private MethodDescriptor removeNonePrefixes(MethodDescriptor desc) { + Type[] params = new Type[desc.getParamTypes().length]; + for (int i = 0; i < params.length; i++) { + params[i] = removeNonePrefix(desc.getParamTypes()[i]); + } + Type returnType = removeNonePrefix(desc.getReturnType()); + return new MethodDescriptor(returnType, params); + } + } diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/reader/JamReader.java b/src/main/java/blue/lapis/nocturne/mapping/io/reader/JamReader.java new file mode 100644 index 0000000..30f459e --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/mapping/io/reader/JamReader.java @@ -0,0 +1,178 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.mapping.io.reader; + +import static blue.lapis.nocturne.util.Constants.SPACE_PATTERN; + +import blue.lapis.nocturne.Main; +import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.jar.model.attribute.Type; +import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.mapping.model.ClassMapping; +import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; +import blue.lapis.nocturne.util.helper.MappingsHelper; + +import java.io.BufferedReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * The mappings reader, for the SRG format. + */ +public class JamReader extends MappingsReader { + + private static final String CLASS_MAPPING_KEY = "CL"; + private static final String FIELD_MAPPING_KEY = "FD"; + private static final String METHOD_MAPPING_KEY = "MD"; + private static final String PARAM_MAPPING_KEY = "MP"; + + private static final int CLASS_MAPPING_ELEMENT_COUNT = 3; + private static final int FIELD_MAPPING_ELEMENT_COUNT = 5; + private static final int METHOD_MAPPING_ELEMENT_COUNT = 5; + private static final int PARAM_MAPPING_ELEMENT_COUNT = 7; + + public JamReader(BufferedReader reader) { + super(reader); + } + + @Override + public MappingContext read() { + MappingContext mappings = new MappingContext(); + + Pattern spacePattern = Pattern.compile(" ", Pattern.LITERAL); + List rawClassMappings = new ArrayList<>(); + List rawFieldMappings = new ArrayList<>(); + List rawMethodMappings = new ArrayList<>(); + List rawParamMappings = new ArrayList<>(); + + for (String line : reader.lines().collect(Collectors.toList())) { + String trim = line.trim(); + if (trim.charAt(0) == '#' || trim.isEmpty()) { + continue; + } + + if (line.length() < 4) { + Main.getLogger().warning("Found bogus line in mappings file - ignoring"); + continue; + } + + int len = spacePattern.split(line).length; + + String key = line.substring(0, 2); + if (key.equals(CLASS_MAPPING_KEY) && len == CLASS_MAPPING_ELEMENT_COUNT) { + rawClassMappings.add(line); + } else if (key.equals(FIELD_MAPPING_KEY) && len == FIELD_MAPPING_ELEMENT_COUNT) { + rawFieldMappings.add(line); + } else if (key.equals(METHOD_MAPPING_KEY) && len == METHOD_MAPPING_ELEMENT_COUNT) { + rawMethodMappings.add(line); + } else if (key.equals(PARAM_MAPPING_KEY) && len == PARAM_MAPPING_ELEMENT_COUNT) { + rawParamMappings.add(line); + } else { + Main.getLogger().warning("Discovered unrecognized key \"" + key + "\" in mappings file - ignoring"); + } + } + + // we need to sort the class mappings in order of ascending nesting level + rawClassMappings.sort((s1, s2) -> getClassNestingLevel(s1) - getClassNestingLevel(s2)); + + genClassMappings(mappings, rawClassMappings); + genFieldMappings(mappings, rawFieldMappings); + genMethodMappings(mappings, rawMethodMappings); + genMethodParamMappings(mappings, rawParamMappings); + + return mappings; + } + + private void genClassMappings(MappingContext context, List classMappings) { + for (String mapping : classMappings) { + String[] arr = SPACE_PATTERN.split(mapping); + String obf = arr[1]; + String deobf = arr[2]; + MappingsHelper.genClassMapping(context, obf, deobf, false); + } + } + + private void genFieldMappings(MappingContext context, List fieldMappings) { + for (String mapping : fieldMappings) { + String[] arr = SPACE_PATTERN.split(mapping); + String owningClass = arr[1]; + String obf = arr[2]; + String desc = arr[3]; + String deobf = arr[4]; + MappingsHelper.genFieldMapping(context, owningClass, new FieldSignature(obf, Type.fromString(desc)), deobf); + } + } + + private void genMethodMappings(MappingContext context, List methodMappings) { + for (String mapping : methodMappings) { + String[] arr = SPACE_PATTERN.split(mapping); + String owningClass = arr[1]; + String obf = arr[2]; + String desc = arr[3]; + String deobf = arr[4]; + MappingsHelper.genMethodMapping(context, owningClass, + new MethodSignature(obf, MethodDescriptor.fromString(desc)), deobf, false); + } + } + + private void genMethodParamMappings(MappingContext context, List paramMappings) { + for (String mapping : paramMappings) { + String[] arr = SPACE_PATTERN.split(mapping); + String owningClass = arr[1]; + String owningMethod = arr[2]; + String owningMethodDesc = arr[3]; //TODO: *stretching collar* oooooh... + Optional classMapping = MappingsHelper.getClassMapping(context, owningClass); + if (!classMapping.isPresent()) { + Main.getLogger().warning("Discovered orphaned method parameter mapping (class) - ignoring"); + continue; + } + MethodMapping methodMapping = classMapping.get().getMethodMappings() + .get(new MethodSignature(owningMethod, MethodDescriptor.fromString(owningMethodDesc))); + if (methodMapping == null) { + methodMapping = new MethodMapping(classMapping.get(), + new MethodSignature(owningMethod, MethodDescriptor.fromString(owningMethodDesc)), owningMethod, + false); + } + int index; + try { + index = Integer.parseInt(arr[4]); + } catch (NumberFormatException ex) { + Main.getLogger().warning("Discovered invalid method parameter mapping (index) - ignoring"); + continue; + } + + String deobf = arr[5]; + + MappingsHelper.genArgumentMapping(context, methodMapping, index, deobf); + } + } + +} diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/reader/MappingReaderType.java b/src/main/java/blue/lapis/nocturne/mapping/io/reader/MappingReaderType.java index 1ccb576..cf70b97 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/io/reader/MappingReaderType.java +++ b/src/main/java/blue/lapis/nocturne/mapping/io/reader/MappingReaderType.java @@ -26,6 +26,7 @@ package blue.lapis.nocturne.mapping.io.reader; import blue.lapis.nocturne.Main; +import blue.lapis.nocturne.mapping.io.MappingFormatType; import com.google.common.collect.Maps; import javafx.stage.FileChooser; @@ -38,12 +39,13 @@ public enum MappingReaderType { - SRG(new FileChooser.ExtensionFilter(Main.getResourceBundle().getString("filechooser.type_srg"), "*.srg"), - SrgReader.class); - /*ENIGMA(new FileChooser.ExtensionFilter(Main.getResourceBundle().getString("filechooser.type_enigma"), "*.*"), - EnigmaReader.class);*/ + SRG(MappingFormatType.SRG, SrgReader.class), + JAM(MappingFormatType.JAM, JamReader.class), + ENIGMA(MappingFormatType.ENIGMA, EnigmaReader.class); - private static final Map filterToType = Maps.newHashMap(); + private final MappingFormatType type; + + public static final Map filterToType = Maps.newHashMap(); static { Arrays.asList(values()).forEach(t -> filterToType.put(t.getExtensionFilter(), t)); @@ -52,8 +54,11 @@ public enum MappingReaderType { private final FileChooser.ExtensionFilter extensionFilter; private final Constructor readerCtor; - MappingReaderType(FileChooser.ExtensionFilter extensionFilter, Class readerClass) { - this.extensionFilter = extensionFilter; + MappingReaderType(MappingFormatType mappingType, Class readerClass) { + this.type = mappingType; + this.extensionFilter = new FileChooser.ExtensionFilter(Main.getResourceBundle() + .getString("filechooser.type_" + mappingType.name().toLowerCase()), + "*." + mappingType.getFileExtension()); try { this.readerCtor = readerClass.getConstructor(BufferedReader.class); } catch (NoSuchMethodException ex) { @@ -61,6 +66,10 @@ public enum MappingReaderType { } } + public MappingFormatType getFormatType() { + return this.type; + } + public FileChooser.ExtensionFilter getExtensionFilter() { return this.extensionFilter; } diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/reader/SrgReader.java b/src/main/java/blue/lapis/nocturne/mapping/io/reader/SrgReader.java index 30edd48..a0a0743 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/io/reader/SrgReader.java +++ b/src/main/java/blue/lapis/nocturne/mapping/io/reader/SrgReader.java @@ -27,7 +27,11 @@ import static blue.lapis.nocturne.util.Constants.CLASS_PATH_SEPARATOR_CHAR; +import blue.lapis.nocturne.Main; +import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.util.helper.MappingsHelper; import java.io.BufferedReader; @@ -41,9 +45,9 @@ */ public class SrgReader extends MappingsReader { - private static final String CLASS_MAPPING_KEY = "CL: "; - private static final String FIELD_MAPPING_KEY = "FD: "; - private static final String METHOD_MAPPING_KEY = "MD: "; + private static final String CLASS_MAPPING_KEY = "CL:"; + private static final String FIELD_MAPPING_KEY = "FD:"; + private static final String METHOD_MAPPING_KEY = "MD:"; private static final int CLASS_MAPPING_ELEMENT_COUNT = 3; private static final int FIELD_MAPPING_ELEMENT_COUNT = 3; @@ -63,13 +67,27 @@ public MappingContext read() { List rawMethodMappings = new ArrayList<>(); for (String line : reader.lines().collect(Collectors.toList())) { + String trim = line.trim(); + if (trim.charAt(0) == '#' || trim.isEmpty()) { + continue; + } + + if (line.length() < 4) { + Main.getLogger().warning("Found bogus line in mappings file - ignoring"); + continue; + } + int len = spacePattern.split(line).length; - if (line.startsWith(CLASS_MAPPING_KEY) && len == CLASS_MAPPING_ELEMENT_COUNT) { + + String key = line.substring(0, 3); + if (key.equals(CLASS_MAPPING_KEY) && len == CLASS_MAPPING_ELEMENT_COUNT) { rawClassMappings.add(line); - } else if (line.startsWith(FIELD_MAPPING_KEY) && len == FIELD_MAPPING_ELEMENT_COUNT) { + } else if (key.equals(FIELD_MAPPING_KEY) && len == FIELD_MAPPING_ELEMENT_COUNT) { rawFieldMappings.add(line); - } else if (line.startsWith(METHOD_MAPPING_KEY) && len == METHOD_MAPPING_ELEMENT_COUNT) { + } else if (key.equals(METHOD_MAPPING_KEY) && len == METHOD_MAPPING_ELEMENT_COUNT) { rawMethodMappings.add(line); + } else { + Main.getLogger().warning("Discovered unrecognized key \"" + key + "\" in mappings file - ignoring"); } } @@ -99,7 +117,8 @@ private void genFieldMappings(MappingContext context, List fieldMappings String owningClass = arr[1].substring(0, lastIndex); String obf = arr[1].substring(lastIndex + 1); String deobf = arr[2].substring(arr[2].lastIndexOf(CLASS_PATH_SEPARATOR_CHAR) + 1); - MappingsHelper.genFieldMapping(context, owningClass, obf, deobf); + // SRG doesn't support field types so we just pass a null type arg and let the helper method figure it out + MappingsHelper.genFieldMapping(context, owningClass, new FieldSignature(obf, null), deobf); } } @@ -111,7 +130,8 @@ private void genMethodMappings(MappingContext context, List methodMappin String obf = arr[1].substring(lastIndex + 1); String descriptor = arr[2]; String deobf = arr[3].substring(arr[3].lastIndexOf(CLASS_PATH_SEPARATOR_CHAR) + 1); - MappingsHelper.genMethodMapping(context, owningClass, obf, deobf, descriptor); + MappingsHelper.genMethodMapping(context, owningClass, + new MethodSignature(obf, MethodDescriptor.fromString(descriptor)), deobf, false); } } diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/writer/EnigmaWriter.java b/src/main/java/blue/lapis/nocturne/mapping/io/writer/EnigmaWriter.java new file mode 100644 index 0000000..852d964 --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/mapping/io/writer/EnigmaWriter.java @@ -0,0 +1,142 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.mapping.io.writer; + +import static blue.lapis.nocturne.util.Constants.CLASS_PATH_SEPARATOR_PATTERN; +import static blue.lapis.nocturne.util.Constants.ENIGMA_ROOT_PACKAGE_PREFIX; + +import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.jar.model.attribute.Type; +import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.mapping.model.ClassMapping; +import blue.lapis.nocturne.mapping.model.FieldMapping; +import blue.lapis.nocturne.mapping.model.InnerClassMapping; +import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.mapping.model.MethodParameterMapping; +import blue.lapis.nocturne.mapping.model.TopLevelClassMapping; + +import java.io.PrintWriter; + +/** + * The mappings writer, for the Enigma format. + */ +public class EnigmaWriter extends MappingsWriter { + + /** + * Constructs a new {@link EnigmaWriter} which outputs to the given + * {@link PrintWriter}. + * + * @param outputWriter The {@link PrintWriter} to output to + */ + public EnigmaWriter(PrintWriter outputWriter) { + super(outputWriter); + } + + @Override + public void write(MappingContext mappings) { + for (TopLevelClassMapping classMapping : mappings.getMappings().values()) { + this.writeClassMapping(classMapping, 0); + } + out.close(); + } + + protected void writeClassMapping(ClassMapping classMapping, int depth) { + boolean inner = classMapping instanceof InnerClassMapping; + if (classMapping.getDeobfuscatedName().equals(classMapping.getObfuscatedName())) { + if (!classMapping.getInnerClassMappings().isEmpty()) { + out.println(getIndentForDepth(depth) + "CLASS " + + (inner ? classMapping.getObfuscatedName() : addNonePrefix(classMapping.getObfuscatedName()))); + } + } else { + out.println(getIndentForDepth(depth) + "CLASS " + + (inner ? classMapping.getFullObfuscatedName() : addNonePrefix(classMapping.getObfuscatedName())) + + " " + + (inner ? classMapping.getDeobfuscatedName() : addNonePrefix(classMapping.getDeobfuscatedName()))); + } + + classMapping.getInnerClassMappings().values().forEach(m -> this.writeClassMapping(m, depth + 1)); + + classMapping.getFieldMappings().values().stream().filter(NOT_USELESS) + .forEach(m -> this.writeFieldMapping(m, depth + 1)); + + classMapping.getMethodMappings().values().stream().filter(NOT_USELESS) + .forEach(m -> this.writeMethodMapping(m, depth + 1)); + } + + protected void writeFieldMapping(FieldMapping fieldMapping, int depth) { + out.println(getIndentForDepth(depth) + "FIELD " + fieldMapping.getObfuscatedName() + " " + + fieldMapping.getDeobfuscatedName() + " " + + addNonePrefix(fieldMapping.getObfuscatedType()).toString()); + } + + protected void writeMethodMapping(MethodMapping methodMapping, int depth) { + if (methodMapping.getDeobfuscatedName().equals(methodMapping.getObfuscatedName())) { + out.println(getIndentForDepth(depth) + "METHOD " + methodMapping.getObfuscatedName() + " " + + addNonePrefixes(methodMapping.getObfuscatedDescriptor()).toString()); + } else { + out.println(getIndentForDepth(depth) + "METHOD " + methodMapping.getObfuscatedName() + " " + + methodMapping.getDeobfuscatedName() + " " + + addNonePrefixes(methodMapping.getObfuscatedDescriptor()).toString()); + } + + for (MethodParameterMapping methodParameterMapping : methodMapping.getParamMappings().values()) { + writeArgumentMapping(methodParameterMapping, depth + 1); + } + } + + protected void writeArgumentMapping(MethodParameterMapping argMapping, int depth) { + out.println(getIndentForDepth(depth) + "ARG " + argMapping.getIndex() + " " + argMapping.getDeobfuscatedName()); + } + + private String getIndentForDepth(int depth) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < depth; i++) { + builder.append("\t"); + } + return builder.toString(); + } + + private String addNonePrefix(String str) { + if (!CLASS_PATH_SEPARATOR_PATTERN.matcher(str).find()) { + return ENIGMA_ROOT_PACKAGE_PREFIX + str; + } + return str; + } + + private Type addNonePrefix(Type type) { + return type.isPrimitive() ? type : Type.fromString("L" + addNonePrefix(type.getClassName()) + ";"); + } + + private MethodDescriptor addNonePrefixes(MethodDescriptor desc) { + Type[] params = new Type[desc.getParamTypes().length]; + for (int i = 0; i < params.length; i++) { + params[i] = addNonePrefix(desc.getParamTypes()[i]); + } + Type returnType = addNonePrefix(desc.getReturnType()); + return new MethodDescriptor(returnType, params); + } + +} diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/writer/JamWriter.java b/src/main/java/blue/lapis/nocturne/mapping/io/writer/JamWriter.java new file mode 100644 index 0000000..404962c --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/mapping/io/writer/JamWriter.java @@ -0,0 +1,139 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.mapping.io.writer; + +import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.mapping.model.ClassMapping; +import blue.lapis.nocturne.mapping.model.FieldMapping; +import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.mapping.model.MethodParameterMapping; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * The mappings writer, for the SRG format. + */ +public class JamWriter extends MappingsWriter { + + private final ByteArrayOutputStream clOut = new ByteArrayOutputStream(); + private final ByteArrayOutputStream fdOut = new ByteArrayOutputStream(); + private final ByteArrayOutputStream mdOut = new ByteArrayOutputStream(); + private final ByteArrayOutputStream mpOut = new ByteArrayOutputStream(); + + private final PrintWriter clWriter = new PrintWriter(clOut); + private final PrintWriter fdWriter = new PrintWriter(fdOut); + private final PrintWriter mdWriter = new PrintWriter(mdOut); + private final PrintWriter mpWriter = new PrintWriter(mpOut); + + /** + * Constructs a new {@link JamWriter} which outputs to the given + * {@link PrintWriter}. + * + * @param out The {@link PrintWriter} to output to + */ + public JamWriter(PrintWriter out) { + super(out); + } + + @Override + public void write(MappingContext mappingContext) { + mappingContext.getMappings().values().forEach(this::writeClassMapping); + clWriter.close(); + fdWriter.close(); + mdWriter.close(); + mpWriter.close(); + out.write(clOut.toString()); + out.write(fdOut.toString()); + out.write(mdOut.toString()); + out.write(mpOut.toString()); + out.close(); + try { + clOut.close(); + fdOut.close(); + mdOut.close(); + mpOut.close(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Writes the given {@link ClassMapping} to the {@link JamWriter}'s + * {@link PrintWriter}. + * + * @param classMapping The {@link ClassMapping} to write + */ + protected void writeClassMapping(ClassMapping classMapping) { + if (NOT_USELESS.test(classMapping)) { + clWriter.format("CL %s %s\n", + classMapping.getFullObfuscatedName(), classMapping.getFullDeobfuscatedName()); + } + + classMapping.getInnerClassMappings().values().stream().filter(NOT_USELESS).forEach(this::writeClassMapping); + classMapping.getFieldMappings().values().stream().filter(NOT_USELESS).forEach(this::writeFieldMapping); + classMapping.getMethodMappings().values().forEach(this::writeMethodMapping); + } + + /** + * Writes the given {@link FieldMapping} to the {@link JamWriter}'s + * {@link PrintWriter}. + * + * @param fieldMapping The {@link FieldMapping} to write + */ + protected void writeFieldMapping(FieldMapping fieldMapping) { + fdWriter.format("FD %s %s %s %s\n", + fieldMapping.getParent().getFullObfuscatedName(), + fieldMapping.getObfuscatedName(), + fieldMapping.getObfuscatedType().toString(), + fieldMapping.getDeobfuscatedName()); + } + + /** + * Writes the given {@link MethodMapping} to the {@link JamWriter}'s + * {@link PrintWriter}. + * + * @param mapping The {@link MethodMapping} to write + */ + protected void writeMethodMapping(MethodMapping mapping) { + if (!mapping.getObfuscatedName().equals(mapping.getDeobfuscatedName())) { + mdWriter.format("MD %s %s %s %s\n", + mapping.getParent().getFullObfuscatedName(), + mapping.getObfuscatedName(), + mapping.getObfuscatedDescriptor(), + mapping.getDeobfuscatedName()); + } + for (MethodParameterMapping pm : mapping.getParamMappings().values()) { + mpWriter.format("MP %s %s %s %s %s\n", + pm.getParent().getParent().getFullDeobfuscatedName(), + pm.getParent().getObfuscatedName(), + pm.getParent().getObfuscatedDescriptor(), + pm.getIndex(), + pm.getDeobfuscatedName()); + } + } +} diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingWriterType.java b/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingWriterType.java new file mode 100644 index 0000000..d5a4d52 --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingWriterType.java @@ -0,0 +1,89 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.mapping.io.writer; + +import blue.lapis.nocturne.Main; +import blue.lapis.nocturne.mapping.io.MappingFormatType; + +import com.google.common.collect.Maps; +import javafx.stage.FileChooser; + +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Map; + +public enum MappingWriterType { + + SRG(MappingFormatType.SRG, SrgWriter.class), + JAM(MappingFormatType.JAM, JamWriter.class), + ENIGMA(MappingFormatType.ENIGMA, EnigmaWriter.class); + + private final MappingFormatType type; + + private static final Map filterToType = Maps.newHashMap(); + + static { + Arrays.asList(values()).forEach(t -> filterToType.put(t.getExtensionFilter(), t)); + } + + private final FileChooser.ExtensionFilter extensionFilter; + private final Constructor writerCtor; + + MappingWriterType(MappingFormatType mappingType, Class readerClass) { + this.type = mappingType; + this.extensionFilter = new FileChooser.ExtensionFilter(Main.getResourceBundle() + .getString("filechooser.type_" + mappingType.name().toLowerCase()), + "*." + mappingType.getFileExtension()); + try { + this.writerCtor = readerClass.getConstructor(PrintWriter.class); + } catch (NoSuchMethodException ex) { + throw new RuntimeException("Failed to initialize reader type for class " + readerClass.getName(), ex); + } + } + + public MappingFormatType getFormatType() { + return this.type; + } + + public FileChooser.ExtensionFilter getExtensionFilter() { + return this.extensionFilter; + } + + public MappingsWriter constructWriter(PrintWriter writer) { + try { + return writerCtor.newInstance(writer); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException ex) { + throw new RuntimeException("Failed to construct writer with class " + + writerCtor.getDeclaringClass().getName(), ex); + } + } + + public static MappingWriterType fromExtensionFilter(FileChooser.ExtensionFilter filter) { + return filterToType.get(filter); + } +} diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingsWriter.java b/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingsWriter.java index 1a8d317..a659578 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingsWriter.java +++ b/src/main/java/blue/lapis/nocturne/mapping/io/writer/MappingsWriter.java @@ -26,19 +26,21 @@ package blue.lapis.nocturne.mapping.io.writer; import blue.lapis.nocturne.mapping.MappingContext; -import blue.lapis.nocturne.mapping.model.ClassMapping; -import blue.lapis.nocturne.mapping.model.FieldMapping; -import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.mapping.model.Mapping; import java.io.Closeable; import java.io.IOException; import java.io.PrintWriter; +import java.util.function.Predicate; /** * Superclass for all writer classes. */ public abstract class MappingsWriter implements Closeable { + protected static final Predicate NOT_USELESS + = mapping -> !mapping.getObfuscatedName().equals(mapping.getDeobfuscatedName()); + protected final PrintWriter out; /** @@ -59,30 +61,6 @@ protected MappingsWriter(PrintWriter outputWriter) { */ public abstract void write(MappingContext mappings); - /** - * Writes the given {@link ClassMapping} to the {@link MappingsWriter}'s - * {@link PrintWriter}. - * - * @param classMapping The {@link ClassMapping} to write - */ - protected abstract void writeClassMapping(ClassMapping classMapping); - - /** - * Writes the given {@link FieldMapping} to the {@link MappingsWriter}'s - * {@link PrintWriter}. - * - * @param fieldMapping The {@link FieldMapping} to write - */ - protected abstract void writeFieldMapping(FieldMapping fieldMapping); - - /** - * Writes the given {@link MethodMapping} to the {@link MappingsWriter}'s - * {@link PrintWriter}. - * - * @param mapping The {@link MethodMapping} to write - */ - protected abstract void writeMethodMapping(MethodMapping mapping); - @Override public void close() throws IOException { out.close(); diff --git a/src/main/java/blue/lapis/nocturne/mapping/io/writer/SrgWriter.java b/src/main/java/blue/lapis/nocturne/mapping/io/writer/SrgWriter.java index b8b2309..7874828 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/io/writer/SrgWriter.java +++ b/src/main/java/blue/lapis/nocturne/mapping/io/writer/SrgWriter.java @@ -83,9 +83,14 @@ public void write(MappingContext mappingContext) { } } - @Override + /** + * Writes the given {@link ClassMapping} to the {@link SrgWriter}'s + * {@link PrintWriter}. + * + * @param classMapping The {@link ClassMapping} to write + */ protected void writeClassMapping(ClassMapping classMapping) { - if (!classMapping.getObfuscatedName().equals(classMapping.getDeobfuscatedName())) { + if (NOT_USELESS.test(classMapping)) { clWriter.format("CL: %s %s\n", classMapping.getFullObfuscatedName(), classMapping.getFullDeobfuscatedName()); } @@ -101,14 +106,24 @@ protected void writeClassMapping(ClassMapping classMapping) { ).forEach(this::writeMethodMapping); } - @Override + /** + * Writes the given {@link FieldMapping} to the {@link SrgWriter}'s + * {@link PrintWriter}. + * + * @param fieldMapping The {@link FieldMapping} to write + */ protected void writeFieldMapping(FieldMapping fieldMapping) { fdWriter.format("FD: %s/%s %s/%s\n", fieldMapping.getParent().getFullObfuscatedName(), fieldMapping.getObfuscatedName(), fieldMapping.getParent().getFullDeobfuscatedName(), fieldMapping.getDeobfuscatedName()); } - @Override + /** + * Writes the given {@link MethodMapping} to the {@link SrgWriter}'s + * {@link PrintWriter}. + * + * @param mapping The {@link MethodMapping} to write + */ protected void writeMethodMapping(MethodMapping mapping) { mdWriter.format("MD: %s/%s %s %s/%s %s\n", mapping.getParent().getFullObfuscatedName(), mapping.getObfuscatedName(), diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/ClassMapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/ClassMapping.java index cd8aeed..6bca435 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/model/ClassMapping.java +++ b/src/main/java/blue/lapis/nocturne/mapping/model/ClassMapping.java @@ -32,6 +32,8 @@ import blue.lapis.nocturne.gui.scene.text.SelectableMember; import blue.lapis.nocturne.jar.model.JarClassEntry; import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.util.helper.StringHelper; import com.google.common.collect.ImmutableMap; @@ -46,8 +48,8 @@ */ public abstract class ClassMapping extends Mapping { - private final Map fieldMappings = new HashMap<>(); - private final Map methodMappings = new HashMap<>(); + private final Map fieldMappings = new HashMap<>(); + private final Map methodMappings = new HashMap<>(); private final Map innerClassMappings = new HashMap<>(); /** @@ -69,7 +71,7 @@ protected ClassMapping(String obfName, String deobfName) { * * @return A clone of the {@link FieldMapping}s */ - public ImmutableMap getFieldMappings() { + public ImmutableMap getFieldMappings() { return ImmutableMap.copyOf(this.fieldMappings); } @@ -78,7 +80,7 @@ public ImmutableMap getFieldMappings() { * * @return A clone of the {@link MethodMapping}s */ - public ImmutableMap getMethodMappings() { + public ImmutableMap getMethodMappings() { return ImmutableMap.copyOf(this.methodMappings); } @@ -98,17 +100,17 @@ public ImmutableMap getInnerClassMappings() { */ void addFieldMapping(FieldMapping mapping) { mapping.initialize(); - fieldMappings.put(mapping.getObfuscatedName(), mapping); + fieldMappings.put(mapping.getSignature(), mapping); } /** - * Removes the {@link FieldMapping} by the given name from this + * Removes the {@link FieldMapping} with the given signature from this * {@link ClassMapping}. * - * @param fieldName The name of the field to remove the mapping of + * @param fieldSig The signature of the field to remove the mapping of */ - public void removeFieldMapping(String fieldName) { - fieldMappings.remove(fieldName); + public void removeFieldMapping(FieldSignature fieldSig) { + fieldMappings.remove(fieldSig); } /** @@ -120,17 +122,17 @@ public void removeFieldMapping(String fieldName) { */ void addMethodMapping(MethodMapping mapping, boolean propagate) { mapping.initialize(propagate); - methodMappings.put(mapping.getObfuscatedName() + mapping.getObfuscatedDescriptor(), mapping); + methodMappings.put(mapping.getSignature(), mapping); } /** - * Removes the {@link MethodMapping} by the given name from this + * Removes the {@link MethodMapping} with the given signature from this * {@link ClassMapping}. * - * @param methodName The name of the method to remove the mapping of + * @param methodSig The signature of the method to remove the mapping of */ - public void removeMethodMapping(String methodName) { - methodMappings.remove(methodName); + public void removeMethodMapping(MethodSignature methodSig) { + methodMappings.remove(methodSig); } /** @@ -193,7 +195,7 @@ public void setDeobfuscatedName(String name, boolean updateClassViews) { String unqualName = this instanceof InnerClassMapping ? name : StringHelper.unqualify(name); memberList.forEach(member -> { member.setText(unqualName); - member.setDeobfuscated(!unqualName.equals(member.getName())); + member.setDeobfuscated(!name.equals(member.getName())); }); if (updateClassViews) { @@ -201,12 +203,10 @@ public void setDeobfuscatedName(String name, boolean updateClassViews) { } } - public void updateEntryDeobfuscation() { + private void updateEntryDeobfuscation() { if (Main.getInstance() != null && Main.getLoadedJar() != null) { // first check is to fix stupid unit tests Optional classEntry = Main.getLoadedJar().getClass(getFullObfuscatedName()); - if (classEntry.isPresent()) { - classEntry.get().setDeobfuscated(!getObfuscatedName().equals(getDeobfuscatedName())); - } + classEntry.ifPresent(jce -> jce.setDeobfuscated(!getObfuscatedName().equals(getDeobfuscatedName()))); } } diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/FieldMapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/FieldMapping.java index 6f37638..9c81081 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/model/FieldMapping.java +++ b/src/main/java/blue/lapis/nocturne/mapping/model/FieldMapping.java @@ -30,6 +30,7 @@ import blue.lapis.nocturne.Main; import blue.lapis.nocturne.gui.scene.text.SelectableMember; import blue.lapis.nocturne.jar.model.attribute.Type; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; import blue.lapis.nocturne.util.MemberType; /** @@ -38,20 +39,19 @@ public class FieldMapping extends MemberMapping { private final ClassMapping parent; - private final Type type; //TODO: not necessary (or possible) for SRG mappings so we won't enforce it + private final FieldSignature sig; /** * Constructs a new {@link FieldMapping} with the given parameters. * * @param parent The parent {@link ClassMapping} - * @param obfName The obfuscated name of the field + * @param sig The obfuscated signature of the field * @param deobfName The deobfuscated name of the field - * @param type The (obfuscated) {@link Type} of the field */ - public FieldMapping(ClassMapping parent, String obfName, String deobfName, Type type) { - super(parent, obfName, deobfName); + public FieldMapping(ClassMapping parent, FieldSignature sig, String deobfName) { + super(parent, sig.getName(), deobfName); this.parent = parent; - this.type = type; + this.sig = sig; parent.addFieldMapping(this); } @@ -61,8 +61,8 @@ public FieldMapping(ClassMapping parent, String obfName, String deobfName, Type * * @return The {@link Type} of this field */ - public Type getType() { - return type; + public Type getObfuscatedType() { + return sig.getType(); } /** @@ -71,7 +71,7 @@ public Type getType() { * @return The deobfuscated {@link Type} of this field */ public Type getDeobfuscatedType() { - return getType().deobfuscate(getParent().getContext()); + return getObfuscatedType().deobfuscate(getParent().getContext()); } @Override @@ -79,17 +79,23 @@ public void setDeobfuscatedName(String deobf) { super.setDeobfuscatedName(deobf); Main.getLoadedJar().getClass(getParent().getFullObfuscatedName()).get() - .getCurrentFieldNames().put(getObfuscatedName(), deobf); + .getCurrentFields().put(sig, getObfuscatedName().equals(getDeobfuscatedName()) ? sig + : new FieldSignature(getDeobfuscatedName(), sig.getType())); + } + + @Override + public FieldSignature getSignature() { + return sig; } @Override protected SelectableMember.MemberKey getMemberKey() { - return new SelectableMember.MemberKey(MemberType.FIELD, getQualifiedName(), null); + return new SelectableMember.MemberKey(MemberType.FIELD, getQualifiedName(), sig.getType().toString()); } private String getQualifiedName() { return (getParent() instanceof InnerClassMapping - ? ((InnerClassMapping) getParent()).getFullObfuscatedName() + ? getParent().getFullObfuscatedName() : getParent().getObfuscatedName()) + CLASS_PATH_SEPARATOR_CHAR + getObfuscatedName(); } diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/InnerClassMapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/InnerClassMapping.java index 1decc54..9690273 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/model/InnerClassMapping.java +++ b/src/main/java/blue/lapis/nocturne/mapping/model/InnerClassMapping.java @@ -107,7 +107,7 @@ public void setDeobfuscatedName(String deobf) { return; } - super.setDeobfuscatedName(deobf); + super.setDeobfuscatedName(deobf, false); } @Override diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/Mapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/Mapping.java index d2ef8e9..063bd6b 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/model/Mapping.java +++ b/src/main/java/blue/lapis/nocturne/mapping/model/Mapping.java @@ -35,6 +35,7 @@ public abstract class Mapping { private final String obf; private String deobf; + private boolean adHoc; /** * Constructs a new mapping with the given parameters. @@ -75,10 +76,33 @@ public String getDeobfuscatedName() { * @param name The new deobfuscated name of this {@link Mapping} */ public void setDeobfuscatedName(String name) { + if (this.deobf.equals(name)) { + this.setAdHoc(false); + } this.deobf = name; getContext().setDirty(true); } + /** + * Gets whether this mapping is ad hoc, for the purpose of on-demand + * deobfuscation toggling. + * + * @return Whether this mapping is ad hoc + */ + public boolean isAdHoc() { + return adHoc; + } + + /** + * Sets whether this mapping is ad hoc, for the purpose of on-demand + * deobfuscation toggling. + * + * @param adHoc Whether this mapping is ad hoc + */ + public void setAdHoc(boolean adHoc) { + this.adHoc = adHoc; + } + /** * Gets the {@link MappingContext} which owns this {@link Mapping}. * diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/MemberMapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/MemberMapping.java index dbb9bee..e1da3b5 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/model/MemberMapping.java +++ b/src/main/java/blue/lapis/nocturne/mapping/model/MemberMapping.java @@ -27,6 +27,7 @@ import blue.lapis.nocturne.gui.scene.text.SelectableMember; import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.processor.index.model.signature.MemberSignature; import java.util.List; @@ -67,6 +68,13 @@ public void setDeobfuscatedName(String name) { }); } + /** + * Gets the signature of this {@link MemberMapping}. + * + * @return The signature of this {@link MemberMapping} + */ + public abstract MemberSignature getSignature(); + @Override public MappingContext getContext() { return getParent().getContext(); diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/MethodMapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/MethodMapping.java index c6c8616..836d363 100644 --- a/src/main/java/blue/lapis/nocturne/mapping/model/MethodMapping.java +++ b/src/main/java/blue/lapis/nocturne/mapping/model/MethodMapping.java @@ -31,36 +31,38 @@ import blue.lapis.nocturne.gui.scene.text.SelectableMember; import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; import blue.lapis.nocturne.processor.index.model.IndexedClass; -import blue.lapis.nocturne.processor.index.model.IndexedMethod; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import blue.lapis.nocturne.util.MemberType; import blue.lapis.nocturne.util.helper.HierarchyHelper; import blue.lapis.nocturne.util.helper.MappingsHelper; +import com.google.common.collect.ImmutableMap; + +import java.util.HashMap; +import java.util.Map; + /** * Represents a {@link Mapping} for a method. */ public class MethodMapping extends MemberMapping { + //TODO: this needs to have integers as keys. it doesn't make sense with strings. + private final Map argumentMappings = new HashMap<>(); private final SelectableMember.MemberKey memberKey; - private final MethodDescriptor descriptor; - private final IndexedMethod.Signature sig; + private final MethodSignature sig; /** * Constructs a new {@link MethodMapping} with the given parameters. * * @param parent The parent {@link ClassMapping} - * @param obfName The obfuscated name of the method + * @param sig The obfuscated signature of the method * @param deobfName The deobfuscated name of the method - * @param descriptor The (obfuscated) {@link MethodDescriptor descriptor} of - * the method * @param propagate Whether to propagate this mapping to super- and * sub-classes */ - public MethodMapping(ClassMapping parent, String obfName, String deobfName, MethodDescriptor descriptor, - boolean propagate) { - super(parent, obfName, deobfName); - this.descriptor = descriptor; - this.sig = new IndexedMethod.Signature(getObfuscatedName(), descriptor); + public MethodMapping(ClassMapping parent, MethodSignature sig, String deobfName, boolean propagate) { + super(parent, sig.getName(), deobfName); + this.sig = sig; memberKey = new SelectableMember.MemberKey(MemberType.METHOD, getQualifiedName(), getObfuscatedDescriptor().toString()); parent.addMethodMapping(this, propagate); @@ -70,26 +72,49 @@ public MethodMapping(ClassMapping parent, String obfName, String deobfName, Meth * Constructs a new {@link MethodMapping} with the given parameters. * * @param parent The parent {@link ClassMapping} - * @param obfName The obfuscated name of the method + * @param sig The obfuscated signature of the method * @param deobfName The deobfuscated name of the method - * @param descriptor The (obfuscated) {@link MethodDescriptor descriptor} of - * the method */ - public MethodMapping(ClassMapping parent, String obfName, String deobfName, MethodDescriptor descriptor) { - this(parent, obfName, deobfName, descriptor, true); + public MethodMapping(ClassMapping parent, MethodSignature sig, String deobfName) { + this(parent, sig, deobfName, true); } public void initialize(boolean propagate) { this.setDeobfuscatedName(getDeobfuscatedName(), propagate); } + /** + * Gets a clone of the {@link MethodParameterMapping}s. + * + * @return A clone of the {@link MethodParameterMapping}s + */ + public ImmutableMap getParamMappings() { + return ImmutableMap.copyOf(this.argumentMappings); + } + + /** + * Adds the given {@link MethodParameterMapping} to this {@link ClassMapping}. + * + * @param mapping The {@link MethodParameterMapping} to add + * @param propagate Whether to propagate this mapping to super- and + * sub-classes + */ + void addParamMapping(MethodParameterMapping mapping, boolean propagate) { + mapping.initialize(propagate); + argumentMappings.put(mapping.getObfuscatedName(), mapping); + } + + public void removeParamMapping(String name) { + argumentMappings.remove(name); + } + /** * Returns the {@link MethodDescriptor} of this method. * * @return The {@link MethodDescriptor} of this method */ public MethodDescriptor getObfuscatedDescriptor() { - return descriptor; + return sig.getDescriptor(); } /** @@ -101,6 +126,11 @@ public MethodDescriptor getDeobfuscatedDescriptor() { return getObfuscatedDescriptor().deobfuscate(getParent().getContext()); } + @Override + public MethodSignature getSignature() { + return sig; + } + @Override protected SelectableMember.MemberKey getMemberKey() { return memberKey; @@ -125,16 +155,17 @@ public void setDeobfuscatedName(String deobf, boolean propagate) { } ClassMapping cm = MappingsHelper.getOrCreateClassMapping(getContext(), clazz); - if (cm.getMethodMappings().containsKey(getObfuscatedName())) { - cm.getMethodMappings().get(getObfuscatedName()).setDeobfuscatedName(deobf, false); + if (cm.getMethodMappings().containsKey(getSignature())) { + cm.getMethodMappings().get(getSignature()).setDeobfuscatedName(deobf, false); } else { - new MethodMapping(cm, getObfuscatedName(), deobf, getObfuscatedDescriptor(), false); + new MethodMapping(cm, getSignature(), deobf, false); } } } Main.getLoadedJar().getClass(getParent().getFullObfuscatedName()).get() - .getCurrentMethodNames().put(sig, new IndexedMethod.Signature(deobf, descriptor)); + .getCurrentMethods().put(sig, getObfuscatedName().equals(getDeobfuscatedName()) ? sig + : new MethodSignature(getDeobfuscatedName(), sig.getDescriptor())); } } diff --git a/src/main/java/blue/lapis/nocturne/mapping/model/MethodParameterMapping.java b/src/main/java/blue/lapis/nocturne/mapping/model/MethodParameterMapping.java new file mode 100644 index 0000000..00669a4 --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/mapping/model/MethodParameterMapping.java @@ -0,0 +1,101 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.mapping.model; + +import blue.lapis.nocturne.gui.scene.text.SelectableMember; +import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.util.MemberType; + +/** + * Represents a {@link Mapping} for arguments. + */ +public class MethodParameterMapping extends Mapping { + + private final SelectableMember.MemberKey memberKey; + private final MethodMapping parent; + private final int index; + + /** + * Constructs a new {@link MethodParameterMapping} with the given parameters. + * + * @param parent The parent method mapping + * @param index The index of the argument + * @param deobfName The deobfuscated name of the mapped argument + * @param propagate Whether to propagate this mapping to super- and + * sub-classes + */ + public MethodParameterMapping(MethodMapping parent, int index, String deobfName, boolean propagate) { + super(deobfName, deobfName); + this.memberKey = new SelectableMember.MemberKey(MemberType.ARG, "", ""); // TODO: Use actual values + this.parent = parent; + this.index = index; + + this.parent.addParamMapping(this, propagate); + } + + public void initialize(boolean propagate) { + this.setDeobfuscatedName(getDeobfuscatedName(), propagate); + } + + /** + * Gets the index of the mapped argument. + * + * @return The index + */ + public int getIndex() { + return index; + } + + /** + * Gets the parent method mapping of this argument mapping. + * + * @return The parent mapping + */ + public MethodMapping getParent() { + return this.parent; + } + + @Override + public MappingContext getContext() { + return this.getParent().getContext(); + } + + @Override + protected SelectableMember.MemberKey getMemberKey() { + return this.memberKey; + } + + @Override + public void setDeobfuscatedName(String name) { + setDeobfuscatedName(name, true); + } + + public void setDeobfuscatedName(String deobf, boolean propagate) { + super.setDeobfuscatedName(deobf); + + // TODO: propagate + } +} diff --git a/src/main/java/blue/lapis/nocturne/processor/index/ClassIndexer.java b/src/main/java/blue/lapis/nocturne/processor/index/ClassIndexer.java index a7c61ed..c18edad 100644 --- a/src/main/java/blue/lapis/nocturne/processor/index/ClassIndexer.java +++ b/src/main/java/blue/lapis/nocturne/processor/index/ClassIndexer.java @@ -33,6 +33,7 @@ import blue.lapis.nocturne.Main; import blue.lapis.nocturne.jar.model.JarClassEntry; import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.jar.model.attribute.Type; import blue.lapis.nocturne.processor.ClassProcessor; import blue.lapis.nocturne.processor.constantpool.ConstantPoolReader; import blue.lapis.nocturne.processor.constantpool.model.ConstantPool; @@ -41,7 +42,10 @@ import blue.lapis.nocturne.processor.constantpool.model.structure.ConstantStructure; import blue.lapis.nocturne.processor.constantpool.model.structure.Utf8Structure; import blue.lapis.nocturne.processor.index.model.IndexedClass; +import blue.lapis.nocturne.processor.index.model.IndexedField; import blue.lapis.nocturne.processor.index.model.IndexedMethod; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -90,7 +94,7 @@ public IndexedClass index() { } } - List fields = indexFields(buffer, pool); + List fields = indexFields(buffer, pool); List methods = indexMethods(buffer, pool); @@ -103,16 +107,17 @@ public IndexedClass index() { * * @param buffer The buffer to read from */ - private List indexFields(ByteBuffer buffer, ConstantPool pool) { - List fields = new ArrayList<>(); + private List indexFields(ByteBuffer buffer, ConstantPool pool) { + List fields = new ArrayList<>(); int fieldCount = buffer.getShort(); // read the field count for (int i = 0; i < fieldCount; i++) { - buffer.position(buffer.position() + 2); // skip the access + IndexedField.Visibility vis = IndexedField.Visibility.fromAccessFlags(buffer.getShort()); // get the access String name = getString(pool, buffer.getShort()); // get the name - fields.add(name); - jce.getCurrentFieldNames().put(name, name); // index the field name for future reference - buffer.position(buffer.position() + 2); // skip the descriptor + Type desc = Type.fromString(getString(pool, buffer.getShort())); // get the descriptor + FieldSignature sig = new FieldSignature(name, desc); + fields.add(new IndexedField(sig, vis)); + jce.getCurrentFields().put(sig, sig); // index the field name for future reference skipAttributes(buffer); } @@ -135,9 +140,9 @@ private List indexMethods(ByteBuffer buffer, ConstantPool pool) { IndexedMethod.Visibility vis = IndexedMethod.Visibility.fromAccessFlags(buffer.getShort()); String name = getString(pool, buffer.getShort()); MethodDescriptor desc = MethodDescriptor.fromString(getString(pool, buffer.getShort())); - IndexedMethod.Signature sig = new IndexedMethod.Signature(name, desc); + MethodSignature sig = new MethodSignature(name, desc); methods.add(new IndexedMethod(sig, vis)); - jce.getCurrentMethodNames().put(sig, sig); // index the method sig for future reference + jce.getCurrentMethods().put(sig, sig); // index the method sig for future reference skipAttributes(buffer); } diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/Hierarchical.java b/src/main/java/blue/lapis/nocturne/processor/index/model/Hierarchical.java index 24e6a38..46b24da 100644 --- a/src/main/java/blue/lapis/nocturne/processor/index/model/Hierarchical.java +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/Hierarchical.java @@ -46,6 +46,7 @@ public Set getHierarchy() { public void finalizeHierarchy() { checkState(!isHierarchyFinalized, "Cannot finalize hierarchy more than once"); + //noinspection SuspiciousMethodCalls getHierarchy().remove(this); hierarchy = ImmutableSet.copyOf(hierarchy); diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedClass.java b/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedClass.java index fde0643..b195d9b 100644 --- a/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedClass.java +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedClass.java @@ -26,10 +26,11 @@ package blue.lapis.nocturne.processor.index.model; import blue.lapis.nocturne.processor.constantpool.model.ImmutableConstantPool; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import java.util.HashMap; import java.util.List; @@ -47,16 +48,17 @@ public class IndexedClass extends Hierarchical { private ImmutableConstantPool constantPool; private final String superClass; private final ImmutableList interfaces; - private final ImmutableSet fields; //TODO: use full signature instead of just name - private final ImmutableMap methods; + private final ImmutableMap fields; + private final ImmutableMap methods; public IndexedClass(String name, ImmutableConstantPool constantPool, String superClass, List interfaces, - List fields, List methods) { + List fields, List methods) { this.name = name; this.constantPool = constantPool; this.superClass = superClass; this.interfaces = ImmutableList.copyOf(interfaces); - this.fields = ImmutableSet.copyOf(fields); + this.fields = ImmutableMap.copyOf( + fields.stream().collect(Collectors.toMap(IndexedField::getSignature, f -> f))); this.methods = ImmutableMap.copyOf( methods.stream().collect(Collectors.toMap(IndexedMethod::getSignature, m -> m)) ); @@ -78,11 +80,11 @@ public ImmutableList getInterfaces() { return interfaces; } - public ImmutableSet getFields() { + public ImmutableMap getFields() { return fields; } - public ImmutableMap getMethods() { + public ImmutableMap getMethods() { return methods; } diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedField.java b/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedField.java new file mode 100644 index 0000000..c7ad5fc --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedField.java @@ -0,0 +1,89 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.processor.index.model; + +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a method serialized by Nocturne's class indexer. + */ +public class IndexedField extends Hierarchical { + + private final FieldSignature signature; + private final Visibility visibility; + + public IndexedField(FieldSignature signature, Visibility visibility) { + this.signature = signature; + this.visibility = visibility; + } + + public FieldSignature getSignature() { + return signature; + } + + public Visibility getVisibility() { + return visibility; + } + + /** + * Represents the visibility level of a particular method. + */ + public enum Visibility { + + PACKAGE ((byte) 0b000), + PUBLIC ((byte) 0b001), + PRIVATE ((byte) 0b010), + PROTECTED((byte) 0b100); + + private static Map visMap; + + private final byte tag; + + Visibility(byte tag) { + this.tag = tag; + register(); + } + + public byte getTag() { + return tag; + } + + private void register() { + if (visMap == null) { + visMap = new HashMap<>(); + } + visMap.put(getTag(), this); + } + + public static Visibility fromAccessFlags(short flags) { + return visMap.get((byte) (flags & 0b111)); // we're only interested in the last 3 bytes + } + + } +} diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedMethod.java b/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedMethod.java index 91ee88c..65453b6 100644 --- a/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedMethod.java +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/IndexedMethod.java @@ -25,26 +25,25 @@ package blue.lapis.nocturne.processor.index.model; -import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import java.util.HashMap; import java.util.Map; -import java.util.Objects; /** * Represents a method serialized by Nocturne's class indexer. */ public class IndexedMethod extends Hierarchical { - private final Signature signature; + private final MethodSignature signature; private final Visibility visibility; - public IndexedMethod(Signature signature, Visibility visibility) { + public IndexedMethod(MethodSignature signature, Visibility visibility) { this.signature = signature; this.visibility = visibility; } - public Signature getSignature() { + public MethodSignature getSignature() { return signature; } @@ -52,43 +51,6 @@ public Visibility getVisibility() { return visibility; } - /** - * Represents the unique signature of a particular method. - */ - public static class Signature { - - private final String name; - private final MethodDescriptor descriptor; - - public Signature(String name, MethodDescriptor descriptor) { - this.name = name; - this.descriptor = descriptor; - } - - public String getName() { - return name; - } - - public MethodDescriptor getDescriptor() { - return descriptor; - } - - @Override - public boolean equals(Object otherObj) { - if (!(otherObj instanceof Signature)) { - return false; - } - Signature sig = (Signature) otherObj; - return sig.getName().equals(getName()) && sig.getDescriptor().equals(getDescriptor()); - } - - @Override - public int hashCode() { - return Objects.hash(name, descriptor); - } - - } - /** * Represents the visibility level of a particular method. */ diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/signature/FieldSignature.java b/src/main/java/blue/lapis/nocturne/processor/index/model/signature/FieldSignature.java new file mode 100644 index 0000000..0c3b3fa --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/signature/FieldSignature.java @@ -0,0 +1,67 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.processor.index.model.signature; + +import blue.lapis.nocturne.jar.model.attribute.Type; + +import java.util.Objects; + +/** + * Represents the unique signature of a particular field. + */ +public class FieldSignature extends MemberSignature { + + protected final Type type; + + public FieldSignature(String name, Type type) { + super(name); + this.type = type; + } + + public Type getType() { + return type; + } + + @Override + public String toString() { + return this.getName() + ":" + this.getType().toString(); + } + + @Override + public boolean equals(Object otherObj) { + if (!(otherObj instanceof FieldSignature)) { + return false; + } + FieldSignature sig = (FieldSignature) otherObj; + return sig.getName().equals(getName()) && sig.getType().equals(getType()); + } + + @Override + public int hashCode() { + return Objects.hash(name, type); + } + +} diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/signature/MemberSignature.java b/src/main/java/blue/lapis/nocturne/processor/index/model/signature/MemberSignature.java new file mode 100644 index 0000000..ea5fd52 --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/signature/MemberSignature.java @@ -0,0 +1,43 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.processor.index.model.signature; + +/** + * Represents the unique signature of a particular class member. + */ +public abstract class MemberSignature { + + protected final String name; + + public MemberSignature(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/src/main/java/blue/lapis/nocturne/processor/index/model/signature/MethodSignature.java b/src/main/java/blue/lapis/nocturne/processor/index/model/signature/MethodSignature.java new file mode 100644 index 0000000..20461ff --- /dev/null +++ b/src/main/java/blue/lapis/nocturne/processor/index/model/signature/MethodSignature.java @@ -0,0 +1,62 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.processor.index.model.signature; + +import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; + +import java.util.Objects; + +/** + * Represents the unique signature of a particular method. + */ +public class MethodSignature extends MemberSignature { + + protected final MethodDescriptor descriptor; + + public MethodSignature(String name, MethodDescriptor descriptor) { + super(name); + this.descriptor = descriptor; + } + + public MethodDescriptor getDescriptor() { + return descriptor; + } + + @Override + public boolean equals(Object otherObj) { + if (!(otherObj instanceof MethodSignature)) { + return false; + } + MethodSignature sig = (MethodSignature) otherObj; + return sig.getName().equals(getName()) && sig.getDescriptor().equals(getDescriptor()); + } + + @Override + public int hashCode() { + return Objects.hash(name, descriptor); + } + +} diff --git a/src/main/java/blue/lapis/nocturne/util/Constants.java b/src/main/java/blue/lapis/nocturne/util/Constants.java index 9199c9e..dca4523 100644 --- a/src/main/java/blue/lapis/nocturne/util/Constants.java +++ b/src/main/java/blue/lapis/nocturne/util/Constants.java @@ -29,6 +29,7 @@ import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableMap; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import java.util.regex.Pattern; @@ -49,6 +50,8 @@ public final class Constants { public static final Pattern DOT_PATTERN = Pattern.compile(".", Pattern.LITERAL); + public static final Pattern SPACE_PATTERN = Pattern.compile(" ", Pattern.LITERAL); + public static final String CLASS_FILE_NAME_TAIL = ".class"; public static final int CLASS_FORMAT_CONSTANT_POOL_OFFSET = 8; // byte offset of the CP per the class file format @@ -62,10 +65,12 @@ public final class Constants { public static final Pattern TYPE_SEQUENCE_REGEX = Pattern.compile("(\\[*(?:(?:L.+?;)|.))"); public static final ImmutableMap FF_OPTIONS = ImmutableMap.builder() - .put("rsy", "1") // hide synthetic class members - .put("ind", " ") // set indentation string + .put(IFernflowerPreferences.REMOVE_SYNTHETIC, "1") + .put(IFernflowerPreferences.INDENT_STRING, " ") .build(); + public static final String ENIGMA_ROOT_PACKAGE_PREFIX = "none/"; + static { VERSION = MoreObjects.firstNonNull(Main.class.getPackage().getImplementationVersion(), "UNKNOWN"); } diff --git a/src/main/java/blue/lapis/nocturne/util/JavaSyntaxHighlighter.java b/src/main/java/blue/lapis/nocturne/util/JavaSyntaxHighlighter.java index 12562b7..3361229 100644 --- a/src/main/java/blue/lapis/nocturne/util/JavaSyntaxHighlighter.java +++ b/src/main/java/blue/lapis/nocturne/util/JavaSyntaxHighlighter.java @@ -81,41 +81,40 @@ private JavaSyntaxHighlighter() { */ public static void highlight(List nodes) { List newNodes = new ArrayList<>(); - nodes.stream() - .forEach(node -> { - if (node.getClass() == SelectableMember.class) { - newNodes.add(node); - return; - } - String text = ((Text) node).getText(); - Matcher matcher = PATTERN.matcher(text); - int lastIndex = 0; - - while (matcher.find()) { - String group = null; - for (String pattern : PATTERN_NAMES) { - if (matcher.group(pattern) != null) { - group = pattern; - break; - } - } - assert group != null; + nodes.forEach(node -> { + if (node.getClass() == SelectableMember.class) { + newNodes.add(node); + return; + } + String text = ((Text) node).getText(); + Matcher matcher = PATTERN.matcher(text); + int lastIndex = 0; - int start = matcher.start(group); - int end = matcher.end(group); - if (group.equals("NUMBER") && !Character.isDigit(matcher.group(group).charAt(0))) { - //TODO: I am a horrible person - start += 1; - } - newNodes.add(new Text(text.substring(lastIndex, start))); - Text syntaxItem = new Text(text.substring(start, end)); - syntaxItem.getStyleClass().add("syntax"); - syntaxItem.getStyleClass().add(group.toLowerCase()); - newNodes.add(syntaxItem); - lastIndex = matcher.end(); + while (matcher.find()) { + String group = null; + for (String pattern : PATTERN_NAMES) { + if (matcher.group(pattern) != null) { + group = pattern; + break; } - newNodes.add(new Text(text.substring(lastIndex))); - }); + } + assert group != null; + + int start = matcher.start(group); + int end = matcher.end(group); + if (group.equals("NUMBER") && !Character.isDigit(matcher.group(group).charAt(0))) { + //TODO: I am a horrible person + start += 1; + } + newNodes.add(new Text(text.substring(lastIndex, start))); + Text syntaxItem = new Text(text.substring(start, end)); + syntaxItem.getStyleClass().add("syntax"); + syntaxItem.getStyleClass().add(group.toLowerCase()); + newNodes.add(syntaxItem); + lastIndex = matcher.end(); + } + newNodes.add(new Text(text.substring(lastIndex))); + }); nodes.clear(); nodes.addAll(newNodes); } diff --git a/src/main/java/blue/lapis/nocturne/util/MemberType.java b/src/main/java/blue/lapis/nocturne/util/MemberType.java index 7146497..2d2d26a 100644 --- a/src/main/java/blue/lapis/nocturne/util/MemberType.java +++ b/src/main/java/blue/lapis/nocturne/util/MemberType.java @@ -25,10 +25,6 @@ package blue.lapis.nocturne.util; -import com.google.common.base.Preconditions; - -import java.util.HashMap; - /** * Represents a particular type of member. */ @@ -36,24 +32,7 @@ public enum MemberType { CLASS, FIELD, - METHOD; - - private static HashMap index; - - MemberType() { - addToIndex(); - } - - private void addToIndex() { - if (index == null) { - index = new HashMap<>(); - } - index.put(name(), this); - } - - public static MemberType fromString(String name) { - Preconditions.checkArgument(index.containsKey(name), "Invalid key for MemberType"); - return index.get(name); - } + METHOD, + ARG } diff --git a/src/main/java/blue/lapis/nocturne/util/OperatingSystem.java b/src/main/java/blue/lapis/nocturne/util/OperatingSystem.java index cd6c5ea..81c8df9 100644 --- a/src/main/java/blue/lapis/nocturne/util/OperatingSystem.java +++ b/src/main/java/blue/lapis/nocturne/util/OperatingSystem.java @@ -33,7 +33,28 @@ public enum OperatingSystem implements Predicate { OSX("mac"), - LINUX("nix", "nux"), + LINUX("nix", "nux") { + private final String home = System.getenv().getOrDefault("HOME", System.getProperty("user.home", "~")); + + private final String dataHome = System.getenv().getOrDefault("XDG_DATA_HOME", home + "/.local/share"); + private final String configHome = System.getenv().getOrDefault("XDG_CONFIG_HOME", home + "/.config"); + private final String cacheHome = System.getenv().getOrDefault("XDG_CACHE_HOME", home + "/.cache"); + + @Override + public String getConfigFolder() { + return configHome; + } + + @Override + public String getDataFolder() { + return dataHome; + } + + @Override + public String getCacheFolder() { + return cacheHome; + } + }, WINDOWS("win"), UNKNOWN; @@ -53,18 +74,57 @@ public boolean test(String s) { return false; } - public String getAppDataFolder() { + /** + * Returns the path to the config home, as defined by the XDG Base Directory specification. + * + *

The config home defines the base directory relative to which user specific configuration + * files should be stored. On Linux, this can be modified by the environment variable + * {@code $XDG_CONFIG_HOME}. On OS X, this is ~/Library/Application Support. On Windows, + * this is %APPDATA%. + * + * @return The path to the config home, as defined by the XDG Base Directory specification. + */ + public String getConfigFolder() { switch (this) { case OSX: return System.getProperty("user.home") + "/Library/Application Support"; case WINDOWS: return System.getenv("APPDATA"); case LINUX: + throw new AssertionError(); case UNKNOWN: default: return System.getProperty("user.home"); } } + + /** + * Returns the path to the data home, as defined by the XDG Base Directory specification. + * + *

The data home defines the base directory relative to which user specific data + * files should be stored. On Linux, this can be modified by the environment variable + * {@code $XDG_DATA_HOME}. On other operating systems, this method is equivalent to + * {@code getConfigFolder()}. + * + * @return The path to the data home, as defined by the XDG Base Directory specification. + */ + public String getDataFolder() { + return getConfigFolder(); + } + + /** + * Returns the path to the cache home, as defined by the XDG Base Directory specification. + * + *

The cache home defines the base directory relative to which user specific + * non-essential data files should be stored. On Linux, this can be modified + * by the environment variable {@code $XDG_CACHE_HOME}. On other operating systems, + * this method is equivalent to {@code getConfigFolder()}. + * + * @return The path to the cache home, as defined by the XDG Base Directory specification. + */ + public String getCacheFolder() { + return getConfigFolder(); + } /** * Gets the operating system currently running on the user's system. diff --git a/src/main/java/blue/lapis/nocturne/util/helper/HierarchyHelper.java b/src/main/java/blue/lapis/nocturne/util/helper/HierarchyHelper.java index a1e76b6..e98e611 100644 --- a/src/main/java/blue/lapis/nocturne/util/helper/HierarchyHelper.java +++ b/src/main/java/blue/lapis/nocturne/util/helper/HierarchyHelper.java @@ -31,6 +31,7 @@ import blue.lapis.nocturne.processor.index.model.IndexedClass; import blue.lapis.nocturne.processor.index.model.IndexedMethod; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; import java.util.Set; import java.util.stream.Collectors; @@ -40,7 +41,7 @@ */ public final class HierarchyHelper { - public static Set getClassesInHierarchy(String className, IndexedMethod.Signature sig) { + public static Set getClassesInHierarchy(String className, MethodSignature sig) { checkState(INDEXED_CLASSES.containsKey(className), "Class \"" + className + "\" is not indexed"); IndexedClass clazz = INDEXED_CLASSES.get(className); diff --git a/src/main/java/blue/lapis/nocturne/util/helper/MappingsHelper.java b/src/main/java/blue/lapis/nocturne/util/helper/MappingsHelper.java index 5529631..ce804e6 100644 --- a/src/main/java/blue/lapis/nocturne/util/helper/MappingsHelper.java +++ b/src/main/java/blue/lapis/nocturne/util/helper/MappingsHelper.java @@ -29,97 +29,169 @@ import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_PATTERN; import blue.lapis.nocturne.Main; -import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; import blue.lapis.nocturne.mapping.MappingContext; import blue.lapis.nocturne.mapping.model.ClassMapping; import blue.lapis.nocturne.mapping.model.FieldMapping; import blue.lapis.nocturne.mapping.model.InnerClassMapping; import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.mapping.model.MethodParameterMapping; import blue.lapis.nocturne.mapping.model.TopLevelClassMapping; +import blue.lapis.nocturne.processor.index.model.IndexedClass; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; /** * Static utility class for assisting with mapping retrieval and creation. */ public final class MappingsHelper { - public static void genClassMapping(MappingContext context, String obf, String deobf, boolean updateClassViews) { + public static ClassMapping genClassMapping(MappingContext context, String obf, String deobf, + boolean updateClassViews) { if (!Main.getLoadedJar().getClass(obf).isPresent()) { Main.getLogger().warning("Discovered mapping for non-existent class \"" + obf + "\" - ignoring"); - return; + return null; } else if (!StringHelper.isJavaClassIdentifier(obf) || !StringHelper.isJavaClassIdentifier(deobf)) { Main.getLogger().warning("Discovered class mapping with illegal name - ignoring"); - return; + return null; } if (obf.contains(INNER_CLASS_SEPARATOR_CHAR + "")) { String[] obfSplit = INNER_CLASS_SEPARATOR_PATTERN.split(obf); String[] deobfSplit = INNER_CLASS_SEPARATOR_PATTERN.split(deobf); if (obfSplit.length != deobfSplit.length) { // non-inner mapped to inner or vice versa - Main.getLogger().warning("Unsupported mapping: " + obf + " <-> " + deobf); - return; // ignore it + Main.getLogger().warning("Unsupported mapping: " + obf + " -> " + deobf); + return null; // ignore it } // get the direct parent class to this inner class ClassMapping parent = getOrCreateClassMapping(context, obf.substring(0, obf.lastIndexOf(INNER_CLASS_SEPARATOR_CHAR))); + // atomic validation pass + ClassMapping next = parent; + for (int i = deobfSplit.length - 2; i >= 0; i--) { + if (!next.getObfuscatedName().equals(next.getDeobfuscatedName()) + && !next.getDeobfuscatedName().equals(deobfSplit[i])) { + Main.getLogger().warning("Nonsense mapping " + obf + " -> " + deobf + + " - conflicts with outer class mapping. Ignoring..."); + return null; + } + if (next instanceof InnerClassMapping) { + next = ((InnerClassMapping) next).getParent(); + } + } + + // application pass + next = parent; + for (int i = deobfSplit.length - 2; i >= 0; i--) { + if (next.getObfuscatedName().equals(next.getDeobfuscatedName())) { + next.setDeobfuscatedName(deobfSplit[i]); + } + if (next instanceof InnerClassMapping) { + next = ((InnerClassMapping) next).getParent(); + } + } + String baseObfName = obfSplit[obfSplit.length - 1]; String baseDeobfname = deobfSplit[deobfSplit.length - 1]; if (parent.getInnerClassMappings().containsKey(baseObfName)) { - parent.getInnerClassMappings().get(baseObfName).setDeobfuscatedName(baseDeobfname); + InnerClassMapping mapping = parent.getInnerClassMappings().get(baseObfName); + mapping.setDeobfuscatedName(baseDeobfname); + return mapping; } else { - new InnerClassMapping(parent, baseObfName, baseDeobfname); + return new InnerClassMapping(parent, baseObfName, baseDeobfname); } } else { if (context.getMappings().containsKey(obf)) { - context.getMappings().get(obf).setDeobfuscatedName(deobf); + TopLevelClassMapping mapping = context.getMappings().get(obf); + mapping.setDeobfuscatedName(deobf); + return mapping; } else { - context.addMapping(new TopLevelClassMapping(context, obf, deobf), updateClassViews); + TopLevelClassMapping mapping = new TopLevelClassMapping(context, obf, deobf); + context.addMapping(mapping, updateClassViews); + return mapping; } } } - public static void genFieldMapping(MappingContext context, String owningClass, String obf, String deobf) { + public static FieldMapping genFieldMapping(MappingContext context, String owningClass, final FieldSignature sig, + String deobf) { if (!Main.getLoadedJar().getClass(owningClass).isPresent()) { Main.getLogger().warning("Discovered mapping for field in non-existent class \"" + owningClass + "\" - ignoring"); - return; - } else if (!StringHelper.isJavaIdentifier(obf) || !StringHelper.isJavaIdentifier(deobf)) { + return null; + } else if (!StringHelper.isJavaIdentifier(sig.getName()) || !StringHelper.isJavaIdentifier(deobf)) { Main.getLogger().warning("Discovered field mapping with illegal name - ignoring"); - return; + return null; } ClassMapping parent = getOrCreateClassMapping(context, owningClass); - if (parent.getFieldMappings().containsKey(obf)) { - parent.getFieldMappings().get(obf).setDeobfuscatedName(deobf); + if (parent.getFieldMappings().containsKey(sig)) { + final FieldMapping fieldMapping = parent.getFieldMappings().get(sig); + fieldMapping.setDeobfuscatedName(deobf); + return fieldMapping; } else { - new FieldMapping(parent, obf, deobf, null); + FieldSignature finalSig = sig; + if (sig.getType() == null) { + List sigList = IndexedClass.INDEXED_CLASSES.get(owningClass).getFields().keySet() + .stream().filter(s -> s.getName().equals(sig.getName())).collect(Collectors.toList()); + if (sigList.size() > 1) { + Main.getLogger().warning("Discovered ambiguous field mapping! Ignoring..."); + return null; + } else if (sigList.size() == 0) { + Main.getLogger().warning("Discovered field mapping for non-existent field - ignoring..."); + return null; + } + finalSig = sigList.get(0); + } + return new FieldMapping(parent, finalSig, deobf); } } - public static void genMethodMapping(MappingContext context, String owningClass, String obf, String deobf, - String descriptor) { + public static MethodMapping genMethodMapping(MappingContext context, String owningClass, MethodSignature sig, + String deobf, boolean acceptInitializer) { if (!Main.getLoadedJar().getClass(owningClass).isPresent()) { Main.getLogger().warning("Discovered mapping for method in non-existent class \"" + owningClass + "\" - ignoring"); - return; - } else if (!StringHelper.isJavaIdentifier(obf) || !StringHelper.isJavaIdentifier(deobf)) { + return null; + } else if (!(sig.getName().equals("") && acceptInitializer && sig.getName().equals(deobf)) + && (!StringHelper.isJavaIdentifier(sig.getName()) || !StringHelper.isJavaIdentifier(deobf))) { Main.getLogger().warning("Discovered method mapping with illegal name - ignoring"); - return; + return null; } ClassMapping parent = getOrCreateClassMapping(context, owningClass); - if (parent.getMethodMappings().containsKey(obf)) { - parent.getMethodMappings().get(obf).setDeobfuscatedName(deobf); + if (parent.getMethodMappings().containsKey(sig)) { + final MethodMapping methodMapping = parent.getMethodMappings().get(sig); + methodMapping.setDeobfuscatedName(deobf); + return methodMapping; + } else { + return new MethodMapping(parent, sig, deobf); + } + } + + public static void genArgumentMapping(MappingContext context, MethodMapping methodMapping, int index, + String deobf) { + if (!StringHelper.isJavaIdentifier(deobf)) { + Main.getLogger().warning("Discovered argument mapping with illegal name - ignoring"); + return; + } + + Optional mapping = methodMapping.getParamMappings().values().stream() + .filter(argumentMapping -> argumentMapping.getIndex() == index).findFirst(); + if (mapping.isPresent()) { + mapping.get().setDeobfuscatedName(deobf); } else { - new MethodMapping(parent, obf, deobf, MethodDescriptor.fromString(descriptor)); + new MethodParameterMapping(methodMapping, index, deobf, true); } } private static Optional getClassMapping(MappingContext context, String qualifiedName, - boolean create) { + boolean create) { String[] arr = INNER_CLASS_SEPARATOR_PATTERN.split(qualifiedName); ClassMapping mapping = context.getMappings().get(arr[0]); diff --git a/src/main/java/blue/lapis/nocturne/util/helper/PropertiesHelper.java b/src/main/java/blue/lapis/nocturne/util/helper/PropertiesHelper.java index 97d0d68..09e9ed8 100644 --- a/src/main/java/blue/lapis/nocturne/util/helper/PropertiesHelper.java +++ b/src/main/java/blue/lapis/nocturne/util/helper/PropertiesHelper.java @@ -89,9 +89,9 @@ public void store() throws IOException { } private File getNocturneDirectory() { - String appdata = OperatingSystem.getOs().getAppDataFolder(); + String appdata = OperatingSystem.getOs().getConfigFolder(); if (OperatingSystem.getOs() == OperatingSystem.LINUX) { - return new File(appdata, ".config" + File.separator + "nocturne"); + return new File(appdata, "nocturne"); // to maintain compatibility with earlier Nocturne versions } else { return new File(appdata, "Nocturne"); } @@ -104,6 +104,8 @@ public static class Key { public static final Key LOCALE = new Key("locale", "en_US"); public static final Key LAST_JAR_DIRECTORY = new Key("lastJarDir", ""); public static final Key LAST_MAPPINGS_DIRECTORY = new Key("lastMappingsDir", ""); + public static final Key LAST_MAPPING_LOAD_FORMAT = new Key("lastMappingLoadFormat", ""); + public static final Key LAST_MAPPING_SAVE_FORMAT = new Key("lastMappingSaveFormat", ""); private final String key; private final String defaultValue; diff --git a/src/main/resources/fxml/CodeTab.fxml b/src/main/resources/fxml/CodeTab.fxml index b513896..6d76f37 100644 --- a/src/main/resources/fxml/CodeTab.fxml +++ b/src/main/resources/fxml/CodeTab.fxml @@ -33,34 +33,32 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/main.fxml b/src/main/resources/fxml/main.fxml index 6248acb..1dd08d8 100644 --- a/src/main/resources/fxml/main.fxml +++ b/src/main/resources/fxml/main.fxml @@ -51,6 +51,8 @@ +

+ text="العربية" id="langRadio-ar_SA"> + + + + diff --git a/src/main/resources/lang/ar_SA.properties b/src/main/resources/lang/ar_SA.properties new file mode 100644 index 0000000..d5ff7b4 --- /dev/null +++ b/src/main/resources/lang/ar_SA.properties @@ -0,0 +1,79 @@ +#X-Generator: crowdin.com +menu.file=_\u0645\u0644\u0641 +menu.file.open_jar=_\u0627\u0641\u062a\u062d JAR +menu.file.close_jar=_\u0623\u063a\u0644\u0650\u0642 JAR \u0627\u0644\u062d\u0627\u0644\u064a +menu.file.load_mappings=_\u062a\u062d\u0645\u064a\u0644 \u0631\u0633\u0645 \u0627\u0644\u062e\u0631\u0627\u0626\u0637 +menu.file.merge_mappings=Load and _Merge Mappings +menu.file.save_mappings=_\u062d\u0641\u0638 \u0631\u0633\u0645 \u0627\u0644\u062e\u0631\u0627\u0626\u0637 +menu.file.save_mappings_as=\u062d\u0641\u0638 \u0631\u0633\u0645 \u0627\u0644\u062e\u0631\u0627\u0626\u0637 _\u0628\u0634\u0643\u0644 +menu.file.close=\u0623\u063a\u0644\u0650\u0642 + +menu.edit=_\u062a\u0639\u062f\u064a\u0644 +menu.file.reset_mappings=_Reset All Mappings + +menu.language=_\u0627\u0644\u0644\u063a\u0629 + +menu.help=_\u0645\u0633\u0627\u0639\u062f\u0629 +menu.help.about=_\u062d\u0648\u0644 + +classes.obfuscated=Obfuscated Classes +classes.deobfuscated=Deobfuscated Classes + +codetab.identifier=Identifier Info +codetab.identifier.field=Field +codetab.identifier.type=Type +codetab.identifier.method=Method +codetab.identifier.descriptor=Descriptor +codetab.identifier.class=Class +codetab.identifier.param=Parameter + +member.contextmenu.rename=Rename +member.contextmenu.reset=Reset +member.contextmenu.toggleDeobf=Toggle Deobfuscated +member.contextmenu.jumpToDef=Jump to Defining Class + +about.title=About Nocturne +about.copyright=Copyright +about.description=A graphical tool for creation of Java deobfuscation mappings. +about.license=Nocturne is open source software available under the MIT License. +about.github=The source code is available on GitHub. + +filechooser.type_jar=JAR Files +filechooser.type_srg=SRG Files +filechooser.type_jam=JAM Files +filechooser.type_enigma=Enigma Files +filechooser.type_all=All Files +filechooser.open_jar=Select JAR File +filechooser.open_mapping=Select Mapping File +filechooser.save_mapping=Select Destination File +filechooser.dirty.title=Save? +filechooser.dirty.content=Would you like to save the current mappings? +filechooser.no_extension.title=Invalid extension filter +filechooser.no_extension=Invalid extension filter selected! + +exception.title=Aw, rats\! +exception.header=Something has broken inside Nocturne\! \:( +exception.dialog1=You may click "OK" to continue work within the program, or "Close" to exit. +exception.dialog2=You will lose all unsaved work if you exit\! +exception.dialog3=If you believe this to be a bug, please report it here\: +exception.dialog4=Below is a stack trace of the uncaught exception\: + +jarload.invalid=Failed to read JAR file\! (Maybe it's invalid?) +jarload.empty=No class entries were found in the selected JAR file. Not loading. + +rename.dupe.title=Duplicate member name +rename.dupe.content=A member with the specified name already exists\! +rename.dupe.content.hierarchy=A member with the specified name already exists in this method's hierarchy\! + +rename.illegal.title=Illegal member name +rename.illegal.content=The provided name is not a legal Java identifier. + +dialog.load_jar.title=Loading JAR +dialog.load_jar.content=Loading JAR file, please wait... + +dialog.decompile.title=Decompiling class +dialog.decompile.content=Decompiling class file, please wait... + +dialog.restart.title=Restart required +dialog.restart.content=You must restart Nocturne for this change to take effect. + diff --git a/src/main/resources/lang/de_DE.properties b/src/main/resources/lang/de_DE.properties index db6a2b5..320bb90 100644 --- a/src/main/resources/lang/de_DE.properties +++ b/src/main/resources/lang/de_DE.properties @@ -3,6 +3,7 @@ menu.file=_Datei menu.file.open_jar=_JAR \u00f6ffnen menu.file.close_jar=_Aktuelle JAR schlie\u00dfen menu.file.load_mappings=Zuordnungen _laden +menu.file.merge_mappings=Zuordnungen laden und _vereinigen menu.file.save_mappings=Zuordnungen _speichern menu.file.save_mappings_as=Zuordnungen speichern _Als menu.file.close=Schlie\u00dfen @@ -24,9 +25,11 @@ codetab.identifier.type=Typ codetab.identifier.method=Methode codetab.identifier.descriptor=Deskriptor codetab.identifier.class=Klasse +codetab.identifier.param=Parameter member.contextmenu.rename=Umbenennen member.contextmenu.reset=Zur\u00fccksetzen +member.contextmenu.toggleDeobf=Entschleierte umschalten member.contextmenu.jumpToDef=Springen zu definieren-Klasse about.title=\u00dcber Nocturne @@ -37,6 +40,7 @@ about.github=Der Quellcode ist auf GitHub verf\u00fcgbar. filechooser.type_jar=JAR Dateien filechooser.type_srg=SRG Dateien +filechooser.type_jam=JAM Dateien filechooser.type_enigma=Enigma Dateien filechooser.type_all=Alle Dateien filechooser.open_jar=JAR Datei ausw\u00e4hlen @@ -44,6 +48,8 @@ filechooser.open_mapping=W\u00e4hle Zuordnungsdatei filechooser.save_mapping=Zieldatei ausw\u00e4hlen filechooser.dirty.title=Speichern? filechooser.dirty.content=M\u00f6chtest du die gegenw\u00e4rtig Zuordnungen speichern? +filechooser.no_extension.title=Invalid extension filter +filechooser.no_extension=Invalid extension filter selected! exception.title=Och N\u00f6\! exception.header=Etwas in Nocturne is kaputt\! \:( diff --git a/src/main/resources/lang/en_US.properties b/src/main/resources/lang/en_US.properties index 9454894..15f066f 100644 --- a/src/main/resources/lang/en_US.properties +++ b/src/main/resources/lang/en_US.properties @@ -2,6 +2,7 @@ menu.file=_File menu.file.open_jar=_Open JAR menu.file.close_jar=_Close Current JAR menu.file.load_mappings=_Load Mappings +menu.file.merge_mappings=Load and _Merge Mappings menu.file.save_mappings=_Save Mappings menu.file.save_mappings_as=Save Mappings _As menu.file.close=Close @@ -23,9 +24,11 @@ codetab.identifier.type=Type codetab.identifier.method=Method codetab.identifier.descriptor=Descriptor codetab.identifier.class=Class +codetab.identifier.param=Parameter member.contextmenu.rename=Rename member.contextmenu.reset=Reset +member.contextmenu.toggleDeobf=Toggle Deobfuscated member.contextmenu.jumpToDef=Jump to Defining Class about.title=About Nocturne @@ -36,6 +39,7 @@ about.github=The source code is available on GitHub. filechooser.type_jar=JAR Files filechooser.type_srg=SRG Files +filechooser.type_jam=JAM Files filechooser.type_enigma=Enigma Files filechooser.type_all=All Files filechooser.open_jar=Select JAR File @@ -43,6 +47,8 @@ filechooser.open_mapping=Select Mapping File filechooser.save_mapping=Select Destination File filechooser.dirty.title=Save? filechooser.dirty.content=Would you like to save the current mappings? +filechooser.no_extension.title=Invalid extension filter +filechooser.no_extension=Invalid extension filter selected! exception.title=Aw, rats! exception.header=Something has broken inside Nocturne! :( diff --git a/src/main/resources/lang/es_ES.properties b/src/main/resources/lang/es_ES.properties new file mode 100644 index 0000000..52fd77d --- /dev/null +++ b/src/main/resources/lang/es_ES.properties @@ -0,0 +1,79 @@ +#X-Generator: crowdin.com +menu.file=_Archivo +menu.file.open_jar=_Abrir JAR +menu.file.close_jar=_Cerrar este JAR +menu.file.load_mappings=_Cargar mapeado +menu.file.merge_mappings=Load and _Merge Mappings +menu.file.save_mappings=_Guardar mapeado +menu.file.save_mappings_as=Guardar mapeado _como +menu.file.close=Cerrar + +menu.edit=_Editar +menu.file.reset_mappings=_Reiniciar todos los mapeados + +menu.language=_Idioma + +menu.help=_Ayuda +menu.help.about=_Acerca de + +classes.obfuscated=Clases Ofuscadas +classes.deobfuscated=Clases Desofuscadas + +codetab.identifier=Informaci\u00f3n del identificador +codetab.identifier.field=Campo +codetab.identifier.type=Tipo +codetab.identifier.method=M\u00e9todo +codetab.identifier.descriptor=Descriptor +codetab.identifier.class=Clase +codetab.identifier.param=Parameter + +member.contextmenu.rename=Renombrar +member.contextmenu.reset=Reiniciar +member.contextmenu.toggleDeobf=Toggle Deobfuscated +member.contextmenu.jumpToDef=Saltar a clase en definici\u00f3n + +about.title=Acerca de Nocturne +about.copyright=Copyright +about.description=Una herramienta gr\u00e1fica para la creaci\u00f3n de mapeos de deofuscaci\u00f3n de Java. +about.license=Nocturne es un software libre disponible bajo la licencia MIT. +about.github=El c\u00f3digo fuente est\u00e1 disponible en GitHub. + +filechooser.type_jar=Archivos JAR +filechooser.type_srg=Archivos SRG +filechooser.type_jam=JAM Files +filechooser.type_enigma=Archivos Enigma +filechooser.type_all=Todos los archivos +filechooser.open_jar=Seleccionar archivo JAR +filechooser.open_mapping=Seleccionar archivo de mapeado +filechooser.save_mapping=Seleccionar archivo de destino +filechooser.dirty.title=\u00bfGuardar? +filechooser.dirty.content=\u00bfQuieres guardar el mapeado actual? +filechooser.no_extension.title=Invalid extension filter +filechooser.no_extension=Invalid extension filter selected! + +exception.title=\u00a1Oh, demonios\! +exception.header=\u00a1Algo de Nocturne ha fallado\! \:( +exception.dialog1=Puedes hacer clic en "Aceptar" para continuar con el programa o en "Cerrar" para salir. +exception.dialog2=\u00a1Perder\u00e1s todo tu trabajo si sales\! +exception.dialog3=Si crees que esto es un error, inf\u00f3rmanos de ello aqu\u00ed\: +exception.dialog4=A continuaci\u00f3n tienes unas cuantas excepciones no capturadas\: + +jarload.invalid=\u00a1No se ha podido leer el archivo JAR\! (\u00bftal vez no sea v\u00e1lido?) +jarload.empty=No se han encontrado entradas de clases en este archivo JAR. No se puede cargar. + +rename.dupe.title=Nombre de miembro duplicado +rename.dupe.content=\u00a1Ya existe un miembro con ese nombre\! +rename.dupe.content.hierarchy=\u00a1Ya existe un miembro con ese nombre en la jerarqu\u00eda de este m\u00e9todo\! + +rename.illegal.title=El nombre del miembro no es v\u00e1lido +rename.illegal.content=El nombre proporcionado no es un identificador Java v\u00e1lido. + +dialog.load_jar.title=Cargando JAR +dialog.load_jar.content=Cargando archivo JAR. Por favor, espera... + +dialog.decompile.title=Descompilando clase +dialog.decompile.content=Descompilado archivo de clase. Por favor, espera... + +dialog.restart.title=Es necesario reiniciar +dialog.restart.content=Tienes que reiniciar Nocturne para que este cambio surta efecto. + diff --git a/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/EnigmaReaderTest.java b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/EnigmaReaderTest.java new file mode 100644 index 0000000..202ace2 --- /dev/null +++ b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/EnigmaReaderTest.java @@ -0,0 +1,101 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.test.mapping.io.reader; + +import static blue.lapis.nocturne.test.mapping.io.reader.ReaderTestHelper.loadMain; + +import blue.lapis.nocturne.mapping.io.reader.EnigmaReader; +import blue.lapis.nocturne.mapping.io.reader.SrgReader; + +import jdk.nashorn.api.scripting.URLReader; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.IOException; + +/** + * Unit tests related to the {@link SrgReader}. + */ +public class EnigmaReaderTest { + + private static ReaderTestHelper helper; + + @BeforeClass + public static void initialize() throws IOException { + loadMain(); + EnigmaReader reader + = new EnigmaReader(new BufferedReader(new URLReader( + ClassLoader.getSystemResource("mappings/example.eng")))); + helper = new ReaderTestHelper(reader.read()); + } + + @Test + public void classTest() { + helper.classTest(); + } + + @Test + public void innerClassTest() { + helper.innerClassTest(); + } + + @Test + public void innerClassWithoutParentMappingTest() { + helper.innerClassWithoutParentMappingTest(); + } + + @Test + public void nestedInnerClassWithoutParentMappingTest() { + helper.nestedInnerClassWithoutParentMappingTest(); + } + + @Test + public void fieldTest() { + helper.fieldTest(); + } + + @Test + public void fieldInnerClassTest() { + helper.fieldInnerClassTest(); + } + + @Test + public void fieldNestedInnerClassTest() { + helper.fieldNestedInnerClassTest(); + } + + @Test + public void methodTest() { + helper.methodTest(); + } + + @Test + public void partialDeobfuscationTest() { + helper.partialDeobfuscationTest(); + } + +} diff --git a/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/JamReaderTest.java b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/JamReaderTest.java new file mode 100644 index 0000000..ad61262 --- /dev/null +++ b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/JamReaderTest.java @@ -0,0 +1,101 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.test.mapping.io.reader; + +import static blue.lapis.nocturne.test.mapping.io.reader.ReaderTestHelper.loadMain; + +import blue.lapis.nocturne.mapping.io.reader.JamReader; +import blue.lapis.nocturne.mapping.io.reader.SrgReader; + +import jdk.nashorn.api.scripting.URLReader; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.IOException; + +/** + * Unit tests related to the {@link SrgReader}. + */ +public class JamReaderTest { + + private static ReaderTestHelper helper; + + @BeforeClass + public static void initialize() throws IOException { + loadMain(); + JamReader reader + = new JamReader(new BufferedReader(new URLReader( + ClassLoader.getSystemResource("mappings/example.jam")))); + helper = new ReaderTestHelper(reader.read()); + } + + @Test + public void classTest() { + helper.classTest(); + } + + @Test + public void innerClassTest() { + helper.innerClassTest(); + } + + @Test + public void innerClassWithoutParentMappingTest() { + helper.innerClassWithoutParentMappingTest(); + } + + @Test + public void nestedInnerClassWithoutParentMappingTest() { + helper.nestedInnerClassWithoutParentMappingTest(); + } + + @Test + public void fieldTest() { + helper.fieldTest(); + } + + @Test + public void fieldInnerClassTest() { + helper.fieldInnerClassTest(); + } + + @Test + public void fieldNestedInnerClassTest() { + helper.fieldNestedInnerClassTest(); + } + + @Test + public void methodTest() { + helper.methodTest(); + } + + @Test + public void partialDeobfuscationTest() { + helper.partialDeobfuscationTest(); + } + +} diff --git a/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/ReaderTestHelper.java b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/ReaderTestHelper.java new file mode 100644 index 0000000..dcaacdf --- /dev/null +++ b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/ReaderTestHelper.java @@ -0,0 +1,191 @@ +/* + * Nocturne + * Copyright (c) 2015-2016, Lapis + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package blue.lapis.nocturne.test.mapping.io.reader; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import blue.lapis.nocturne.Main; +import blue.lapis.nocturne.jar.io.JarLoader; +import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; +import blue.lapis.nocturne.jar.model.attribute.Primitive; +import blue.lapis.nocturne.jar.model.attribute.Type; +import blue.lapis.nocturne.mapping.MappingContext; +import blue.lapis.nocturne.mapping.io.reader.SrgReader; +import blue.lapis.nocturne.mapping.model.ClassMapping; +import blue.lapis.nocturne.mapping.model.FieldMapping; +import blue.lapis.nocturne.mapping.model.InnerClassMapping; +import blue.lapis.nocturne.mapping.model.MethodMapping; +import blue.lapis.nocturne.processor.index.model.signature.FieldSignature; +import blue.lapis.nocturne.processor.index.model.signature.MethodSignature; + +import java.io.IOException; + +/** + * Unit tests related to the {@link SrgReader}. + */ +class ReaderTestHelper { + + private static final String EXAMPLE_PACKAGE = "com/example/project"; + + private MappingContext mappings; + + ReaderTestHelper(MappingContext mappings) { + this.mappings = mappings; + } + + static void loadMain() throws IOException { + new Main(true); + Main.setLoadedJar(JarLoader.loadJar("test.jar", ReaderTestHelper.class.getResourceAsStream("/test.jar"))); + } + + void classTest() { + assertTrue(mappings.getMappings().containsKey("a")); + ClassMapping mapping = mappings.getMappings().get("a"); + assertEquals("a", mapping.getObfuscatedName()); + assertEquals(EXAMPLE_PACKAGE + "/Example", mapping.getDeobfuscatedName()); + } + + void innerClassTest() { + assertTrue(mappings.getMappings().containsKey("a")); + ClassMapping mapping = mappings.getMappings().get("a"); + + assertTrue(mapping.getInnerClassMappings().containsKey("b")); + InnerClassMapping inner = mapping.getInnerClassMappings().get("b"); + + assertEquals("b", inner.getObfuscatedName()); + assertEquals("Inner", inner.getDeobfuscatedName()); + assertEquals(EXAMPLE_PACKAGE + "/Example$Inner", inner.getFullDeobfuscatedName()); + } + + void innerClassWithoutParentMappingTest() { + assertTrue(mappings.getMappings().containsKey("b")); + ClassMapping mapping = mappings.getMappings().get("b"); + + assertTrue(mapping.getInnerClassMappings().containsKey("a")); + InnerClassMapping inner = mapping.getInnerClassMappings().get("a"); + + assertEquals("a", inner.getObfuscatedName()); + assertEquals("Inner", inner.getDeobfuscatedName()); + assertEquals("b$a", inner.getFullObfuscatedName()); + assertEquals("com/example/project/Another$Inner", inner.getFullDeobfuscatedName()); + } + + void nestedInnerClassWithoutParentMappingTest() { + assertTrue(mappings.getMappings().containsKey("b")); + ClassMapping mapping = mappings.getMappings().get("b"); + + assertTrue(mapping.getInnerClassMappings().containsKey("a")); + InnerClassMapping inner = mapping.getInnerClassMappings().get("a"); + assertTrue(inner.getInnerClassMappings().containsKey("c")); + InnerClassMapping deeper = inner.getInnerClassMappings().get("c"); + + assertEquals("c", deeper.getObfuscatedName()); + assertEquals("Deeper", deeper.getDeobfuscatedName()); + assertEquals("b$a$c", deeper.getFullObfuscatedName()); + assertEquals("com/example/project/Another$Inner$Deeper", deeper.getFullDeobfuscatedName()); + } + + void fieldTest() { + assertTrue(mappings.getMappings().containsKey("a")); + ClassMapping mapping = mappings.getMappings().get("a"); + + FieldSignature aSig = new FieldSignature("a", Type.fromString("I")); + + assertTrue(mapping.getFieldMappings().containsKey(aSig)); + FieldMapping fieldMapping = mapping.getFieldMappings().get(aSig); + assertEquals("a", fieldMapping.getObfuscatedName()); + assertEquals("someField", fieldMapping.getDeobfuscatedName()); + } + + void fieldInnerClassTest() { + assertTrue(mappings.getMappings().containsKey("a")); + assertTrue(mappings.getMappings().get("a").getInnerClassMappings().containsKey("b")); + + ClassMapping mapping = mappings.getMappings().get("a").getInnerClassMappings().get("b"); + + FieldSignature aSig = new FieldSignature("a", Type.fromString("I")); + + assertTrue(mapping.getFieldMappings().containsKey(aSig)); + + FieldMapping fieldMapping = mapping.getFieldMappings().get(aSig); + assertEquals("a", fieldMapping.getObfuscatedName()); + assertEquals("someInnerField", fieldMapping.getDeobfuscatedName()); + } + + void fieldNestedInnerClassTest() { + assertTrue(mappings.getMappings().containsKey("a")); + assertTrue(mappings.getMappings().get("a").getInnerClassMappings().containsKey("b")); + ClassMapping inner = mappings.getMappings().get("a").getInnerClassMappings().get("b"); + assertTrue(inner.getInnerClassMappings().containsKey("c")); + ClassMapping deeper = inner.getInnerClassMappings().get("c"); + + FieldSignature aSig = new FieldSignature("a", Type.fromString("I")); + + assertTrue(deeper.getFieldMappings().containsKey(aSig)); + + FieldMapping fieldMapping = deeper.getFieldMappings().get(aSig); + assertEquals("a", fieldMapping.getObfuscatedName()); + assertEquals("someDeeperField", fieldMapping.getDeobfuscatedName()); + } + + void methodTest() { + assertTrue(mappings.getMappings().containsKey("a")); + ClassMapping mapping = mappings.getMappings().get("a"); + + MethodSignature aSig = new MethodSignature("a", MethodDescriptor.fromString("(ILa;I)La;")); + assertTrue(mapping.getMethodMappings().containsKey(aSig)); + + MethodMapping methodMapping = mapping.getMethodMappings().get(aSig); + assertEquals("a", methodMapping.getObfuscatedName()); + assertEquals("someMethod", methodMapping.getDeobfuscatedName()); + assertArrayEquals( + new Type[]{ + new Type(Primitive.INT, 0), + new Type("a", 0), + new Type(Primitive.INT, 0) + }, + methodMapping.getObfuscatedDescriptor().getParamTypes()); + assertEquals(new Type("a", 0), methodMapping.getObfuscatedDescriptor().getReturnType()); + + MethodDescriptor deobfSig = methodMapping.getDeobfuscatedDescriptor(); + assertArrayEquals( + new Type[]{ + new Type(Primitive.INT, 0), + new Type(EXAMPLE_PACKAGE + "/Example", 0), + new Type(Primitive.INT, 0) + }, + deobfSig.getParamTypes() + ); + assertEquals(new Type(EXAMPLE_PACKAGE + "/Example", 0), deobfSig.getReturnType()); + } + + void partialDeobfuscationTest() { + assertEquals("com/example/project/Example$c", ClassMapping.deobfuscate(mappings, "a$c")); + } + +} diff --git a/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/SrgReaderTest.java b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/SrgReaderTest.java index 4a4501c..c6c7cca 100644 --- a/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/SrgReaderTest.java +++ b/src/test/java/blue/lapis/nocturne/test/mapping/io/reader/SrgReaderTest.java @@ -25,24 +25,10 @@ package blue.lapis.nocturne.test.mapping.io.reader; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import blue.lapis.nocturne.Main; -import blue.lapis.nocturne.jar.io.JarLoader; -import blue.lapis.nocturne.jar.model.attribute.MethodDescriptor; -import blue.lapis.nocturne.jar.model.attribute.Primitive; -import blue.lapis.nocturne.jar.model.attribute.Type; -import blue.lapis.nocturne.mapping.MappingContext; +import static blue.lapis.nocturne.test.mapping.io.reader.ReaderTestHelper.loadMain; + import blue.lapis.nocturne.mapping.io.reader.SrgReader; -import blue.lapis.nocturne.mapping.model.ClassMapping; -import blue.lapis.nocturne.mapping.model.FieldMapping; -import blue.lapis.nocturne.mapping.model.InnerClassMapping; -import blue.lapis.nocturne.mapping.model.MethodMapping; -import javafx.application.Application; -import javafx.stage.Stage; import jdk.nashorn.api.scripting.URLReader; import org.junit.BeforeClass; import org.junit.Test; @@ -55,118 +41,60 @@ */ public class SrgReaderTest { - private static final String EXAMPLE_PACKAGE = "com/example/project"; - - private static MappingContext mappings; - - public static class NonApp extends Application { - @Override - public void start(Stage primaryStage) throws Exception { - } - } + private static ReaderTestHelper helper; @BeforeClass - public static void initialize() throws InterruptedException, IOException { - new Main(true); - Main.setLoadedJar(JarLoader.loadJar("test.jar", SrgReaderTest.class.getResourceAsStream("/test.jar"))); + public static void initialize() throws IOException { + loadMain(); SrgReader reader - = new SrgReader(new BufferedReader(new URLReader(ClassLoader.getSystemResource("example.srg")))); - mappings = reader.read(); + = new SrgReader(new BufferedReader(new URLReader( + ClassLoader.getSystemResource("mappings/example.srg")))); + helper = new ReaderTestHelper(reader.read()); } @Test public void classTest() { - assertTrue(mappings.getMappings().containsKey("a")); - ClassMapping mapping = mappings.getMappings().get("a"); - assertEquals("a", mapping.getObfuscatedName()); - assertEquals(EXAMPLE_PACKAGE + "/Example", mapping.getDeobfuscatedName()); + helper.classTest(); } @Test public void innerClassTest() { - assertTrue(mappings.getMappings().containsKey("a")); - ClassMapping mapping = mappings.getMappings().get("a"); - - assertTrue(mapping.getInnerClassMappings().containsKey("b")); - InnerClassMapping inner = mapping.getInnerClassMappings().get("b"); - - assertEquals("b", inner.getObfuscatedName()); - assertEquals("Inner", inner.getDeobfuscatedName()); - assertEquals(EXAMPLE_PACKAGE + "/Example$Inner", inner.getFullDeobfuscatedName()); + helper.innerClassTest(); } @Test public void innerClassWithoutParentMappingTest() { - assertTrue(mappings.getMappings().containsKey("b")); - ClassMapping mapping = mappings.getMappings().get("b"); - - assertTrue(mapping.getInnerClassMappings().containsKey("a")); - InnerClassMapping inner = mapping.getInnerClassMappings().get("a"); + helper.innerClassWithoutParentMappingTest(); + } - assertEquals("a", inner.getObfuscatedName()); - assertEquals("Inner", inner.getDeobfuscatedName()); - assertEquals("b$a", inner.getFullObfuscatedName()); - assertEquals("b$Inner", inner.getFullDeobfuscatedName()); + @Test + public void nestedInnerClassWithoutParentMappingTest() { + helper.nestedInnerClassWithoutParentMappingTest(); } @Test public void fieldTest() { - assertTrue(mappings.getMappings().containsKey("a")); - ClassMapping mapping = mappings.getMappings().get("a"); - - assertTrue(mapping.getFieldMappings().containsKey("a")); - FieldMapping fieldMapping = mapping.getFieldMappings().get("a"); - assertEquals("a", fieldMapping.getObfuscatedName()); - assertEquals("someField", fieldMapping.getDeobfuscatedName()); + helper.fieldTest(); } @Test public void fieldInnerClassTest() { - assertTrue(mappings.getMappings().containsKey("a")); - assertTrue(mappings.getMappings().get("a").getInnerClassMappings().containsKey("b")); - - ClassMapping mapping = mappings.getMappings().get("a").getInnerClassMappings().get("b"); - assertTrue(mapping.getFieldMappings().containsKey("a")); + helper.fieldInnerClassTest(); + } - FieldMapping fieldMapping = mapping.getFieldMappings().get("a"); - assertEquals("a", fieldMapping.getObfuscatedName()); - assertEquals("someInnerField", fieldMapping.getDeobfuscatedName()); + @Test + public void fieldNestedInnerClassTest() { + helper.fieldNestedInnerClassTest(); } @Test public void methodTest() { - assertTrue(mappings.getMappings().containsKey("a")); - ClassMapping mapping = mappings.getMappings().get("a"); - - assertTrue(mapping.getMethodMappings().containsKey("a(ILa;I)La;")); - - MethodMapping methodMapping = mapping.getMethodMappings().get("a(ILa;I)La;"); - assertEquals("a", methodMapping.getObfuscatedName()); - assertEquals("someMethod", methodMapping.getDeobfuscatedName()); - assertArrayEquals( - new Type[]{ - new Type(Primitive.INT, 0), - new Type("a", 0), - new Type(Primitive.INT, 0) - }, - methodMapping.getObfuscatedDescriptor().getParamTypes()); - assertEquals(new Type("a", 0), methodMapping.getObfuscatedDescriptor().getReturnType()); - - MethodDescriptor deobfSig = methodMapping.getDeobfuscatedDescriptor(); - assertArrayEquals( - new Type[]{ - new Type(Primitive.INT, 0), - new Type(EXAMPLE_PACKAGE + "/Example", 0), - new Type(Primitive.INT, 0) - }, - deobfSig.getParamTypes() - ); - assertEquals(new Type(EXAMPLE_PACKAGE + "/Example", 0), deobfSig.getReturnType()); + helper.methodTest(); } @Test public void partialDeobfuscationTest() { - assertEquals("com/example/project/Example$c", ClassMapping.deobfuscate(mappings, "a$c")); + helper.partialDeobfuscationTest(); } } diff --git a/src/test/resources/mappings/example.eng b/src/test/resources/mappings/example.eng new file mode 100644 index 0000000..7619adc --- /dev/null +++ b/src/test/resources/mappings/example.eng @@ -0,0 +1,10 @@ +CLASS none/a com/example/project/Example + CLASS none/a$b Inner + CLASS none/a$b$c Deeper + FIELD a someDeeperField I + FIELD a someInnerField I + FIELD a someField I + METHOD a someMethod (ILnone/a;I)Lnone/a; +CLASS none/b com/example/project/Another + CLASS none/b$a Inner + CLASS none/b$a$c Deeper diff --git a/src/test/resources/mappings/example.jam b/src/test/resources/mappings/example.jam new file mode 100644 index 0000000..7f61e02 --- /dev/null +++ b/src/test/resources/mappings/example.jam @@ -0,0 +1,8 @@ +CL a com/example/project/Example +CL a$b com/example/project/Example$Inner +CL a$b$c com/example/project/Example$Inner$Deeper +CL b$a$c com/example/project/Another$Inner$Deeper +FD a a I someField +FD a$b a I someInnerField +FD a$b$c a I someDeeperField +MD a a (ILa;I)La; someMethod diff --git a/src/test/resources/example.srg b/src/test/resources/mappings/example.srg similarity index 63% rename from src/test/resources/example.srg rename to src/test/resources/mappings/example.srg index ae539c1..e23269c 100644 --- a/src/test/resources/example.srg +++ b/src/test/resources/mappings/example.srg @@ -1,7 +1,8 @@ -# This is a comment, to check the reader doesn't read them. CL: a com/example/project/Example CL: a$b com/example/project/Example$Inner -CL: b$a com/example/project/Another$Inner +CL: a$b$c com/example/project/Example$Inner$Deeper +CL: b$a$c com/example/project/Another$Inner$Deeper FD: a/a com/example/project/Example/someField FD: a$b/a com/example/project/Example$Inner/someInnerField +FD: a$b$c/a com/example/project/Example$Inner$Deeper/someDeeperField MD: a/a (ILa;I)La; com/example/project/Example/someMethod (ILcom/example/project/Example;I)Lcom/example/project/Example; diff --git a/src/test/resources/test.jar b/src/test/resources/test.jar index 1350c4e..27fb635 100644 Binary files a/src/test/resources/test.jar and b/src/test/resources/test.jar differ