From 47de69a821c6e089b01187e93f4f916aceeeea85 Mon Sep 17 00:00:00 2001 From: NebelNidas Date: Thu, 22 Sep 2022 11:59:53 +0200 Subject: Add initial Mapping-IO export support --- build.gradle | 2 + .../main/java/cuchaz/enigma/gui/GuiController.java | 45 +++++ .../java/cuchaz/enigma/gui/elements/MenuBar.java | 23 +++ .../translation/mapping/serde/MappingFormat.java | 25 ++- .../mapping/serde/MappingIoConverter.java | 126 ++++++++++++++ gradlew.bat | 184 ++++++++++----------- 6 files changed, 304 insertions(+), 101 deletions(-) create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java diff --git a/build.gradle b/build.gradle index 08ccffe8..e2d5edd7 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,8 @@ subprojects { implementation 'com.google.guava:guava:30.1.1-jre' implementation 'com.google.code.gson:gson:2.8.7' + implementation 'net.fabricmc:mapping-io:0.3.0' + testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 91037b00..e7e75669 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -29,6 +29,8 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import com.google.common.collect.Lists; +import net.fabricmc.mappingio.MappingWriter; +import net.fabricmc.mappingio.tree.MemoryMappingTree; import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProfile; @@ -77,6 +79,7 @@ import cuchaz.enigma.translation.mapping.EntryUtil; import cuchaz.enigma.translation.mapping.MappingDelta; import cuchaz.enigma.translation.mapping.ResolutionStrategy; import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.serde.MappingIoConverter; import cuchaz.enigma.translation.mapping.serde.MappingParseException; import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; import cuchaz.enigma.translation.mapping.tree.EntryTree; @@ -213,6 +216,48 @@ public class GuiController implements ClientPacketHandler { }); } + public CompletableFuture saveMappings(Path path, net.fabricmc.mappingio.format.MappingFormat format) { + if (project == null) { + return CompletableFuture.completedFuture(null); + } + + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EntryRemapper mapper = project.getMapper(); + MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + + MappingDelta delta = mapper.takeMappingDelta(); + boolean saveAll = !path.equals(loadedMappingPath); + + switch (format) { + case ENIGMA: + loadedMappingFormat = MappingFormat.ENIGMA_DIRECTORY; + loadedMappingPath = path; + break; + case PROGUARD: + loadedMappingFormat = MappingFormat.PROGUARD; + loadedMappingPath = path; + break; + case SRG: + loadedMappingFormat = MappingFormat.SRG_FILE; + loadedMappingPath = path; + break; + case TINY: + loadedMappingFormat = MappingFormat.TINY_FILE; + loadedMappingPath = path; + break; + case TINY_2: + loadedMappingFormat = MappingFormat.TINY_V2; + loadedMappingPath = path; + break; + } + + MemoryMappingTree mappingTree = MappingIoConverter.toMappingIo(mapper.getObfToDeobf()); + MappingWriter writer = MappingWriter.create(path, format); + mappingTree.accept(writer); + writer.close(); + }); + } + public void closeMappings() { if (project == null) { return; diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 1cfad504..1d4c1ca1 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; @@ -439,6 +440,28 @@ public class MenuBar { saveMappingsAsMenu.add(item); } } + + saveMappingsAsMenu.addSeparator(); + + List writableMappingIoFormats = Arrays.asList( + net.fabricmc.mappingio.format.MappingFormat.ENIGMA, + net.fabricmc.mappingio.format.MappingFormat.TINY_2); + for (net.fabricmc.mappingio.format.MappingFormat format : writableMappingIoFormats) { + JMenuItem item = new JMenuItem(format.name + " (via mapping-io, experimental)"); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (gui.enigmaMappingsFileChooser.getCurrentDirectory() == null) { + gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(UiConfig.getLastSelectedDir())); + } + + if (gui.enigmaMappingsFileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + gui.getController().saveMappings(gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); + saveMappingsItem.setEnabled(true); + UiConfig.setLastSelectedDir(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString()); + } + }); + saveMappingsAsMenu.add(item); + } } private static void prepareDecompilerMenu(JMenu decompilerMenu, Gui gui) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java index 367af3b3..4790fee4 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java @@ -21,21 +21,23 @@ import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer; import cuchaz.enigma.translation.mapping.tree.EntryTree; public enum MappingFormat { - ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE), - ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY), - ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP), - TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()), - TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE), - SRG_FILE(SrgMappingsWriter.INSTANCE, null), - PROGUARD(null, ProguardMappingsReader.INSTANCE), - RECAF(RecafMappingsWriter.INSTANCE, RecafMappingsReader.INSTANCE); + ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE, null), + ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY, net.fabricmc.mappingio.format.MappingFormat.ENIGMA), + ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP, null), + TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader(), net.fabricmc.mappingio.format.MappingFormat.TINY_2), + TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE, net.fabricmc.mappingio.format.MappingFormat.TINY), + SRG_FILE(SrgMappingsWriter.INSTANCE, null, net.fabricmc.mappingio.format.MappingFormat.SRG), + PROGUARD(null, ProguardMappingsReader.INSTANCE, net.fabricmc.mappingio.format.MappingFormat.PROGUARD), + RECAF(RecafMappingsWriter.INSTANCE, RecafMappingsReader.INSTANCE, null); private final MappingsWriter writer; private final MappingsReader reader; + private final net.fabricmc.mappingio.format.MappingFormat mappingIoCounterpart; - MappingFormat(MappingsWriter writer, MappingsReader reader) { + MappingFormat(MappingsWriter writer, MappingsReader reader, net.fabricmc.mappingio.format.MappingFormat mappingIoCounterpart) { this.writer = writer; this.reader = reader; + this.mappingIoCounterpart = mappingIoCounterpart; } public void write(EntryTree mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { @@ -67,4 +69,9 @@ public enum MappingFormat { public MappingsReader getReader() { return reader; } + + @Nullable + public net.fabricmc.mappingio.format.MappingFormat getMappingIoCounterpart() { + return mappingIoCounterpart; + } } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java new file mode 100644 index 00000000..3a864766 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java @@ -0,0 +1,126 @@ +package cuchaz.enigma.translation.mapping.serde; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +import net.fabricmc.mappingio.MappedElementKind; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +public class MappingIoConverter { + public static MemoryMappingTree toMappingIo(EntryTree mappings) { + MemoryMappingTree mappingTree = new MemoryMappingTree(); + mappingTree.visitNamespaces("intermediary", List.of("named")); + + for (EntryTreeNode node : mappings) { + if (node.getEntry() instanceof ClassEntry) { + writeClass(node, mappings, mappingTree); + } + } + + mappingTree.visitEnd(); + return mappingTree; + } + + private static void writeClass(EntryTreeNode classNode, EntryMap oldMappingTree, MemoryMappingTree newMappingTree) { + ClassEntry classEntry = (ClassEntry) classNode.getEntry(); + EntryMapping mapping = oldMappingTree.get(classEntry); + Deque parts = new LinkedList<>(); + + newMappingTree.visitClass(classEntry.getFullName()); + newMappingTree.visitComment(MappedElementKind.CLASS, mapping.javadoc()); + + do { + mapping = oldMappingTree.get(classEntry); + + if (mapping != null && mapping.targetName() != null) { + parts.addFirst(mapping.targetName()); + } else { + parts.addFirst(classEntry.getName()); + } + + classEntry = classEntry.getOuterClass(); + } while (classEntry != null); + + String mappedName = String.join("$", parts); + newMappingTree.visitDstName(MappedElementKind.CLASS, 0, mappedName); + + for (EntryTreeNode child : classNode.getChildNodes()) { + Entry entry = child.getEntry(); + + if (entry instanceof FieldEntry) { + writeField(child, newMappingTree); + } else if (entry instanceof MethodEntry) { + writeMethod(child, newMappingTree); + } + } + } + + private static void writeField(EntryTreeNode fieldNode, MemoryMappingTree mappingTree) { + if (fieldNode.getValue() == null || fieldNode.getValue().equals(EntryMapping.DEFAULT)) { + return; // Shortcut + } + + FieldEntry fieldEntry = ((FieldEntry) fieldNode.getEntry()); + mappingTree.visitField(fieldEntry.getName(), fieldEntry.getDesc().toString()); + + EntryMapping fieldMapping = fieldNode.getValue(); + + if (fieldMapping == null) { + fieldMapping = EntryMapping.DEFAULT; + } + + mappingTree.visitDstName(MappedElementKind.FIELD, 0, fieldMapping.targetName()); + mappingTree.visitComment(MappedElementKind.FIELD, fieldMapping.javadoc()); + } + + private static void writeMethod(EntryTreeNode methodNode, MemoryMappingTree mappingTree) { + MethodEntry methodEntry = ((MethodEntry) methodNode.getEntry()); + mappingTree.visitMethod(methodEntry.getName(), methodEntry.getDesc().toString()); + + EntryMapping methodMapping = methodNode.getValue(); + + if (methodMapping == null) { + methodMapping = EntryMapping.DEFAULT; + } + + mappingTree.visitDstName(MappedElementKind.METHOD, 0, methodMapping.targetName()); + mappingTree.visitComment(MappedElementKind.METHOD, methodMapping.javadoc()); + + for (EntryTreeNode child : methodNode.getChildNodes()) { + Entry entry = child.getEntry(); + + if (entry instanceof LocalVariableEntry) { + writeMethodArg(child, mappingTree); + } + } + } + + private static void writeMethodArg(EntryTreeNode methodArgNode, MemoryMappingTree mappingTree) { + if (methodArgNode.getValue() == null || methodArgNode.getValue().equals(EntryMapping.DEFAULT)) { + return; // Shortcut + } + + LocalVariableEntry methodArgEntry = ((LocalVariableEntry) methodArgNode.getEntry()); + mappingTree.visitMethodArg(-1, methodArgEntry.getIndex(), methodArgEntry.getName()); + + EntryMapping methodArgMapping = methodArgNode.getValue(); + + if (methodArgMapping == null) { + methodArgMapping = EntryMapping.DEFAULT; + } + + mappingTree.visitDstName(MappedElementKind.METHOD_ARG, 0, methodArgMapping.targetName()); + mappingTree.visitComment(MappedElementKind.METHOD_ARG, methodArgMapping.javadoc()); + } +} diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..93e3f59f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@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="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega -- cgit v1.2.3