From 2969043799acc38a0ad5155ec8648c6d0bc9b896 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 16 Apr 2024 18:17:05 -0500 Subject: [PATCH 01/45] https://github.com/FabricMC/Enigma/pull/532 - rai version --- .../main/java/org/quiltmc/enigma/gui/Gui.java | 19 ++- .../org/quiltmc/enigma/gui/GuiController.java | 7 + .../quiltmc/enigma/gui/element/MenuBar.java | 29 +++-- .../enigma/gui/util/ExtensionFileFilter.java | 122 ++++++++++++++++++ .../mapping/serde/MappingFormat.java | 71 ++++++---- .../enigma/impl/translation/FileType.java | 51 ++++++++ enigma/src/main/resources/lang/en_us.json | 1 + 7 files changed, 250 insertions(+), 50 deletions(-) create mode 100644 enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java create mode 100644 enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java index e6ff0830c..ed80c3b86 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java @@ -29,6 +29,7 @@ import org.quiltmc.enigma.gui.panel.EditorPanel; import org.quiltmc.enigma.gui.panel.IdentifierPanel; import org.quiltmc.enigma.gui.renderer.MessageListCellRenderer; +import org.quiltmc.enigma.gui.util.ExtensionFileFilter; import org.quiltmc.enigma.gui.util.GuiUtil; import org.quiltmc.enigma.gui.util.LanguageUtil; import org.quiltmc.enigma.gui.util.ScaleUtil; @@ -95,8 +96,7 @@ public class Gui { private final NotificationManager notificationManager; public final JFileChooser jarFileChooser; - public final JFileChooser tinyMappingsFileChooser; - public final JFileChooser enigmaMappingsFileChooser; + public final JFileChooser mappingsFileChooser; public final JFileChooser exportSourceFileChooser; public final JFileChooser exportJarFileChooser; public final SearchDialog searchDialog; @@ -118,8 +118,7 @@ public Gui(EnigmaProfile profile, Set editableTypes, boolean visib this.splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.centerPanel, this.dockerManager.getRightDock()); this.splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.dockerManager.getLeftDock(), this.splitRight); this.jarFileChooser = new JFileChooser(); - this.tinyMappingsFileChooser = new JFileChooser(); - this.enigmaMappingsFileChooser = new JFileChooser(); + this.mappingsFileChooser = new JFileChooser(); this.exportSourceFileChooser = new JFileChooser(); this.exportJarFileChooser = new JFileChooser(); this.connectionStatusLabel = new JLabel(); @@ -165,10 +164,6 @@ private void setupUi() { this.setupDockers(); this.jarFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - this.tinyMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - - this.enigmaMappingsFileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - this.enigmaMappingsFileChooser.setAcceptAllFileFilterUsed(false); this.exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); this.exportSourceFileChooser.setAcceptAllFileFilterUsed(false); @@ -367,7 +362,7 @@ public void updateAllClasses() { } public void setMappingsFile(Path path) { - this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); + this.mappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); this.updateUiState(); } @@ -491,8 +486,10 @@ public void showDiscardDiag(IntFunction callback, String... options) { } public CompletableFuture saveMapping() { - if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.mainWindow.getFrame()) == JFileChooser.APPROVE_OPTION) { - return this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); + ExtensionFileFilter.setupFileChooser(this.mappingsFileChooser, this.controller.getLoadedMappingFormat()); + + if (this.mappingsFileChooser.getSelectedFile() != null || this.mappingsFileChooser.showSaveDialog(this.mainWindow.getFrame()) == JFileChooser.APPROVE_OPTION) { + return this.controller.saveMappings(ExtensionFileFilter.getSavePath(this.mappingsFileChooser)); } return CompletableFuture.completedFuture(null); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java index 15d285420..5e5db63b2 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java @@ -70,6 +70,7 @@ import org.quiltmc.enigma.util.validation.ValidationContext; import org.tinylog.Logger; +import javax.annotation.Nullable; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import java.awt.Desktop; @@ -611,10 +612,16 @@ public Enigma getEnigma() { return this.enigma; } + @Nullable public StatsGenerator getStatsGenerator() { return this.statsGenerator; } + @Nullable + public MappingFormat getLoadedMappingFormat() { + return loadedMappingFormat; + } + public void createClient(String username, String ip, int port, char[] password) throws IOException { this.client = new IntegratedEnigmaClient(this, ip, port); this.client.connect(); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java index 4c9f9b386..de8b7595e 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java @@ -18,6 +18,7 @@ import org.quiltmc.enigma.gui.dialog.StatsDialog; import org.quiltmc.enigma.gui.dialog.decompiler.DecompilerSettingsDialog; import org.quiltmc.enigma.gui.dialog.keybind.ConfigureKeyBindsDialog; +import org.quiltmc.enigma.gui.util.ExtensionFileFilter; import org.quiltmc.enigma.gui.util.GuiUtil; import org.quiltmc.enigma.gui.util.LanguageUtil; import org.quiltmc.enigma.gui.util.ScaleUtil; @@ -222,8 +223,7 @@ public void updateUiState() { this.jarCloseItem.setEnabled(jarOpen); this.openMappingsItem.setEnabled(jarOpen); - this.saveMappingsItem.setEnabled(jarOpen && this.gui.enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); - this.saveMappingsAsMenu.setEnabled(jarOpen); + this.saveMappingsItem.setEnabled(jarOpen && this.gui.mappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); this.saveMappingsAsMenu.setEnabled(jarOpen); this.closeMappingsItem.setEnabled(jarOpen); this.reloadMappingsItem.setEnabled(jarOpen); this.reloadAllItem.setEnabled(jarOpen); @@ -324,7 +324,7 @@ private void onMaxRecentFilesClicked() { } private void onSaveMappingsClicked() { - this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + this.gui.getController().saveMappings(this.gui.mappingsFileChooser.getSelectedFile().toPath()); } private void openMappingsDiscardPrompt(Runnable then) { @@ -469,10 +469,10 @@ private void onGithubClicked() { } private void onOpenMappingsClicked() { - this.gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); - if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile(); - Config.main().stats.lastSelectedDir.setValue(this.gui.enigmaMappingsFileChooser.getCurrentDirectory().toString(), true); + this.gui.mappingsFileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); + if (this.gui.mappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + File selectedFile = this.gui.mappingsFileChooser.getSelectedFile(); + Config.main().stats.lastSelectedDir.setValue(this.gui.mappingsFileChooser.getCurrentDirectory().toString(), true); MappingFormat format = MappingFormat.parseFromFile(selectedFile.toPath()); if (format.getReader() != null) { @@ -549,15 +549,18 @@ private static void prepareSaveMappingsAsMenu(JMenu saveMappingsAsMenu, JMenuIte if (format.getWriter() != null) { JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (gui.enigmaMappingsFileChooser.getCurrentDirectory() == null) { - gui.enigmaMappingsFileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); + JFileChooser fileChooser = gui.mappingsFileChooser; + ExtensionFileFilter.setupFileChooser(fileChooser, format); + + if (fileChooser.getCurrentDirectory() == null) { + fileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); } - if (gui.enigmaMappingsFileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - gui.getController().saveMappings(gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); + if (fileChooser.showSaveDialog(gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + Path savePath = ExtensionFileFilter.getSavePath(fileChooser); + gui.getController().saveMappings(savePath, format); saveMappingsItem.setEnabled(true); - Config.main().stats.lastSelectedDir.setValue(gui.enigmaMappingsFileChooser.getCurrentDirectory().toString(), true); + Config.main().stats.lastSelectedDir.setValue(fileChooser.getCurrentDirectory().toString()); } }); saveMappingsAsMenu.add(item); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java new file mode 100644 index 000000000..6e1645ed8 --- /dev/null +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java @@ -0,0 +1,122 @@ +package org.quiltmc.enigma.gui.util; + +import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; +import org.quiltmc.enigma.util.I18n; + +import javax.annotation.Nullable; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileFilter; +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.StringJoiner; + +public final class ExtensionFileFilter extends FileFilter { + private final String formatName; + private final List extensions; + + /** + * Constructs an {@code ExtensionFileFilter}. + * + * @param formatName the human-readable name of the file format + * @param extensions the file extensions with their leading dots (e.g. {@code .txt}) + */ + public ExtensionFileFilter(String formatName, List extensions) { + this.formatName = formatName; + this.extensions = extensions; + } + + public List getExtensions() { + return extensions; + } + + @Override + public boolean accept(File f) { + // Always accept directories so the user can see them. + if (f.isDirectory()) { + return true; + } + + for (String extension : extensions) { + if (f.getName().endsWith(extension)) { + return true; + } + } + + return false; + } + + @Override + public String getDescription() { + var joiner = new StringJoiner(", "); + + for (String extension : extensions) { + joiner.add("*" + extension); + } + + return I18n.translateFormatted("menu.file.mappings.file_filter", formatName, joiner.toString()); + } + + /** + * Sets up a file chooser with a mapping format. This method resets the choosable filters, + * and adds and selects a new filter based on the provided mapping format. + * + * @param fileChooser the file chooser to set up + * @param format the mapping format to use. if {@code null}, the file chooser will accept only directories + */ + public static void setupFileChooser(JFileChooser fileChooser, @Nullable MappingFormat format) { + if (format == null) { + format = MappingFormat.ENIGMA_DIRECTORY; + } + + // Remove previous custom filters. + fileChooser.resetChoosableFileFilters(); + + if (format.getFileType().isDirectory()) { + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + } else { + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + String formatName = I18n.translate("mapping_format." + format.name().toLowerCase()); + var filter = new ExtensionFileFilter(formatName, format.getFileType().getExtensions()); + // Add our new filter to the list... + fileChooser.addChoosableFileFilter(filter); + // ...and choose it as the default. + fileChooser.setFileFilter(filter); + } + } + + /** + * Fixes a missing file extension in a save file path when the selected filter + * is an {@code ExtensionFileFilter}. + * + * @param fileChooser the file chooser to check + * @return the fixed path + */ + public static Path getSavePath(JFileChooser fileChooser) { + Path savePath = fileChooser.getSelectedFile().toPath(); + + if (fileChooser.getFileFilter() instanceof ExtensionFileFilter extensionFilter) { + // Check that the file name ends with the extension. + String fileName = savePath.getFileName().toString(); + boolean hasExtension = false; + + for (String extension : extensionFilter.getExtensions()) { + if (fileName.endsWith(extension)) { + hasExtension = true; + break; + } + } + + if (!hasExtension) { + String defaultExtension = extensionFilter.getExtensions().get(0); + // If not, add the extension. + savePath = savePath.resolveSibling(fileName + defaultExtension); + // Store the adjusted file, so that it shows up properly + // the next time this dialog is used. + fileChooser.setSelectedFile(savePath.toFile()); + } + } + + return savePath; + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java index 6d995b4c3..e75b1d913 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java @@ -11,26 +11,32 @@ import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Reader; import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; +import org.quiltmc.enigma.impl.translation.FileType; import javax.annotation.Nullable; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; 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()), - SRG_FILE(SrgMappingsWriter.INSTANCE, null), - PROGUARD(null, ProguardMappingsReader.INSTANCE); + ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE, FileType.ENIGMA_MAPPING), + ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY, FileType.ENIGMA_DIRECTORY), + ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP, FileType.ENIGMA_ZIP), + TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader(), FileType.TINY_V2), + SRG_FILE(SrgMappingsWriter.INSTANCE, null, FileType.SRG), + PROGUARD(null, ProguardMappingsReader.INSTANCE, FileType.PROGUARD); private final MappingsWriter writer; private final MappingsReader reader; + private final FileType fileType; - MappingFormat(MappingsWriter writer, MappingsReader reader) { + MappingFormat(MappingsWriter writer, MappingsReader reader, FileType fileType) { this.writer = writer; this.reader = reader; + this.fileType = fileType; } public void write(EntryTree mappings, Path path, MappingSaveParameters saveParameters) { @@ -90,34 +96,47 @@ public MappingsReader getReader() { return this.reader; } + public FileType getFileType() { + return this.fileType; + } + /** - * Determines the mapping format of the provided file. Checks all formats, and returns {@link #PROGUARD} if none match. + * Determines the mapping format of the provided file. Checks all formats according to their {@link #getFileType()} file extensions. + * If the file is a directory, it will check the first file in the directory. + * Will return {@link #PROGUARD} if no format is found for single files, and {@link #ENIGMA_DIRECTORY} if no format is found for directories. * @param file the file to analyse - * @apiNote Any directory is considered to be the {@link #ENIGMA_DIRECTORY} format. - * Proguard does not have an explicit file extension, so it is the fallback. * @return the mapping format of the file. */ public static MappingFormat parseFromFile(Path file) { if (Files.isDirectory(file)) { - return ENIGMA_DIRECTORY; - } else { - switch (MoreFiles.getFileExtension(file).toLowerCase()) { - case "zip" -> { - return ENIGMA_ZIP; - } - case "mapping", "mappings" -> { - return ENIGMA_FILE; - } - case "tiny" -> { - return TINY_V2; - } - case "tsrg" -> { - return SRG_FILE; + try { + File firstFile = Arrays.stream(Objects.requireNonNull(file.toFile().listFiles())).findFirst().orElseThrow(); + + for (MappingFormat format : values()) { + if (!format.getFileType().isDirectory()) { + continue; + } + + String extension = MoreFiles.getFileExtension(firstFile.toPath()).toLowerCase(); + if (format.fileType.getExtensions().contains(extension)) { + return format; + } } - default -> { - return PROGUARD; + + return ENIGMA_DIRECTORY; + } catch (Exception e) { + return ENIGMA_DIRECTORY; + } + } else { + String extension = MoreFiles.getFileExtension(file).toLowerCase(); + + for (MappingFormat format : values()) { + if (format.fileType.getExtensions().contains(extension)) { + return format; } } } + + return PROGUARD; } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java b/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java new file mode 100644 index 000000000..7d0b3bb85 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java @@ -0,0 +1,51 @@ +package org.quiltmc.enigma.impl.translation; + +import java.util.List; + +/** + * A file type. It can be either a single file with an extension, or a directory. + * + *

If a file type has multiple extensions, the default for saving will be the first one. + */ +public interface FileType { + FileType ENIGMA_MAPPING = new File(List.of("mapping", "mappings")); + FileType ENIGMA_DIRECTORY = new Directory((File) ENIGMA_MAPPING); + FileType ENIGMA_ZIP = new File(List.of("zip")); + FileType PROGUARD = new File(List.of("txt")); + FileType SRG = new File(List.of("tsrg")); + FileType TINY_V2 = new File(List.of("tiny")); + + /** + * Gets all possible extensions for this type of mapping file. + * If {@link #isDirectory()} is {@code true}, this will return the types of mapping allowed inside the directory. + * @return the file extension options + */ + List getExtensions(); + + /** + * {@return whether this file type is a directory} + */ + boolean isDirectory(); + + record Directory(File file) implements FileType { + @Override + public List getExtensions() { + return this.file.getExtensions(); + } + + public boolean isDirectory() { + return true; + } + } + + record File(List extensions) implements FileType { + @Override + public List getExtensions() { + return this.extensions; + } + + public boolean isDirectory() { + return false; + } + } +} diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 005ca5175..08627a084 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -24,6 +24,7 @@ "menu.file.mappings.save_as": "Save Mappings As...", "menu.file.mappings.close": "Close Mappings", "menu.file.mappings.drop": "Drop Invalid Mappings", + "menu.file.mappings.file_filter": "%s (%s)", "menu.file.save.non_writeable": "Format \"%s\" is not writeable!", "menu.file.save.cannot_save": "Cannot save mappings!", "menu.file.open.non_parseable": "Format \"%s\" is not parseable!", From dd1ed5c3256f13b06946fe3b6ecfcefab86c75b4 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 16 Apr 2024 18:23:51 -0500 Subject: [PATCH 02/45] fix ExtensionFileFilter for leading dot change --- .../java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java index 6e1645ed8..d4b202108 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java @@ -19,11 +19,11 @@ public final class ExtensionFileFilter extends FileFilter { * Constructs an {@code ExtensionFileFilter}. * * @param formatName the human-readable name of the file format - * @param extensions the file extensions with their leading dots (e.g. {@code .txt}) + * @param extensions the file extensions with no leading dots */ public ExtensionFileFilter(String formatName, List extensions) { this.formatName = formatName; - this.extensions = extensions; + this.extensions = extensions.stream().map(s -> "." + s).toList(); } public List getExtensions() { From e8c1d9546da16456bdd841c0acc612f7dd8bfbb5 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 16 Apr 2024 18:27:57 -0500 Subject: [PATCH 03/45] checkstyle --- .../main/java/org/quiltmc/enigma/gui/GuiController.java | 2 +- .../main/java/org/quiltmc/enigma/gui/element/MenuBar.java | 3 ++- .../org/quiltmc/enigma/gui/util/ExtensionFileFilter.java | 8 ++++---- .../java/org/quiltmc/enigma/gui/util/ProgressBar.java | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java index 5e5db63b2..852f529ca 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java @@ -619,7 +619,7 @@ public StatsGenerator getStatsGenerator() { @Nullable public MappingFormat getLoadedMappingFormat() { - return loadedMappingFormat; + return this.loadedMappingFormat; } public void createClient(String username, String ip, int port, char[] password) throws IOException { diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java index de8b7595e..b668330aa 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java @@ -223,7 +223,8 @@ public void updateUiState() { this.jarCloseItem.setEnabled(jarOpen); this.openMappingsItem.setEnabled(jarOpen); - this.saveMappingsItem.setEnabled(jarOpen && this.gui.mappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); this.saveMappingsAsMenu.setEnabled(jarOpen); + this.saveMappingsItem.setEnabled(jarOpen && this.gui.mappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); + this.saveMappingsAsMenu.setEnabled(jarOpen); this.closeMappingsItem.setEnabled(jarOpen); this.reloadMappingsItem.setEnabled(jarOpen); this.reloadAllItem.setEnabled(jarOpen); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java index d4b202108..596a50138 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java @@ -27,7 +27,7 @@ public ExtensionFileFilter(String formatName, List extensions) { } public List getExtensions() { - return extensions; + return this.extensions; } @Override @@ -37,7 +37,7 @@ public boolean accept(File f) { return true; } - for (String extension : extensions) { + for (String extension : this.extensions) { if (f.getName().endsWith(extension)) { return true; } @@ -50,11 +50,11 @@ public boolean accept(File f) { public String getDescription() { var joiner = new StringJoiner(", "); - for (String extension : extensions) { + for (String extension : this.extensions) { joiner.add("*" + extension); } - return I18n.translateFormatted("menu.file.mappings.file_filter", formatName, joiner.toString()); + return I18n.translateFormatted("menu.file.mappings.file_filter", this.formatName, joiner.toString()); } /** diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ProgressBar.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ProgressBar.java index f6f975b7d..1565480e3 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ProgressBar.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ProgressBar.java @@ -47,7 +47,7 @@ public void init(int totalWork, String title) { @Override public void step(int workDone, String message) { super.step(workDone, message); - this.messageUpdateListeners.forEach(listener -> listener.accept(message, workDone == totalWork)); + this.messageUpdateListeners.forEach(listener -> listener.accept(message, workDone == this.totalWork)); SwingUtilities.invokeLater(() -> { if (workDone != -1) { From 67ff574c4cf009f3d4f43f1732f979f7e298e8e2 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 16 Apr 2024 19:25:16 -0500 Subject: [PATCH 04/45] oro review + filter "open" dialogue --- .../quiltmc/enigma/gui/element/MenuBar.java | 4 ++ .../enigma/gui/util/ExtensionFileFilter.java | 41 ++++++++++++------- .../enigma/impl/translation/FileType.java | 14 ++++--- enigma/src/main/resources/lang/en_us.json | 1 + 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java index b668330aa..1797528b8 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java @@ -471,6 +471,10 @@ private void onGithubClicked() { private void onOpenMappingsClicked() { this.gui.mappingsFileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); + + List types = Arrays.stream(MappingFormat.values()).filter(format -> format.getReader() != null).toList(); + ExtensionFileFilter.setupFileChooser(this.gui.mappingsFileChooser, types.toArray(new MappingFormat[0])); + if (this.gui.mappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { File selectedFile = this.gui.mappingsFileChooser.getSelectedFile(); Config.main().stats.lastSelectedDir.setValue(this.gui.mappingsFileChooser.getCurrentDirectory().toString(), true); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java index 596a50138..a90f7603b 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java @@ -3,11 +3,11 @@ import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.util.I18n; -import javax.annotation.Nullable; import javax.swing.JFileChooser; import javax.swing.filechooser.FileFilter; import java.io.File; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import java.util.StringJoiner; @@ -23,7 +23,11 @@ public final class ExtensionFileFilter extends FileFilter { */ public ExtensionFileFilter(String formatName, List extensions) { this.formatName = formatName; - this.extensions = extensions.stream().map(s -> "." + s).toList(); + this.extensions = extensions.stream().peek(s -> { + if (s.contains(".")) { + throw new IllegalArgumentException("extensions cannot contain dots!"); + } + }).map(s -> "." + s).toList(); } public List getExtensions() { @@ -62,25 +66,34 @@ public String getDescription() { * and adds and selects a new filter based on the provided mapping format. * * @param fileChooser the file chooser to set up - * @param format the mapping format to use. if {@code null}, the file chooser will accept only directories + * @param formats the mapping formats to use. if empty, defaults to {@link MappingFormat#ENIGMA_DIRECTORY} */ - public static void setupFileChooser(JFileChooser fileChooser, @Nullable MappingFormat format) { - if (format == null) { - format = MappingFormat.ENIGMA_DIRECTORY; + public static void setupFileChooser(JFileChooser fileChooser, MappingFormat... formats) { + if (formats.length == 0) { + formats = new MappingFormat[]{MappingFormat.ENIGMA_DIRECTORY}; } // Remove previous custom filters. fileChooser.resetChoosableFileFilters(); - if (format.getFileType().isDirectory()) { - fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - } else { - fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - String formatName = I18n.translate("mapping_format." + format.name().toLowerCase()); - var filter = new ExtensionFileFilter(formatName, format.getFileType().getExtensions()); - // Add our new filter to the list... + for (MappingFormat format : formats) { + if (format.getFileType().isDirectory()) { + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + } else { + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + String formatName = I18n.translate("mapping_format." + format.name().toLowerCase()); + var filter = new ExtensionFileFilter(formatName, format.getFileType().getExtensions()); + // Add our new filter to the list... + fileChooser.addChoosableFileFilter(filter); + // ...and choose it as the default. + fileChooser.setFileFilter(filter); + } + } + + if (formats.length > 1) { + List extensions = Arrays.stream(formats).flatMap(format -> format.getFileType().getExtensions().stream()).distinct().toList(); + var filter = new ExtensionFileFilter(I18n.translate("mapping_format.all_formats"), extensions); fileChooser.addChoosableFileFilter(filter); - // ...and choose it as the default. fileChooser.setFileFilter(filter); } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java b/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java index 7d0b3bb85..42a5e915b 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java @@ -8,12 +8,12 @@ *

If a file type has multiple extensions, the default for saving will be the first one. */ public interface FileType { - FileType ENIGMA_MAPPING = new File(List.of("mapping", "mappings")); + FileType ENIGMA_MAPPING = new File("mapping", "mappings"); FileType ENIGMA_DIRECTORY = new Directory((File) ENIGMA_MAPPING); - FileType ENIGMA_ZIP = new File(List.of("zip")); - FileType PROGUARD = new File(List.of("txt")); - FileType SRG = new File(List.of("tsrg")); - FileType TINY_V2 = new File(List.of("tiny")); + FileType ENIGMA_ZIP = new File("zip"); + FileType PROGUARD = new File("txt"); + FileType SRG = new File("tsrg"); + FileType TINY_V2 = new File("tiny"); /** * Gets all possible extensions for this type of mapping file. @@ -39,6 +39,10 @@ public boolean isDirectory() { } record File(List extensions) implements FileType { + public File(String... extensions) { + this(List.of(extensions)); + } + @Override public List getExtensions() { return this.extensions; diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 08627a084..ab8efe7da 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -8,6 +8,7 @@ "mapping_format.tiny_file": "Tiny File", "mapping_format.srg_file": "SRG File", "mapping_format.proguard": "Proguard", + "mapping_format.all_formats": "All Formats", "type.methods": "Methods", "type.fields": "Fields", "type.parameters": "Parameters", From 9dc672c02d6f4e21599fc574b15ba9671ee0689b Mon Sep 17 00:00:00 2001 From: ix0rai Date: Thu, 18 Apr 2024 16:01:37 -0500 Subject: [PATCH 05/45] start moving to plugin --- .../java/org/quiltmc/enigma/api/Enigma.java | 64 ++++++ .../enigma/api/service/ReadWriteService.java | 43 ++++ .../translation/mapping/serde}/FileType.java | 9 +- .../mapping/serde/MappingFormat.java | 85 +------ .../enigma/impl/analysis/BuiltinPlugin.java | 210 ------------------ .../impl/plugin/BuiltinMappingFormats.java | 58 +++++ .../enigma/impl/plugin/BuiltinPlugin.java | 98 ++++++++ .../plugin/EnumFieldNameFindingVisitor.java | 118 ++++++++++ .../org.quiltmc.enigma.api.EnigmaPlugin | 2 +- 9 files changed, 384 insertions(+), 303 deletions(-) create mode 100644 enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java rename enigma/src/main/java/org/quiltmc/enigma/{impl/translation => api/translation/mapping/serde}/FileType.java (76%) delete mode 100644 enigma/src/main/java/org/quiltmc/enigma/impl/analysis/BuiltinPlugin.java create mode 100644 enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java create mode 100644 enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java create mode 100644 enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 9bfe81798..7846d3d03 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.api; +import com.google.common.io.MoreFiles; import org.quiltmc.enigma.api.analysis.index.jar.JarIndex; import org.quiltmc.enigma.api.analysis.index.mapping.MappingsIndex; import org.quiltmc.enigma.api.service.EnigmaService; @@ -13,8 +14,11 @@ import org.quiltmc.enigma.api.class_provider.JarClassProvider; import org.quiltmc.enigma.api.class_provider.ObfuscationFixClassProvider; import org.quiltmc.enigma.api.service.NameProposalService; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.api.translation.representation.entry.Entry; @@ -24,13 +28,18 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableListMultimap; import org.objectweb.asm.Opcodes; +import org.tinylog.Logger; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.ServiceLoader; @@ -128,6 +137,61 @@ public List getNameProposalServices() { return proposalServices; } + public List getReadWriteServices() { + return this.services.get(ReadWriteService.TYPE); + } + + public List getSupportedFileTypes() { + return this.getReadWriteServices().stream().map(ReadWriteService::getFileType).toList(); + } + + public Optional getReadWriteService(FileType fileType) { + return this.getReadWriteServices().stream().filter(service -> service.getFileType().equals(fileType)).findFirst(); + } + + public Optional parseReadWriteService(Path path) { + return this.parseFileType(path).flatMap(this::getReadWriteService); + } + + /** + * Determines the mapping format of the provided path. Checks all formats according to their {@link FileType} file extensions. + * If the path is a directory, it will check the first file in the directory. + * @param path the path to analyse + * @return the mapping format of the path + */ + public Optional parseFileType(Path path) { + List supportedTypes = this.getSupportedFileTypes(); + + if (Files.isDirectory(path)) { + try { + File firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())).findFirst().orElseThrow(); + + for (FileType type : supportedTypes) { + if (!type.isDirectory()) { + continue; + } + + String extension = MoreFiles.getFileExtension(firstFile.toPath()).toLowerCase(); + if (type.getExtensions().contains(extension)) { + return Optional.of(type); + } + } + } catch (Exception e) { + Logger.error(e, "Failed to determine mapping format of directory {}", path); + } + } else { + String extension = MoreFiles.getFileExtension(path).toLowerCase(); + + for (FileType type : supportedTypes) { + if (type.getExtensions().contains(extension)) { + return Optional.of(type); + } + } + } + + return Optional.empty(); + } + public static class Builder { private EnigmaProfile profile = EnigmaProfile.EMPTY; private Iterable plugins = ServiceLoader.load(EnigmaPlugin.class); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java new file mode 100644 index 000000000..34bf350e7 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java @@ -0,0 +1,43 @@ +package org.quiltmc.enigma.api.service; + +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; + +import javax.annotation.Nullable; + +public interface ReadWriteService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("read_write"); + + @Nullable + MappingsReader createReader(); + + @Nullable + MappingsWriter createWriter(String fromNamespace, String toNamespace); + + FileType getFileType(); + + static ReadWriteService create(MappingsReader reader, MappingsWriter writer, FileType fileType, String id) { + return new ReadWriteService() { + @Override + public String getId() { + return id; + } + + @Override + public MappingsReader createReader() { + return reader; + } + + @Override + public MappingsWriter createWriter(String fromNamespace, String toNamespace) { + return writer; + } + + @Override + public FileType getFileType() { + return fileType; + } + }; + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/FileType.java similarity index 76% rename from enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java rename to enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/FileType.java index 42a5e915b..dac25dadf 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/translation/FileType.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/FileType.java @@ -1,4 +1,4 @@ -package org.quiltmc.enigma.impl.translation; +package org.quiltmc.enigma.api.translation.mapping.serde; import java.util.List; @@ -8,13 +8,6 @@ *

If a file type has multiple extensions, the default for saving will be the first one. */ public interface FileType { - FileType ENIGMA_MAPPING = new File("mapping", "mappings"); - FileType ENIGMA_DIRECTORY = new Directory((File) ENIGMA_MAPPING); - FileType ENIGMA_ZIP = new File("zip"); - FileType PROGUARD = new File("txt"); - FileType SRG = new File("tsrg"); - FileType TINY_V2 = new File("tiny"); - /** * Gets all possible extensions for this type of mapping file. * If {@link #isDirectory()} is {@code true}, this will return the types of mapping allowed inside the directory. diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java index e75b1d913..53f00aaa7 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java @@ -1,44 +1,15 @@ package org.quiltmc.enigma.api.translation.mapping.serde; -import com.google.common.io.MoreFiles; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; -import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; -import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsWriter; -import org.quiltmc.enigma.api.translation.mapping.serde.proguard.ProguardMappingsReader; -import org.quiltmc.enigma.api.translation.mapping.serde.srg.SrgMappingsWriter; -import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Reader; -import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; -import org.quiltmc.enigma.impl.translation.FileType; import javax.annotation.Nullable; -import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Objects; - -public enum MappingFormat { - ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE, FileType.ENIGMA_MAPPING), - ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY, FileType.ENIGMA_DIRECTORY), - ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP, FileType.ENIGMA_ZIP), - TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader(), FileType.TINY_V2), - SRG_FILE(SrgMappingsWriter.INSTANCE, null, FileType.SRG), - PROGUARD(null, ProguardMappingsReader.INSTANCE, FileType.PROGUARD); - - private final MappingsWriter writer; - private final MappingsReader reader; - private final FileType fileType; - - MappingFormat(MappingsWriter writer, MappingsReader reader, FileType fileType) { - this.writer = writer; - this.reader = reader; - this.fileType = fileType; - } +public class MappingFormat { public void write(EntryTree mappings, Path path, MappingSaveParameters saveParameters) { this.write(mappings, MappingDelta.added(mappings), path, ProgressListener.createEmpty(), saveParameters); } @@ -85,58 +56,4 @@ public EntryTree read(Path path, ProgressListener progressListener return this.reader.read(path, progressListener); } - - @Nullable - public MappingsWriter getWriter() { - return this.writer; - } - - @Nullable - public MappingsReader getReader() { - return this.reader; - } - - public FileType getFileType() { - return this.fileType; - } - - /** - * Determines the mapping format of the provided file. Checks all formats according to their {@link #getFileType()} file extensions. - * If the file is a directory, it will check the first file in the directory. - * Will return {@link #PROGUARD} if no format is found for single files, and {@link #ENIGMA_DIRECTORY} if no format is found for directories. - * @param file the file to analyse - * @return the mapping format of the file. - */ - public static MappingFormat parseFromFile(Path file) { - if (Files.isDirectory(file)) { - try { - File firstFile = Arrays.stream(Objects.requireNonNull(file.toFile().listFiles())).findFirst().orElseThrow(); - - for (MappingFormat format : values()) { - if (!format.getFileType().isDirectory()) { - continue; - } - - String extension = MoreFiles.getFileExtension(firstFile.toPath()).toLowerCase(); - if (format.fileType.getExtensions().contains(extension)) { - return format; - } - } - - return ENIGMA_DIRECTORY; - } catch (Exception e) { - return ENIGMA_DIRECTORY; - } - } else { - String extension = MoreFiles.getFileExtension(file).toLowerCase(); - - for (MappingFormat format : values()) { - if (format.fileType.getExtensions().contains(extension)) { - return format; - } - } - } - - return PROGUARD; - } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/BuiltinPlugin.java b/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/BuiltinPlugin.java deleted file mode 100644 index f0c9499fd..000000000 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/BuiltinPlugin.java +++ /dev/null @@ -1,210 +0,0 @@ -package org.quiltmc.enigma.impl.analysis; - -import org.quiltmc.enigma.api.Enigma; -import org.quiltmc.enigma.api.analysis.index.jar.BridgeMethodIndex; -import org.quiltmc.enigma.api.EnigmaPlugin; -import org.quiltmc.enigma.api.EnigmaPluginContext; -import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex; -import org.quiltmc.enigma.api.analysis.index.jar.JarIndex; -import org.quiltmc.enigma.api.service.JarIndexerService; -import org.quiltmc.enigma.api.service.NameProposalService; -import org.quiltmc.enigma.api.source.DecompilerService; -import org.quiltmc.enigma.api.source.Decompilers; -import org.quiltmc.enigma.api.source.TokenType; -import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.EntryRemapper; -import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; -import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; -import org.quiltmc.enigma.api.translation.representation.entry.Entry; -import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; -import org.jetbrains.java.decompiler.util.Pair; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public final class BuiltinPlugin implements EnigmaPlugin { - @Override - public void init(EnigmaPluginContext ctx) { - this.registerEnumNamingService(ctx); - this.registerSpecializedMethodNamingService(ctx); - this.registerDecompilerServices(ctx); - } - - private void registerEnumNamingService(EnigmaPluginContext ctx) { - final Map, String> names = new HashMap<>(); - final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); - - ctx.registerService(JarIndexerService.TYPE, ctx1 -> JarIndexerService.fromVisitor(visitor, "enigma:enum_initializer_indexer")); - - ctx.registerService(NameProposalService.TYPE, ctx1 -> new NameProposalService() { - @Override - public Map, EntryMapping> getProposedNames(JarIndex index) { - Map, EntryMapping> mappings = new HashMap<>(); - - index.getIndex(EntryIndex.class).getFields().forEach(field -> { - if (names.containsKey(field)) { - mappings.put(field, this.createMapping(names.get(field), TokenType.JAR_PROPOSED)); - } - }); - - return mappings; - } - - @Override - public Map, EntryMapping> getDynamicProposedNames(EntryRemapper remapper, @Nullable Entry obfEntry, @Nullable EntryMapping oldMapping, @Nullable EntryMapping newMapping) { - return null; - } - - @Override - public String getId() { - return "enigma:enum_name_proposer"; - } - }); - } - - private void registerSpecializedMethodNamingService(EnigmaPluginContext ctx) { - ctx.registerService(NameProposalService.TYPE, ctx1 -> new NameProposalService() { - @Override - public Map, EntryMapping> getProposedNames(JarIndex index) { - BridgeMethodIndex bridgeMethodIndex = index.getIndex(BridgeMethodIndex.class); - Map, EntryMapping> mappings = new HashMap<>(); - - bridgeMethodIndex.getSpecializedToBridge().forEach((specialized, bridge) -> { - EntryMapping mapping = this.createMapping(bridge.getName(), TokenType.JAR_PROPOSED); - - mappings.put(specialized, mapping); - // IndexEntryResolver#resolveEntry can return the bridge method, so we can just use the name - mappings.put(bridge, mapping); - }); - - return mappings; - } - - @Override - public Map, EntryMapping> getDynamicProposedNames(EntryRemapper remapper, @Nullable Entry obfEntry, @Nullable EntryMapping oldMapping, @Nullable EntryMapping newMapping) { - return null; - } - - @Override - public String getId() { - return "enigma:specialized_method_name_proposer"; - } - }); - } - - private void registerDecompilerServices(EnigmaPluginContext ctx) { - ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.VINEFLOWER); - ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); - ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.CFR); - ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.BYTECODE); - } - - private static final class EnumFieldNameFindingVisitor extends ClassVisitor { - private ClassEntry clazz; - private String className; - private final Map, String> mappings; - private final Set> enumFields = new HashSet<>(); - private final List classInits = new ArrayList<>(); - - EnumFieldNameFindingVisitor(Map, String> mappings) { - super(Enigma.ASM_VERSION); - this.mappings = mappings; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - this.className = name; - this.clazz = new ClassEntry(name); - this.enumFields.clear(); - this.classInits.clear(); - } - - @Override - public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { - if ((access & Opcodes.ACC_ENUM) != 0 - && !this.enumFields.add(Pair.of(name, descriptor))) { - throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); - } - - return super.visitField(access, name, descriptor, signature, value); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - if ("".equals(name)) { - MethodNode node = new MethodNode(this.api, access, name, descriptor, signature, exceptions); - this.classInits.add(node); - return node; - } - - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - - @Override - public void visitEnd() { - super.visitEnd(); - try { - this.collectResults(); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private void collectResults() throws Exception { - String owner = this.className; - Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); - - for (MethodNode mn : this.classInits) { - Frame[] frames = analyzer.analyze(this.className, mn); - - InsnList instrs = mn.instructions; - for (int i = 1; i < instrs.size(); i++) { - AbstractInsnNode instr1 = instrs.get(i - 1); - AbstractInsnNode instr2 = instrs.get(i); - String s = null; - - if (instr2.getOpcode() == Opcodes.PUTSTATIC - && ((FieldInsnNode) instr2).owner.equals(owner) - && this.enumFields.contains(Pair.of(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) - && instr1.getOpcode() == Opcodes.INVOKESPECIAL - && "".equals(((MethodInsnNode) instr1).name)) { - for (int j = 0; j < frames[i - 1].getStackSize(); j++) { - SourceValue sv = frames[i - 1].getStack(j); - for (AbstractInsnNode ci : sv.insns) { - if (ci instanceof LdcInsnNode insnNode && insnNode.cst instanceof String && s == null) { - s = (String) (insnNode.cst); - } - } - } - } - - if (s != null) { - this.mappings.put(new FieldEntry(this.clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s); - } - - // report otherwise? - } - } - } - } -} diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java new file mode 100644 index 000000000..5954510cb --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -0,0 +1,58 @@ +package org.quiltmc.enigma.impl.plugin; + +import org.quiltmc.enigma.api.EnigmaPluginContext; +import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; +import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; +import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsWriter; +import org.quiltmc.enigma.api.translation.mapping.serde.proguard.ProguardMappingsReader; +import org.quiltmc.enigma.api.translation.mapping.serde.srg.SrgMappingsWriter; +import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Reader; +import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; + +public class BuiltinMappingFormats { + public static void register(EnigmaPluginContext ctx) { + FileType.File enigmaMapping = new FileType.File("mapping", "mappings"); + + ctx.registerService(ReadWriteService.TYPE, + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.FILE, EnigmaMappingsWriter.FILE, enigmaMapping, "enigma_file") + ); + ctx.registerService(ReadWriteService.TYPE, + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.DIRECTORY, EnigmaMappingsWriter.DIRECTORY, new FileType.Directory(enigmaMapping), "enigma_directory") + ); + ctx.registerService(ReadWriteService.TYPE, + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.ZIP, EnigmaMappingsWriter.ZIP, new FileType.File("zip"), "enigma_zip") + ); + ctx.registerService(ReadWriteService.TYPE, + ctx1 -> new ReadWriteService() { + @Override + public MappingsReader createReader() { + return new TinyV2Reader(); + } + + @Override + public MappingsWriter createWriter(String fromNamespace, String toNamespace) { + return new TinyV2Writer(fromNamespace, toNamespace); + } + + @Override + public FileType getFileType() { + return new FileType.File("tiny"); + } + + @Override + public String getId() { + return "tiny_v2"; + } + } + ); + ctx.registerService(ReadWriteService.TYPE, + ctx1 -> ReadWriteService.create(null, SrgMappingsWriter.INSTANCE, new FileType.File("tsrg"), "srg_file") + ); + ctx.registerService(ReadWriteService.TYPE, + ctx1 -> ReadWriteService.create(ProguardMappingsReader.INSTANCE, null, new FileType.File("txt"), "proguard") + ); + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java new file mode 100644 index 000000000..b8bff4f9d --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java @@ -0,0 +1,98 @@ +package org.quiltmc.enigma.impl.plugin; + +import org.quiltmc.enigma.api.analysis.index.jar.BridgeMethodIndex; +import org.quiltmc.enigma.api.EnigmaPlugin; +import org.quiltmc.enigma.api.EnigmaPluginContext; +import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex; +import org.quiltmc.enigma.api.analysis.index.jar.JarIndex; +import org.quiltmc.enigma.api.service.JarIndexerService; +import org.quiltmc.enigma.api.service.NameProposalService; +import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.source.Decompilers; +import org.quiltmc.enigma.api.source.TokenType; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.EntryRemapper; +import org.quiltmc.enigma.api.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +public final class BuiltinPlugin implements EnigmaPlugin { + @Override + public void init(EnigmaPluginContext ctx) { + registerEnumNamingService(ctx); + registerSpecializedMethodNamingService(ctx); + registerDecompilerServices(ctx); + BuiltinMappingFormats.register(ctx); + } + + private static void registerEnumNamingService(EnigmaPluginContext ctx) { + final Map, String> names = new HashMap<>(); + final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); + + ctx.registerService(JarIndexerService.TYPE, ctx1 -> JarIndexerService.fromVisitor(visitor, "enigma:enum_initializer_indexer")); + + ctx.registerService(NameProposalService.TYPE, ctx1 -> new NameProposalService() { + @Override + public Map, EntryMapping> getProposedNames(JarIndex index) { + Map, EntryMapping> mappings = new HashMap<>(); + + index.getIndex(EntryIndex.class).getFields().forEach(field -> { + if (names.containsKey(field)) { + mappings.put(field, this.createMapping(names.get(field), TokenType.JAR_PROPOSED)); + } + }); + + return mappings; + } + + @Override + public Map, EntryMapping> getDynamicProposedNames(EntryRemapper remapper, @Nullable Entry obfEntry, @Nullable EntryMapping oldMapping, @Nullable EntryMapping newMapping) { + return null; + } + + @Override + public String getId() { + return "enigma:enum_name_proposer"; + } + }); + } + + private static void registerSpecializedMethodNamingService(EnigmaPluginContext ctx) { + ctx.registerService(NameProposalService.TYPE, ctx1 -> new NameProposalService() { + @Override + public Map, EntryMapping> getProposedNames(JarIndex index) { + BridgeMethodIndex bridgeMethodIndex = index.getIndex(BridgeMethodIndex.class); + Map, EntryMapping> mappings = new HashMap<>(); + + bridgeMethodIndex.getSpecializedToBridge().forEach((specialized, bridge) -> { + EntryMapping mapping = this.createMapping(bridge.getName(), TokenType.JAR_PROPOSED); + + mappings.put(specialized, mapping); + // IndexEntryResolver#resolveEntry can return the bridge method, so we can just use the name + mappings.put(bridge, mapping); + }); + + return mappings; + } + + @Override + public Map, EntryMapping> getDynamicProposedNames(EntryRemapper remapper, @Nullable Entry obfEntry, @Nullable EntryMapping oldMapping, @Nullable EntryMapping newMapping) { + return null; + } + + @Override + public String getId() { + return "enigma:specialized_method_name_proposer"; + } + }); + } + + private static void registerDecompilerServices(EnigmaPluginContext ctx) { + ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.VINEFLOWER); + ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); + ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.CFR); + ctx.registerService(DecompilerService.TYPE, ctx1 -> Decompilers.BYTECODE); + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java new file mode 100644 index 000000000..bd7c28412 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java @@ -0,0 +1,118 @@ +package org.quiltmc.enigma.impl.plugin; + +import org.jetbrains.java.decompiler.util.Pair; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; +import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.Entry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +final class EnumFieldNameFindingVisitor extends ClassVisitor { + private ClassEntry clazz; + private String className; + private final Map, String> mappings; + private final Set> enumFields = new HashSet<>(); + private final List classInits = new ArrayList<>(); + + EnumFieldNameFindingVisitor(Map, String> mappings) { + super(Enigma.ASM_VERSION); + this.mappings = mappings; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + this.className = name; + this.clazz = new ClassEntry(name); + this.enumFields.clear(); + this.classInits.clear(); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + if ((access & Opcodes.ACC_ENUM) != 0 + && !this.enumFields.add(Pair.of(name, descriptor))) { + throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); + } + + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + if ("".equals(name)) { + MethodNode node = new MethodNode(this.api, access, name, descriptor, signature, exceptions); + this.classInits.add(node); + return node; + } + + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + @Override + public void visitEnd() { + super.visitEnd(); + try { + this.collectResults(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private void collectResults() throws Exception { + String owner = this.className; + Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); + + for (MethodNode mn : this.classInits) { + Frame[] frames = analyzer.analyze(this.className, mn); + + InsnList instrs = mn.instructions; + for (int i = 1; i < instrs.size(); i++) { + AbstractInsnNode instr1 = instrs.get(i - 1); + AbstractInsnNode instr2 = instrs.get(i); + String s = null; + + if (instr2.getOpcode() == Opcodes.PUTSTATIC + && ((FieldInsnNode) instr2).owner.equals(owner) + && this.enumFields.contains(Pair.of(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) + && instr1.getOpcode() == Opcodes.INVOKESPECIAL + && "".equals(((MethodInsnNode) instr1).name)) { + for (int j = 0; j < frames[i - 1].getStackSize(); j++) { + SourceValue sv = frames[i - 1].getStack(j); + for (AbstractInsnNode ci : sv.insns) { + if (ci instanceof LdcInsnNode insnNode && insnNode.cst instanceof String && s == null) { + s = (String) (insnNode.cst); + } + } + } + } + + if (s != null) { + this.mappings.put(new FieldEntry(this.clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s); + } + + // report otherwise? + } + } + } +} diff --git a/enigma/src/main/resources/META-INF/services/org.quiltmc.enigma.api.EnigmaPlugin b/enigma/src/main/resources/META-INF/services/org.quiltmc.enigma.api.EnigmaPlugin index 67cad0807..9df7ec919 100644 --- a/enigma/src/main/resources/META-INF/services/org.quiltmc.enigma.api.EnigmaPlugin +++ b/enigma/src/main/resources/META-INF/services/org.quiltmc.enigma.api.EnigmaPlugin @@ -1 +1 @@ -org.quiltmc.enigma.impl.analysis.BuiltinPlugin +org.quiltmc.enigma.impl.plugin.BuiltinPlugin From b969aa7d87ea2ae6f288cec41efb54804b4708f0 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sat, 20 Apr 2024 20:32:23 -0500 Subject: [PATCH 06/45] migration to read write service: begin! --- .../java/org/quiltmc/enigma/api/Enigma.java | 1 - .../enigma/api/service/ReadWriteService.java | 28 +++++---- .../mapping/serde/MappingFormat.java | 59 ------------------- .../mapping/serde/MappingsWriter.java | 11 +++- .../serde/enigma/EnigmaMappingsWriter.java | 7 ++- .../mapping/serde/srg/SrgMappingsWriter.java | 3 +- .../mapping/serde/tinyv2/TinyV2Writer.java | 19 +++--- .../impl/plugin/BuiltinMappingFormats.java | 23 ++++++-- .../org/quiltmc/enigma/TestTranslator.java | 3 +- .../translation/mapping/TestComments.java | 5 +- .../mapping/TestDeterministicWrite.java | 1 - .../mapping/TestReadWriteCycle.java | 8 +-- .../translation/mapping/TestV2Main.java | 3 +- 13 files changed, 68 insertions(+), 103 deletions(-) delete mode 100644 enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 7846d3d03..3a481e4f5 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -18,7 +18,6 @@ import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.FileType; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.api.translation.representation.entry.Entry; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java index 34bf350e7..76315a9b3 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java @@ -1,37 +1,39 @@ package org.quiltmc.enigma.api.service; +import org.quiltmc.enigma.api.ProgressListener; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.FileType; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Path; -public interface ReadWriteService extends EnigmaService { +public interface ReadWriteService extends EnigmaService, MappingsWriter, MappingsReader { EnigmaServiceType TYPE = EnigmaServiceType.create("read_write"); - @Nullable - MappingsReader createReader(); - - @Nullable - MappingsWriter createWriter(String fromNamespace, String toNamespace); - FileType getFileType(); static ReadWriteService create(MappingsReader reader, MappingsWriter writer, FileType fileType, String id) { return new ReadWriteService() { @Override - public String getId() { - return id; + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + writer.write(obfNamespace, deobfNamespace, mappings, delta, path, progress, saveParameters); } @Override - public MappingsReader createReader() { - return reader; + public EntryTree read(Path path, ProgressListener progress) throws MappingParseException, IOException { + return reader.read(path, progress); } @Override - public MappingsWriter createWriter(String fromNamespace, String toNamespace) { - return writer; + public String getId() { + return id; } @Override diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java deleted file mode 100644 index 53f00aaa7..000000000 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFormat.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.quiltmc.enigma.api.translation.mapping.serde; - -import org.quiltmc.enigma.api.ProgressListener; -import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.MappingDelta; -import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.Path; - -public class MappingFormat { - public void write(EntryTree mappings, Path path, MappingSaveParameters saveParameters) { - this.write(mappings, MappingDelta.added(mappings), path, ProgressListener.createEmpty(), saveParameters); - } - - public void write(EntryTree mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { - this.write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters); - } - - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { - if (this.writer == null) { - throw new IllegalStateException(this.name() + " does not support writing"); - } - - this.writer.write(mappings, delta, path, progressListener, saveParameters); - } - - /** - * Reads the provided path and returns it as a tree of mappings. - * @param path the path to read - * @return a tree of every read mapping - * @throws IOException when there's an I/O error reading the files - * @throws MappingParseException when there's an issue with the content of a mapping file - */ - public EntryTree read(Path path) throws IOException, MappingParseException { - if (this.reader == null) { - throw new IllegalStateException(this.name() + " does not support reading"); - } - - return this.reader.read(path, ProgressListener.createEmpty()); - } - - /** - * Reads the provided path and returns it as a tree of mappings. - * @param path the path to read - * @param progressListener a progress listener to be used for displaying the progress to the user - * @return a tree of every read mapping - * @throws IOException when there's an I/O error reading the files - * @throws MappingParseException when there's an issue with the content of a mapping file - */ - public EntryTree read(Path path, ProgressListener progressListener) throws IOException, MappingParseException { - if (this.reader == null) { - throw new IllegalStateException(this.name() + " does not support reading"); - } - - return this.reader.read(path, progressListener); - } -} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java index 5b6946b1f..f585aee1e 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java @@ -8,17 +8,22 @@ import org.quiltmc.enigma.api.translation.mapping.tree.EntryTreeNode; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; +import javax.annotation.Nullable; import java.nio.file.Path; public interface MappingsWriter { - void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); + void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); + + default void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + this.write(null, null, mappings, delta, path, progress, saveParameters); + } default void write(EntryTree mappings, Path path, MappingSaveParameters saveParameters) { - this.write(mappings, MappingDelta.added(mappings), path, ProgressListener.createEmpty(), saveParameters); + this.write(null, null, mappings, MappingDelta.added(mappings), path, ProgressListener.createEmpty(), saveParameters); } default void write(EntryTree mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - this.write(mappings, MappingDelta.added(mappings), path, progress, saveParameters); + this.write(null, null, mappings, MappingDelta.added(mappings), path, progress, saveParameters); } /** diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java index b28ed3d09..ef4bf897c 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java @@ -38,11 +38,12 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public enum EnigmaMappingsWriter implements MappingsWriter { FILE { @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { EntryTree writtenMappings = MappingsWriter.filterMappings(mappings, saveParameters); Collection classes = writtenMappings.getRootNodes() @@ -65,7 +66,7 @@ public void write(EntryTree mappings, MappingDelta d }, DIRECTORY { @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { EntryTree writtenMappings = MappingsWriter.filterMappings(mappings, saveParameters); Collection changedClasses = delta.getChangedRoots() @@ -161,7 +162,7 @@ private Path resolve(Path root, ClassEntry classEntry) { }, ZIP { @Override - public void write(EntryTree mappings, MappingDelta delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) { DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters); } catch (IOException e) { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java index 8f47f0baf..cd1a9ead7 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java @@ -19,6 +19,7 @@ import org.quiltmc.enigma.util.I18n; import org.tinylog.Logger; +import javax.annotation.Nullable; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; @@ -32,7 +33,7 @@ public enum SrgMappingsWriter implements MappingsWriter { INSTANCE; @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { EntryTree writtenMappings = MappingsWriter.filterMappings(mappings, saveParameters); try { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java index f031196dd..9547cfa39 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -18,6 +18,7 @@ import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import org.tinylog.Logger; +import javax.annotation.Nullable; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; @@ -30,12 +31,8 @@ public final class TinyV2Writer implements MappingsWriter { private static final String MINOR_VERSION = "0"; - private final String obfHeader; - private final String deobfHeader; - public TinyV2Writer(String obfHeader, String deobfHeader) { - this.obfHeader = obfHeader; - this.deobfHeader = deobfHeader; + public TinyV2Writer() { } private static int getEntryKind(Entry e) { @@ -77,14 +74,22 @@ private static Comparator> mappingComparator() { } @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { + if (obfNamespace == null) { + obfNamespace = "intermediary"; + } + + if (deobfNamespace == null) { + deobfNamespace = "named"; + } + List> classes = StreamSupport.stream(mappings.spliterator(), false) .filter(node -> node.getEntry() instanceof ClassEntry) .sorted(mappingComparator()) .toList(); try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) { - writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + this.obfHeader + "\t" + this.deobfHeader); + writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfNamespace + "\t" + deobfNamespace); // no escape names diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java index 5954510cb..c85df21fc 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -1,16 +1,24 @@ package org.quiltmc.enigma.impl.plugin; import org.quiltmc.enigma.api.EnigmaPluginContext; +import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.FileType; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsWriter; import org.quiltmc.enigma.api.translation.mapping.serde.proguard.ProguardMappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.srg.SrgMappingsWriter; import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Reader; import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Path; public class BuiltinMappingFormats { public static void register(EnigmaPluginContext ctx) { @@ -27,14 +35,17 @@ public static void register(EnigmaPluginContext ctx) { ); ctx.registerService(ReadWriteService.TYPE, ctx1 -> new ReadWriteService() { + static final TinyV2Reader reader = new TinyV2Reader(); + static final TinyV2Writer writer = new TinyV2Writer(); + @Override - public MappingsReader createReader() { - return new TinyV2Reader(); + public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + writer.write(obfNamespace, deobfNamespace, mappings, delta, path, progress, saveParameters); } @Override - public MappingsWriter createWriter(String fromNamespace, String toNamespace) { - return new TinyV2Writer(fromNamespace, toNamespace); + public EntryTree read(Path path, ProgressListener progress) throws MappingParseException, IOException { + return reader.read(path, progress); } @Override diff --git a/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java b/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java index 34b45384f..816ad521d 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java +++ b/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java @@ -7,7 +7,6 @@ import org.quiltmc.enigma.api.translation.TranslateResult; import org.quiltmc.enigma.api.translation.Translator; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.junit.jupiter.api.BeforeAll; @@ -30,7 +29,7 @@ public class TestTranslator { public static void beforeClass() throws Exception { enigma = Enigma.create(); project = enigma.openJar(JAR, new ClasspathClassProvider(), ProgressListener.createEmpty()); - mappings = MappingFormat.ENIGMA_FILE.read( + mappings = enigma.parseReadWriteService(Path.of("/translation.mappings")).get().read( TestUtil.getResource("/translation.mappings"), ProgressListener.createEmpty()); project.setMappings(mappings, ProgressListener.createEmpty()); diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java index 81d0e183b..e85b9f2ca 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java @@ -1,7 +1,9 @@ package org.quiltmc.enigma.translation.mapping; import org.quiltmc.enigma.TestUtil; +import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; @@ -22,7 +24,6 @@ public void testParseAndWrite() throws IOException, MappingParseException { EntryTree mappings = EnigmaMappingsReader.DIRECTORY.read( DIRECTORY); - new TinyV2Writer("intermediary", "named") - .write(mappings, DIRECTORY.resolve("convertedtiny.tiny"), params); + new TinyV2Writer().write("intermediary", "named", mappings, MappingDelta.added(mappings), DIRECTORY.resolve("convertedtiny.tiny"), ProgressListener.createEmpty(), params); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java index 4caebc463..e550a3d44 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.api.translation.representation.ArgumentDescriptor; diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java index f2f6d25a2..61ccace9e 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java @@ -1,10 +1,10 @@ package org.quiltmc.enigma.translation.mapping; import org.quiltmc.enigma.api.ProgressListener; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; @@ -55,7 +55,7 @@ private void insertMapping(EntryTree mappings, Pair testMappings = new HashEntryTree<>(); this.insertMapping(testMappings, this.testClazz); @@ -73,10 +73,10 @@ private void testReadWriteCycle(MappingFormat mappingFormat, String tmpNameSuffi File tempFile = File.createTempFile("readWriteCycle", tmpNameSuffix); tempFile.delete(); //remove the auto created file - mappingFormat.write(testMappings, tempFile.toPath(), ProgressListener.createEmpty(), this.parameters); + readWriteService.write(testMappings, tempFile.toPath(), ProgressListener.createEmpty(), this.parameters); Assertions.assertTrue(tempFile.exists(), "Written file not created"); - EntryTree loadedMappings = mappingFormat.read(tempFile.toPath(), ProgressListener.createEmpty()); + EntryTree loadedMappings = readWriteService.read(tempFile.toPath(), ProgressListener.createEmpty()); Assertions.assertTrue(loadedMappings.contains(this.testClazz.a()), "Loaded mappings don't contain testClazz"); Assertions.assertTrue(loadedMappings.contains(this.testField1.a()), "Loaded mappings don't contain testField1"); diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java index 39f012cc1..63c90a810 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java @@ -2,6 +2,7 @@ import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; @@ -19,6 +20,6 @@ public static void main(String... args) throws Exception { EntryTree tree = EnigmaMappingsReader.DIRECTORY.read(path); - new TinyV2Writer("obf", "deobf").write(tree, Paths.get("currentYarn.tiny"), ProgressListener.createEmpty(), parameters); + new TinyV2Writer().write("obf", "deobf", tree, MappingDelta.added(tree), Paths.get("currentYarn.tiny"), ProgressListener.createEmpty(), parameters); } } From caf0dc24d6b8d2101f4f0b817425e17aa9c900cb Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sat, 20 Apr 2024 22:24:10 -0500 Subject: [PATCH 07/45] add namespace parameters to MappingSaveParameters, port most of command --- .../org/quiltmc/enigma/command/Argument.java | 20 ++++----- .../enigma/command/CheckMappingsCommand.java | 3 +- .../org/quiltmc/enigma/command/Command.java | 12 ++--- .../quiltmc/enigma/command/CommandsUtil.java | 44 +++++++++++++++++++ .../command/ComposeMappingsCommand.java | 30 +++++++------ .../command/ConvertMappingsCommand.java | 26 ++++++----- .../enigma/command/DeobfuscateCommand.java | 3 +- .../command/DropInvalidMappingsCommand.java | 6 +-- .../command/FillClassMappingsCommand.java | 22 ++++++---- .../InsertProposedMappingsCommand.java | 25 ++++++----- .../enigma/command/InvertMappingsCommand.java | 29 +++++++----- .../command/MapSpecializedMethodsCommand.java | 26 ++++++----- .../enigma/command/MappingCommandsUtil.java | 31 ------------- .../command/FillClassMappingsCommandTest.java | 8 ++-- .../MapSpecializedMethodsCommandTest.java | 8 ++-- .../java/org/quiltmc/enigma/api/Enigma.java | 13 ++++++ .../org/quiltmc/enigma/api/EnigmaProfile.java | 2 +- .../enigma/api/service/ReadWriteService.java | 24 +++++++++- .../mapping/serde/MappingFileNameFormat.java | 9 ++++ .../mapping/serde/MappingSaveParameters.java | 30 ++++++++++++- .../impl/plugin/BuiltinMappingFormats.java | 10 +++++ .../enigma/MappingFormatDetectionTest.java | 17 ++++--- 22 files changed, 259 insertions(+), 139 deletions(-) create mode 100644 enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java delete mode 100644 enigma-cli/src/main/java/org/quiltmc/enigma/command/MappingCommandsUtil.java diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/Argument.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/Argument.java index 11ab1c9a0..f91274270 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/Argument.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/Argument.java @@ -17,18 +17,6 @@ public enum Argument { """ A path to the right file or folder to read mappings from, used in commands which take two mapping inputs.""" ), - OUTPUT_MAPPING_FORMAT("", - """ - The mapping format to use when writing output mappings. Allowed values are (case-insensitive): - - TINY_V2:from_namespace:to_namespace (ex: tiny_v2:intermediary:named) - - ENIGMA_FILE - - ENIGMA_DIRECTORY - - ENIGMA_ZIP - - SRG_FILE - - RECAF - - Proguard is not a valid output format, as writing is unsupported.""" - ), MAPPING_OUTPUT("", """ A path to the file or folder to write mappings to. Will be created if missing.""" @@ -60,6 +48,14 @@ The decompiler to use when producing output. Allowed values are (case-insensitiv ENIGMA_PROFILE("", """ A path to an Enigma profile JSON file, used to apply things like plugins.""" + ), + OBFUSCATED_NAMESPACE("", + """ + The namespace to use for obfuscated names when writing mappings. Only used in certain mapping formats.""" + ), + DEOBFUSCATED_NAMESPACE("", + """ + The namespace to use for deobfuscated names when writing mappings. Only used in certain mapping formats.""" ); private final String displayForm; diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CheckMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CheckMappingsCommand.java index 5d889296c..652d55463 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CheckMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CheckMappingsCommand.java @@ -13,7 +13,8 @@ public class CheckMappingsCommand extends Command { public CheckMappingsCommand() { super(Argument.INPUT_JAR.required(), - Argument.INPUT_MAPPINGS.required()); + Argument.INPUT_MAPPINGS.required() + ); } @Override diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/Command.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/Command.java index d25083cc4..2ab68ed85 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/Command.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/Command.java @@ -12,7 +12,7 @@ import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.tree.DeltaTrackingTree; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.tinylog.Logger; @@ -123,7 +123,7 @@ public static Enigma createEnigma() { * @return the argument, as a string */ protected String getArg(String[] args, int index) { - if (index < this.allArguments.size()) { + if (index < this.allArguments.size() && index >= 0) { return getArg(args, index, this.allArguments.get(index)); } else { throw new RuntimeException("arg index is outside of range of possible arguments! (index: " + index + ", allowed arg count: " + this.allArguments.size() + ")"); @@ -153,7 +153,7 @@ public static EnigmaProject openProject(Path fileJarIn, Path fileMappings, Enigm if (fileMappings != null) { Logger.info("Reading mappings..."); - EntryTree mappings = readMappings(fileMappings, progress); + EntryTree mappings = readMappings(enigma, fileMappings, progress); project.setMappings(mappings, new ConsoleProgressListener()); } @@ -161,9 +161,9 @@ public static EnigmaProject openProject(Path fileJarIn, Path fileMappings, Enigm return project; } - protected static EntryTree readMappings(Path path, ProgressListener progress) throws MappingParseException, IOException { - MappingFormat format = MappingFormat.parseFromFile(path); - return format.read(path, progress); + protected static EntryTree readMappings(Enigma enigma, Path path, ProgressListener progress) throws MappingParseException, IOException { + MappingsReader reader = CommandsUtil.getReader(enigma, path); + return reader.read(path, progress); } protected static File getWritableFile(String path) { diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java new file mode 100644 index 000000000..3c085c4db --- /dev/null +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java @@ -0,0 +1,44 @@ +package org.quiltmc.enigma.command; + +import com.google.common.io.MoreFiles; +import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; + +import javax.annotation.Nullable; +import java.nio.file.Path; + +public class CommandsUtil { + public static ReadWriteService getReadWriteService(Enigma enigma, Path file) { + String extension = MoreFiles.getFileExtension(file); + var service = enigma.getReadWriteService(extension); + if (service.isEmpty()) { + throw new UnsupportedOperationException("No reader/writer found for file type \"" + extension + "\""); + } + + return service.get(); + } + + public static MappingsReader getReader(Enigma enigma, Path file) { + String extension = MoreFiles.getFileExtension(file); + ReadWriteService service = getReadWriteService(enigma, file); + + if (!service.supportsReading()) { + throw new UnsupportedOperationException("Read/write service for file type \"" + extension + "\" does not support reading!"); + } + + return service; + } + + public static MappingsWriter getWriter(Enigma enigma, Path file) { + String extension = MoreFiles.getFileExtension(file); + ReadWriteService service = getReadWriteService(enigma, file); + + if (!service.supportsWriting()) { + throw new UnsupportedOperationException("Read/write service for file type \"" + extension + "\" does not support writing!"); + } + + return service; + } +} diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java index 4d1a8cf45..36724eb92 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java @@ -1,8 +1,8 @@ package org.quiltmc.enigma.command; import org.quiltmc.enigma.api.ProgressListener; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.util.MappingOperations; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; @@ -11,6 +11,7 @@ import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.util.Utils; +import javax.annotation.Nullable; import java.io.IOException; import java.nio.file.Path; @@ -18,20 +19,21 @@ public class ComposeMappingsCommand extends Command { public ComposeMappingsCommand() { super(Argument.LEFT_MAPPINGS.required(), Argument.RIGHT_MAPPINGS.required(), - Argument.OUTPUT_MAPPING_FORMAT.required(), Argument.MAPPING_OUTPUT.required(), - Argument.KEEP_MODE.required()); + Argument.KEEP_MODE.required() + ); } @Override public void run(String... args) throws IOException, MappingParseException { Path left = getReadablePath(this.getArg(args, 0)); Path right = getReadablePath(this.getArg(args, 1)); - String resultFormat = this.getArg(args, 2); - Path result = getWritablePath(this.getArg(args, 3)); - String keepMode = this.getArg(args, 4); + Path result = getWritablePath(this.getArg(args, 2)); + String keepMode = this.getArg(args, 3); + String obfuscatedNamespace = this.getArg(args, 4); + String deobfuscatedNamespace = this.getArg(args, 5); - run(left, right, resultFormat, result, keepMode); + run(left, right, result, keepMode, obfuscatedNamespace, deobfuscatedNamespace); } @Override @@ -44,17 +46,17 @@ public String getDescription() { return "Merges the two mapping trees (left and right) into a common (middle) name set, handling conflicts according to the given \"keep mode\"."; } - public static void run(Path leftFile, Path rightFile, String resultFormat, Path resultFile, String keepMode) throws IOException, MappingParseException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); + public static void run(Path leftFile, Path rightFile, Path resultFile, String keepMode, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws IOException, MappingParseException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); - MappingFormat leftFormat = MappingFormat.parseFromFile(leftFile); - EntryTree left = leftFormat.read(leftFile); - MappingFormat rightFormat = MappingFormat.parseFromFile(rightFile); - EntryTree right = rightFormat.read(rightFile); + MappingsReader leftReader = CommandsUtil.getReader(createEnigma(), leftFile); + EntryTree left = leftReader.read(leftFile); + MappingsReader rightReader = CommandsUtil.getReader(createEnigma(), rightFile); + EntryTree right = rightReader.read(rightFile); EntryTree result = MappingOperations.compose(left, right, keepMode.equals("left") || keepMode.equals("both"), keepMode.equals("right") || keepMode.equals("both")); + MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), resultFile); Utils.delete(resultFile); - MappingsWriter writer = MappingCommandsUtil.getWriter(resultFormat); writer.write(result, resultFile, ProgressListener.createEmpty(), saveParameters); } } diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java index b3d6dc7c9..6b9d76ce7 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java @@ -1,32 +1,36 @@ package org.quiltmc.enigma.command; import org.quiltmc.enigma.api.ProgressListener; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.util.Utils; +import javax.annotation.Nullable; import java.io.IOException; import java.nio.file.Path; public class ConvertMappingsCommand extends Command { public ConvertMappingsCommand() { super(Argument.INPUT_MAPPINGS.required(), - Argument.OUTPUT_MAPPING_FORMAT.required(), - Argument.MAPPING_OUTPUT.required()); + Argument.MAPPING_OUTPUT.required(), + Argument.OBFUSCATED_NAMESPACE.required(), + Argument.DEOBFUSCATED_NAMESPACE.required() + ); } @Override public void run(String... args) throws IOException, MappingParseException { Path source = getReadablePath(this.getArg(args, 0)); - String resultFormat = this.getArg(args, 1); - Path result = getWritablePath(this.getArg(args, 2)); + Path result = getWritablePath(this.getArg(args, 1)); + String obfuscatedNamespace = this.getArg(args, 2); + String deobfuscatedNamespace = this.getArg(args, 3); - run(source, resultFormat, result); + run(source, result, obfuscatedNamespace, deobfuscatedNamespace); } @Override @@ -39,14 +43,14 @@ public String getDescription() { return "Converts the provided mappings to a different format."; } - public static void run(Path source, String resultFormat, Path output) throws MappingParseException, IOException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); + public static void run(Path source, Path output, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws MappingParseException, IOException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); - MappingFormat format = MappingFormat.parseFromFile(source); - EntryTree mappings = format.read(source); + MappingsReader reader = CommandsUtil.getReader(createEnigma(), source); + EntryTree mappings = reader.read(source); Utils.delete(output); - MappingsWriter writer = MappingCommandsUtil.getWriter(resultFormat); + MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), output); writer.write(mappings, output, ProgressListener.createEmpty(), saveParameters); } } diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/DeobfuscateCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/DeobfuscateCommand.java index 24b377443..5944f409a 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/DeobfuscateCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/DeobfuscateCommand.java @@ -9,7 +9,8 @@ public class DeobfuscateCommand extends Command { public DeobfuscateCommand() { super(Argument.INPUT_JAR.required(), Argument.OUTPUT_JAR.required(), - Argument.INPUT_MAPPINGS.optional()); + Argument.INPUT_MAPPINGS.optional() + ); } @Override diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/DropInvalidMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/DropInvalidMappingsCommand.java index f398326fc..496e3049c 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/DropInvalidMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/DropInvalidMappingsCommand.java @@ -2,8 +2,8 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.ProgressListener; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; import org.tinylog.Logger; import java.io.IOException; @@ -46,7 +46,7 @@ public static void run(Path jarIn, Path mappingsIn, Path mappingsOut) throws Exc return; } - MappingFormat format = MappingFormat.parseFromFile(mappingsIn); + MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), mappingsIn); EnigmaProject project = openProject(jarIn, mappingsIn); Logger.info("Dropping invalid mappings..."); @@ -75,6 +75,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } MappingSaveParameters saveParameters = project.getEnigma().getProfile().getMappingSaveParameters(); - format.write(project.getRemapper().getMappings(), mappingsOut, ProgressListener.createEmpty(), saveParameters); + writer.write(project.getRemapper().getMappings(), mappingsOut, ProgressListener.createEmpty(), saveParameters); } } diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java index 4905313e1..e19728882 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java @@ -15,6 +15,7 @@ import org.quiltmc.enigma.util.Utils; import org.tinylog.Logger; +import javax.annotation.Nullable; import java.nio.file.Path; import java.util.List; @@ -23,8 +24,10 @@ protected FillClassMappingsCommand() { super(Argument.INPUT_JAR.required(), Argument.INPUT_MAPPINGS.required(), Argument.MAPPING_OUTPUT.required(), - Argument.OUTPUT_MAPPING_FORMAT.required(), - Argument.FILL_ALL.optional()); + Argument.FILL_ALL.optional(), + Argument.OBFUSCATED_NAMESPACE.optional(), + Argument.DEOBFUSCATED_NAMESPACE.optional() + ); } @Override @@ -32,10 +35,11 @@ public void run(String... args) throws Exception { Path inJar = getReadablePath(this.getArg(args, 0)); Path source = getReadablePath(this.getArg(args, 1)); Path result = getWritablePath(this.getArg(args, 2)); - String resultFormat = this.getArg(args, 3); - boolean fillAll = Boolean.parseBoolean(this.getArg(args, 4)); + boolean fillAll = Boolean.parseBoolean(this.getArg(args, 3)); + String obfuscatedNamespace = this.getArg(args, 4); + String deobfuscatedNamespace = this.getArg(args, 5); - run(inJar, source, result, resultFormat, fillAll); + run(inJar, source, result, fillAll, obfuscatedNamespace, deobfuscatedNamespace); } @Override @@ -48,19 +52,19 @@ public String getDescription() { return "Adds empty mappings for classes missing in the input file, but whose parent or ancestors, do have names"; } - public static void run(Path jar, Path source, Path result, String resultFormat, boolean fillAll) throws Exception { + public static void run(Path jar, Path source, Path result, boolean fillAll, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws Exception { boolean debug = shouldDebug(new FillClassMappingsCommand().getName()); JarIndex jarIndex = loadJar(jar); Logger.info("Reading mappings..."); - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); - EntryTree sourceMappings = readMappings(source, ProgressListener.createEmpty()); + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); + EntryTree sourceMappings = readMappings(createEnigma(), source, ProgressListener.createEmpty()); EntryTree resultMappings = exec(jarIndex, sourceMappings, fillAll, debug); Logger.info("Writing mappings..."); + MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), result); Utils.delete(result); - MappingsWriter writer = MappingCommandsUtil.getWriter(resultFormat); writer.write(resultMappings, result, ProgressListener.createEmpty(), saveParameters); if (debug) { diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java index ccccb82f2..ca73896e2 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java @@ -6,6 +6,7 @@ import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.EnigmaPlugin; import org.quiltmc.enigma.api.service.NameProposalService; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; @@ -31,8 +32,10 @@ public InsertProposedMappingsCommand() { super(Argument.INPUT_JAR.required(), Argument.INPUT_MAPPINGS.required(), Argument.MAPPING_OUTPUT.required(), - Argument.OUTPUT_MAPPING_FORMAT.required(), - Argument.ENIGMA_PROFILE.optional()); + Argument.ENIGMA_PROFILE.optional(), + Argument.OBFUSCATED_NAMESPACE.optional(), + Argument.DEOBFUSCATED_NAMESPACE.optional() + ); } @Override @@ -40,10 +43,11 @@ public void run(String... args) throws Exception { Path inJar = getReadablePath(this.getArg(args, 0)); Path source = getReadablePath(this.getArg(args, 1)); Path output = getWritablePath(this.getArg(args, 2)); - String resultFormat = this.getArg(args, 3); - Path profilePath = getReadablePath(this.getArg(args, 4)); + Path profilePath = getReadablePath(this.getArg(args, 3)); + String obfuscatedNamespace = this.getArg(args, 4); + String deobfuscatedNamespace = this.getArg(args, 5); - run(inJar, source, output, resultFormat, profilePath, null); + run(inJar, source, output, profilePath, null, obfuscatedNamespace, deobfuscatedNamespace); } @Override @@ -56,14 +60,14 @@ public String getDescription() { return "Adds all mappings proposed by the plugins on the classpath and declared in the profile into the given mappings."; } - public static void run(Path inJar, Path source, Path output, String resultFormat, @Nullable Path profilePath, @Nullable Iterable plugins) throws Exception { + public static void run(Path inJar, Path source, Path output, @Nullable Path profilePath, @Nullable Iterable plugins, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws Exception { EnigmaProfile profile = EnigmaProfile.read(profilePath); Enigma enigma = createEnigma(profile, plugins); - run(inJar, source, output, resultFormat, enigma); + run(inJar, source, output, enigma, obfuscatedNamespace, deobfuscatedNamespace); } - public static void run(Path inJar, Path source, Path output, String resultFormat, Enigma enigma) throws Exception { + public static void run(Path inJar, Path source, Path output, Enigma enigma, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws Exception { boolean debug = shouldDebug(new InsertProposedMappingsCommand().getName()); int nameProposalServices = enigma.getServices().get(NameProposalService.TYPE).size(); if (nameProposalServices == 0) { @@ -76,8 +80,9 @@ public static void run(Path inJar, Path source, Path output, String resultFormat printStats(project); Utils.delete(output); - MappingSaveParameters saveParameters = new MappingSaveParameters(enigma.getProfile().getMappingSaveParameters().fileNameFormat(), true); - MappingsWriter writer = MappingCommandsUtil.getWriter(resultFormat); + MappingSaveParameters saveParameters = new MappingSaveParameters(enigma.getProfile().getMappingSaveParameters().fileNameFormat(), true, obfuscatedNamespace, deobfuscatedNamespace); + + MappingsWriter writer = CommandsUtil.getWriter(enigma, output); writer.write(mappings, output, ProgressListener.createEmpty(), saveParameters); if (debug) { diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java index cc5173598..882b0d49f 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java @@ -1,32 +1,35 @@ package org.quiltmc.enigma.command; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.util.MappingOperations; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.util.Utils; +import javax.annotation.Nullable; import java.io.IOException; import java.nio.file.Path; public class InvertMappingsCommand extends Command { public InvertMappingsCommand() { super(Argument.INPUT_MAPPINGS.required(), - Argument.OUTPUT_MAPPING_FORMAT.required(), - Argument.OUTPUT_FOLDER.required()); + Argument.OUTPUT_FOLDER.required(), + Argument.OBFUSCATED_NAMESPACE.optional(), + Argument.DEOBFUSCATED_NAMESPACE.optional() + ); } @Override public void run(String... args) throws IOException, MappingParseException { Path source = getReadablePath(this.getArg(args, 0)); - String resultFormat = this.getArg(args, 1); Path result = getWritablePath(this.getArg(args, 2)); + String obfuscatedNamespace = this.getArg(args, 3); + String deobfuscatedNamespace = this.getArg(args, 4); - run(source, resultFormat, result); + run(source, result, obfuscatedNamespace, deobfuscatedNamespace); } @Override @@ -39,15 +42,17 @@ public String getDescription() { return "Flips the source names with the destination names, ie. 'class a -> Example' becomes 'class Example -> a'."; } - public static void run(Path sourceFile, String resultFormat, Path resultFile) throws MappingParseException, IOException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); - MappingFormat format = MappingFormat.parseFromFile(sourceFile); + public static void run(Path sourceFile, Path resultFile, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws MappingParseException, IOException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); + Enigma enigma = Enigma.builder().build(); - EntryTree source = format.read(sourceFile); + var readService = CommandsUtil.getReader(enigma, sourceFile); + var writeService = CommandsUtil.getWriter(enigma, resultFile); + + EntryTree source = readService.read(sourceFile); EntryTree result = MappingOperations.invert(source); Utils.delete(resultFile); - MappingsWriter writer = MappingCommandsUtil.getWriter(resultFormat); - writer.write(result, resultFile, saveParameters); + writeService.write(result, resultFile, saveParameters); } } diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java index 3f4c31e28..6ae3dba52 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java @@ -6,9 +6,9 @@ import org.quiltmc.enigma.api.translation.Translator; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; import org.quiltmc.enigma.api.translation.mapping.tree.DeltaTrackingTree; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; @@ -17,6 +17,7 @@ import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import org.quiltmc.enigma.util.Utils; +import javax.annotation.Nullable; import java.io.IOException; import java.nio.file.Path; import java.util.Map; @@ -25,18 +26,21 @@ public class MapSpecializedMethodsCommand extends Command { public MapSpecializedMethodsCommand() { super(Argument.INPUT_JAR.required(), Argument.INPUT_MAPPINGS.required(), - Argument.OUTPUT_MAPPING_FORMAT.required(), - Argument.MAPPING_OUTPUT.required()); + Argument.MAPPING_OUTPUT.required(), + Argument.OBFUSCATED_NAMESPACE.optional(), + Argument.DEOBFUSCATED_NAMESPACE.optional() + ); } @Override public void run(String... args) throws IOException, MappingParseException { Path jar = getReadablePath(this.getArg(args, 0)); Path source = getReadablePath(this.getArg(args, 1)); - String resultFormat = this.getArg(args, 2); - Path result = getWritablePath(this.getArg(args, 3)); + Path result = getWritablePath(this.getArg(args, 2)); + String obfuscatedNamespace = this.getArg(args, 3); + String deobfuscatedNamespace = this.getArg(args, 4); - run(jar, source, resultFormat, result); + run(jar, source, result, obfuscatedNamespace, deobfuscatedNamespace); } @Override @@ -49,18 +53,18 @@ public String getDescription() { return "Adds names for specialized methods from their corresponding bridge method"; } - public static void run(Path jar, Path sourcePath, String resultFormat, Path output) throws IOException, MappingParseException { + public static void run(Path jar, Path sourcePath, Path output, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws IOException, MappingParseException { boolean debug = shouldDebug(new MapSpecializedMethodsCommand().getName()); JarIndex jarIndex = loadJar(jar); - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); - MappingFormat sourceFormat = MappingFormat.parseFromFile(sourcePath); - EntryTree source = sourceFormat.read(sourcePath); + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); + MappingsReader reader = CommandsUtil.getReader(createEnigma(), sourcePath); + EntryTree source = reader.read(sourcePath); EntryTree result = run(jarIndex, source, debug); Utils.delete(output); - MappingsWriter writer = MappingCommandsUtil.getWriter(resultFormat); + MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), output); writer.write(result, output, saveParameters); if (debug) { diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/MappingCommandsUtil.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/MappingCommandsUtil.java deleted file mode 100644 index 6fdd3bd67..000000000 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/MappingCommandsUtil.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.quiltmc.enigma.command; - -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; -import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; - -public final class MappingCommandsUtil { - private MappingCommandsUtil() { - } - - public static MappingsWriter getWriter(String type) { - if (type.toLowerCase().startsWith("tinyv2:") || type.toLowerCase().startsWith("tiny_v2:")) { - String[] split = type.split(":"); - - if (split.length != 3) { - throw new IllegalArgumentException("specify column names as 'tinyv2:from_namespace:to_namespace'"); - } - - return new TinyV2Writer(split[1], split[2]); - } - - MappingFormat format; - try { - format = MappingFormat.valueOf(type); - } catch (IllegalArgumentException ignored) { - format = MappingFormat.valueOf(type.toUpperCase()); - } - - return format.getWriter(); - } -} diff --git a/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java b/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java index fbc08c60e..34300ea33 100644 --- a/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java +++ b/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java @@ -1,8 +1,9 @@ package org.quiltmc.enigma.command; import org.quiltmc.enigma.TestUtil; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; @@ -47,9 +48,10 @@ public class FillClassMappingsCommandTest extends CommandTest { @Test public void test() throws Exception { Path resultFile = Files.createTempFile("fillClassMappings", ".mappings"); - FillClassMappingsCommand.run(JAR, MAPPINGS, resultFile, MappingFormat.ENIGMA_FILE.name(), false); + FillClassMappingsCommand.run(JAR, MAPPINGS, resultFile, false, null, null); - EntryTree result = MappingFormat.ENIGMA_FILE.read(resultFile); + MappingsReader reader = CommandsUtil.getReader(Enigma.create(), resultFile); + EntryTree result = reader.read(resultFile); assertEquals("A_Anonymous", getName(result, A)); assertNotNull(result.findNode(A_ANONYMOUS)); diff --git a/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java b/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java index 2e1e01e2a..8161e4b47 100644 --- a/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java +++ b/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java @@ -1,9 +1,10 @@ package org.quiltmc.enigma.command; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.TestUtil; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; @@ -60,9 +61,10 @@ public class MapSpecializedMethodsCommandTest extends CommandTest { @Test public void test() throws Exception { Path resultFile = Files.createTempFile("mapSpecializedMethods", ".mappings"); - MapSpecializedMethodsCommand.run(JAR, MAPPINGS, MappingFormat.ENIGMA_FILE.name(), resultFile); + MapSpecializedMethodsCommand.run(JAR, MAPPINGS, resultFile, null, null); - EntryTree result = MappingFormat.ENIGMA_FILE.read(resultFile, ProgressListener.createEmpty()); + MappingsReader reader = CommandsUtil.getReader(Enigma.create(), resultFile); + EntryTree result = reader.read(resultFile, ProgressListener.createEmpty()); assertNotNull(result.findNode(BASE_CLASS)); assertEquals("foo", getName(result, BASE_FOO_1)); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 3a481e4f5..3ade21b82 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -144,6 +144,19 @@ public List getSupportedFileTypes() { return this.getReadWriteServices().stream().map(ReadWriteService::getFileType).toList(); } + public Optional getFileType(String extension) { + return this.getSupportedFileTypes().stream().filter(type -> type.getExtensions().contains(extension)).findFirst(); + } + + public Optional getReadWriteService(String extension) { + var fileType = this.getFileType(extension); + if (fileType.isPresent()) { + return this.getReadWriteService(fileType.get()); + } + + return Optional.empty(); + } + public Optional getReadWriteService(FileType fileType) { return this.getReadWriteServices().stream().filter(service -> service.getFileType().equals(fileType)).findFirst(); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java index 4ab1a121b..73204e737 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java @@ -33,7 +33,7 @@ public final class EnigmaProfile { public static final EnigmaProfile EMPTY = new EnigmaProfile(new ServiceContainer(Map.of())); - private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); + private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, null, null); private static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ServiceContainer.class, (JsonDeserializer) EnigmaProfile::loadServiceContainer) .create(); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java index 76315a9b3..5646c1503 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java @@ -19,18 +19,40 @@ public interface ReadWriteService extends EnigmaService, MappingsWriter, Mapping FileType getFileType(); - static ReadWriteService create(MappingsReader reader, MappingsWriter writer, FileType fileType, String id) { + boolean supportsReading(); + + boolean supportsWriting(); + + static ReadWriteService create(@Nullable MappingsReader reader, @Nullable MappingsWriter writer, FileType fileType, String id) { return new ReadWriteService() { @Override public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + if (writer == null) { + throw new UnsupportedOperationException("This service does not support writing!"); + } + writer.write(obfNamespace, deobfNamespace, mappings, delta, path, progress, saveParameters); } @Override public EntryTree read(Path path, ProgressListener progress) throws MappingParseException, IOException { + if (reader == null) { + throw new UnsupportedOperationException("This service does not support reading!"); + } + return reader.read(path, progress); } + @Override + public boolean supportsReading() { + return reader != null; + } + + @Override + public boolean supportsWriting() { + return writer != null; + } + @Override public String getId() { return id; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java index 93449b13d..8c9462b6e 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java @@ -2,9 +2,18 @@ import com.google.gson.annotations.SerializedName; +/** + * Defines a strategy for naming files in directory-based mapping formats. + */ public enum MappingFileNameFormat { + /** + * Names files based on their obfuscated names. + */ @SerializedName("by_obf") BY_OBF, + /** + * Names files based on their deobfuscated names. + */ @SerializedName("by_deobf") BY_DEOBF } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java index 54da2b6b4..2da63d8de 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java @@ -2,7 +2,17 @@ import com.google.gson.annotations.SerializedName; -public record MappingSaveParameters(@SerializedName("file_name_format") MappingFileNameFormat fileNameFormat, @SerializedName("write_proposed_names") boolean writeProposedNames) { +import javax.annotation.Nullable; + +public record MappingSaveParameters( + @SerializedName("file_name_format") MappingFileNameFormat fileNameFormat, + @SerializedName("write_proposed_names") boolean writeProposedNames, + @SerializedName("obfuscated_namespace") @Nullable String obfuscatedNamespace, + @SerializedName("deobfuscated_namespace") @Nullable String deobfuscatedNamespace +) { + /** + * Controls how individual files will be named in directory-based mapping formats. + */ @Override public MappingFileNameFormat fileNameFormat() { return this.fileNameFormat; @@ -17,4 +27,22 @@ public MappingFileNameFormat fileNameFormat() { public boolean writeProposedNames() { return this.writeProposedNames; } + + /** + * The namespace of the mappings' obfuscated names. This will be saved in certain mapping formats. + * If null, the format will define a default namespace. + */ + @Override + public String obfuscatedNamespace() { + return this.obfuscatedNamespace; + } + + /** + * The namespace of the mappings' deobfuscated names. This will be saved in certain mapping formats. + * If null, the format will define a default namespace. + */ + @Override + public String deobfuscatedNamespace() { + return this.deobfuscatedNamespace; + } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java index c85df21fc..d8892e206 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -53,6 +53,16 @@ public FileType getFileType() { return new FileType.File("tiny"); } + @Override + public boolean supportsReading() { + return true; + } + + @Override + public boolean supportsWriting() { + return true; + } + @Override public String getId() { return "tiny_v2"; diff --git a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java index f5fdb7d38..140b42474 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java @@ -1,6 +1,5 @@ package org.quiltmc.enigma; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -10,13 +9,13 @@ public class MappingFormatDetectionTest { @Test void testFormatDetection() { - File formatsDir = new File(TestUtil.getResource("/formats").toUri()); - - for (File file : Objects.requireNonNull(formatsDir.listFiles())) { - MappingFormat parsedFormat = MappingFormat.parseFromFile(file.toPath()); - MappingFormat expectedFormat = MappingFormat.valueOf(file.getName().toUpperCase().split("_EXAMPLE_MAPPING")[0]); - - Assertions.assertSame(expectedFormat, parsedFormat, "Failed to detect format for " + file.getName()); - } +// File formatsDir = new File(TestUtil.getResource("/formats").toUri()); +// +// for (File file : Objects.requireNonNull(formatsDir.listFiles())) { +// MappingFormat parsedFormat = MappingFormat.parseFromFile(file.toPath()); +// MappingFormat expectedFormat = MappingFormat.valueOf(file.getName().toUpperCase().split("_EXAMPLE_MAPPING")[0]); +// +// Assertions.assertSame(expectedFormat, parsedFormat, "Failed to detect format for " + file.getName()); +// } } } From 682972c9643f7dd002926454a915cb18be77a1da Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 10:25:10 -0500 Subject: [PATCH 08/45] add non functional activeByDefault parameter to service types, move DecompilerService --- .../enigma/command/DecompileCommand.java | 2 +- .../org/quiltmc/enigma/gui/GuiController.java | 2 +- .../quiltmc/enigma/gui/config/Decompiler.java | 2 +- .../java/org/quiltmc/enigma/api/Enigma.java | 10 ++++--- .../org/quiltmc/enigma/api/EnigmaProject.java | 2 +- .../api/class_handle/ClassHandleProvider.java | 2 +- .../enigma/api/service/DecompilerService.java | 11 ++++++++ .../enigma/api/service/EnigmaServiceType.java | 27 ++++++++++++++----- .../enigma/api/service/JarIndexerService.java | 2 +- .../api/service/NameProposalService.java | 2 +- .../api/service/ObfuscationTestService.java | 2 +- .../enigma/api/service/ReadWriteService.java | 6 ++--- .../enigma/api/source/DecompilerService.java | 11 -------- .../enigma/api/source/Decompilers.java | 1 + .../mapping/serde/MappingsWriter.java | 11 +++----- .../serde/enigma/EnigmaMappingsWriter.java | 6 ++--- .../mapping/serde/srg/SrgMappingsWriter.java | 2 +- .../mapping/serde/tinyv2/TinyV2Writer.java | 4 ++- .../impl/plugin/BuiltinMappingFormats.java | 4 +-- .../enigma/impl/plugin/BuiltinPlugin.java | 2 +- .../org/quiltmc/enigma/DecompilationTest.java | 2 +- .../org/quiltmc/enigma/EnigmaProfileTest.java | 2 +- .../enigma/TestJarIndexBridgeMethods.java | 2 +- .../java/org/quiltmc/enigma/TokenChecker.java | 2 +- .../translation/mapping/TestComments.java | 4 +-- .../mapping/TestDeterministicWrite.java | 6 +++-- .../mapping/TestReadWriteCycle.java | 12 +++++---- .../translation/mapping/TestV2Main.java | 5 ++-- 28 files changed, 84 insertions(+), 62 deletions(-) create mode 100644 enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java delete mode 100644 enigma/src/main/java/org/quiltmc/enigma/api/source/DecompilerService.java diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/DecompileCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/DecompileCommand.java index bfafc2a36..8e3c7446a 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/DecompileCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/DecompileCommand.java @@ -3,7 +3,7 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.EnigmaProject.DecompileErrorStrategy; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Decompilers; import org.tinylog.Logger; diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java index 852f529ca..cc914b0f2 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java @@ -39,7 +39,7 @@ import org.quiltmc.enigma.network.packet.c2s.LoginC2SPacket; import org.quiltmc.enigma.network.packet.Packet; import org.quiltmc.enigma.api.source.DecompiledClassSource; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.SourceIndex; import org.quiltmc.enigma.api.source.Token; import org.quiltmc.enigma.api.stats.StatsGenerator; diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Decompiler.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Decompiler.java index e66af2967..76ba3da26 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Decompiler.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/config/Decompiler.java @@ -2,7 +2,7 @@ import org.quiltmc.enigma.gui.Gui; import org.quiltmc.enigma.gui.dialog.decompiler.VineflowerSettingsDialog; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Decompilers; import java.util.function.BiConsumer; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 3ade21b82..0664ab70f 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -144,12 +144,16 @@ public List getSupportedFileTypes() { return this.getReadWriteServices().stream().map(ReadWriteService::getFileType).toList(); } - public Optional getFileType(String extension) { - return this.getSupportedFileTypes().stream().filter(type -> type.getExtensions().contains(extension)).findFirst(); + public Optional getFileType(String extension, boolean isDirectory) { + return this.getSupportedFileTypes().stream().filter(type -> type.getExtensions().contains(extension) && type.isDirectory() == isDirectory).findFirst(); } public Optional getReadWriteService(String extension) { - var fileType = this.getFileType(extension); + return getReadWriteService(extension, false); + } + + public Optional getReadWriteService(String extension, boolean isDirectory) { + var fileType = this.getFileType(extension, isDirectory); if (fileType.isPresent()) { return this.getReadWriteService(fileType.get()); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java index d7c3a371c..09a9204d2 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java @@ -15,7 +15,7 @@ import org.quiltmc.enigma.api.class_provider.ClassProvider; import org.quiltmc.enigma.api.class_provider.ObfuscationFixClassProvider; import org.quiltmc.enigma.api.source.Decompiler; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.SourceSettings; import org.quiltmc.enigma.api.translation.Translator; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/class_handle/ClassHandleProvider.java b/enigma/src/main/java/org/quiltmc/enigma/api/class_handle/ClassHandleProvider.java index 1a686ecff..bff160b36 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/class_handle/ClassHandleProvider.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/class_handle/ClassHandleProvider.java @@ -7,7 +7,7 @@ import org.quiltmc.enigma.api.event.ClassHandleListener; import org.quiltmc.enigma.api.source.DecompiledClassSource; import org.quiltmc.enigma.api.source.Decompiler; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Source; import org.quiltmc.enigma.api.source.SourceIndex; import org.quiltmc.enigma.api.source.SourceSettings; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java new file mode 100644 index 000000000..3ae00f0ec --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java @@ -0,0 +1,11 @@ +package org.quiltmc.enigma.api.service; + +import org.quiltmc.enigma.api.class_provider.ClassProvider; +import org.quiltmc.enigma.api.source.Decompiler; +import org.quiltmc.enigma.api.source.SourceSettings; + +public interface DecompilerService extends EnigmaService { + EnigmaServiceType TYPE = new EnigmaServiceType<>("decompiler", false); + + Decompiler create(ClassProvider classProvider, SourceSettings settings); +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java index 0c7d08f72..b4572b420 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java @@ -1,14 +1,29 @@ package org.quiltmc.enigma.api.service; -public final class EnigmaServiceType { - public final String key; +public record EnigmaServiceType( + String key, + boolean activeByDefault +) { + /** + * The unique key of this service type. + */ + @Override + public String key() { + return this.key; + } - private EnigmaServiceType(String key) { - this.key = key; + /** + * Whether this service type is active by default. + * If {@code true}, this service type will be active without being explicitly enabled in the profile. + */ + @Override + public boolean activeByDefault() { + return this.activeByDefault; } - public static EnigmaServiceType create(String key) { - return new EnigmaServiceType<>(key); + @Override + public String toString() { + return this.key; } @Override diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java index d1d3a03af..66c8596a3 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java @@ -8,7 +8,7 @@ import java.util.Set; public interface JarIndexerService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("jar_indexer"); + EnigmaServiceType TYPE = new EnigmaServiceType<>("jar_indexer", false); void acceptJar(Set scope, ClassProvider classProvider, JarIndex jarIndex); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java index 094d46928..432d766ee 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java @@ -13,7 +13,7 @@ * A name proposal service suggests default names for entries based on context from their types and surrounding mappings. */ public interface NameProposalService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("name_proposal"); + EnigmaServiceType TYPE = new EnigmaServiceType<>("name_proposal", false); /** * Runs when a new JAR file is opened. Note that at this point, no mapping context will exist in the remapper. diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java index 3783f8b56..9e76e581b 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java @@ -3,7 +3,7 @@ import org.quiltmc.enigma.api.translation.representation.entry.Entry; public interface ObfuscationTestService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("obfuscation_test"); + EnigmaServiceType TYPE = new EnigmaServiceType<>("obfuscation_test", false); boolean testDeobfuscated(Entry entry); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java index 5646c1503..83c5604a0 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java @@ -15,7 +15,7 @@ import java.nio.file.Path; public interface ReadWriteService extends EnigmaService, MappingsWriter, MappingsReader { - EnigmaServiceType TYPE = EnigmaServiceType.create("read_write"); + EnigmaServiceType TYPE = new EnigmaServiceType<>("read_write", true); FileType getFileType(); @@ -26,12 +26,12 @@ public interface ReadWriteService extends EnigmaService, MappingsWriter, Mapping static ReadWriteService create(@Nullable MappingsReader reader, @Nullable MappingsWriter writer, FileType fileType, String id) { return new ReadWriteService() { @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { if (writer == null) { throw new UnsupportedOperationException("This service does not support writing!"); } - writer.write(obfNamespace, deobfNamespace, mappings, delta, path, progress, saveParameters); + writer.write(mappings, delta, path, progress, saveParameters); } @Override diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/source/DecompilerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/source/DecompilerService.java deleted file mode 100644 index a9d3030cb..000000000 --- a/enigma/src/main/java/org/quiltmc/enigma/api/source/DecompilerService.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.quiltmc.enigma.api.source; - -import org.quiltmc.enigma.api.class_provider.ClassProvider; -import org.quiltmc.enigma.api.service.EnigmaService; -import org.quiltmc.enigma.api.service.EnigmaServiceType; - -public interface DecompilerService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("decompiler"); - - Decompiler create(ClassProvider classProvider, SourceSettings settings); -} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/source/Decompilers.java b/enigma/src/main/java/org/quiltmc/enigma/api/source/Decompilers.java index 62eeec5c6..1c61a2779 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/source/Decompilers.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/source/Decompilers.java @@ -1,6 +1,7 @@ package org.quiltmc.enigma.api.source; import org.quiltmc.enigma.api.class_provider.ClassProvider; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.impl.source.bytecode.BytecodeDecompiler; import org.quiltmc.enigma.impl.source.cfr.CfrDecompiler; import org.quiltmc.enigma.impl.source.procyon.ProcyonDecompiler; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java index f585aee1e..5b6946b1f 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsWriter.java @@ -8,22 +8,17 @@ import org.quiltmc.enigma.api.translation.mapping.tree.EntryTreeNode; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; -import javax.annotation.Nullable; import java.nio.file.Path; public interface MappingsWriter { - void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); - - default void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - this.write(null, null, mappings, delta, path, progress, saveParameters); - } + void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); default void write(EntryTree mappings, Path path, MappingSaveParameters saveParameters) { - this.write(null, null, mappings, MappingDelta.added(mappings), path, ProgressListener.createEmpty(), saveParameters); + this.write(mappings, MappingDelta.added(mappings), path, ProgressListener.createEmpty(), saveParameters); } default void write(EntryTree mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - this.write(null, null, mappings, MappingDelta.added(mappings), path, progress, saveParameters); + this.write(mappings, MappingDelta.added(mappings), path, progress, saveParameters); } /** diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java index ef4bf897c..c38385539 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java @@ -43,7 +43,7 @@ public enum EnigmaMappingsWriter implements MappingsWriter { FILE { @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { EntryTree writtenMappings = MappingsWriter.filterMappings(mappings, saveParameters); Collection classes = writtenMappings.getRootNodes() @@ -66,7 +66,7 @@ public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace }, DIRECTORY { @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { EntryTree writtenMappings = MappingsWriter.filterMappings(mappings, saveParameters); Collection changedClasses = delta.getChangedRoots() @@ -162,7 +162,7 @@ private Path resolve(Path root, ClassEntry classEntry) { }, ZIP { @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(EntryTree mappings, MappingDelta delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) { DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters); } catch (IOException e) { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java index cd1a9ead7..25d49814f 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java @@ -33,7 +33,7 @@ public enum SrgMappingsWriter implements MappingsWriter { INSTANCE; @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { EntryTree writtenMappings = MappingsWriter.filterMappings(mappings, saveParameters); try { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java index 9547cfa39..1bd4aaaa9 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -74,11 +74,13 @@ private static Comparator> mappingComparator() { } @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { + String obfNamespace = parameters.obfuscatedNamespace(); if (obfNamespace == null) { obfNamespace = "intermediary"; } + String deobfNamespace = parameters.deobfuscatedNamespace(); if (deobfNamespace == null) { deobfNamespace = "named"; } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java index d8892e206..efda1471f 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -39,8 +39,8 @@ public static void register(EnigmaPluginContext ctx) { static final TinyV2Writer writer = new TinyV2Writer(); @Override - public void write(@Nullable String obfNamespace, @Nullable String deobfNamespace, EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - writer.write(obfNamespace, deobfNamespace, mappings, delta, path, progress, saveParameters); + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + writer.write(mappings, delta, path, progress, saveParameters); } @Override diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java index b8bff4f9d..24c2f9c0d 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinPlugin.java @@ -7,7 +7,7 @@ import org.quiltmc.enigma.api.analysis.index.jar.JarIndex; import org.quiltmc.enigma.api.service.JarIndexerService; import org.quiltmc.enigma.api.service.NameProposalService; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Decompilers; import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; diff --git a/enigma/src/test/java/org/quiltmc/enigma/DecompilationTest.java b/enigma/src/test/java/org/quiltmc/enigma/DecompilationTest.java index 225fe11fb..854eeb4fc 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/DecompilationTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/DecompilationTest.java @@ -4,7 +4,7 @@ import org.quiltmc.enigma.api.class_provider.CachingClassProvider; import org.quiltmc.enigma.api.class_provider.ClassProvider; import org.quiltmc.enigma.api.class_provider.JarClassProvider; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Decompilers; import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import org.junit.jupiter.params.ParameterizedTest; diff --git a/enigma/src/test/java/org/quiltmc/enigma/EnigmaProfileTest.java b/enigma/src/test/java/org/quiltmc/enigma/EnigmaProfileTest.java index fdcf408f1..8114abe5a 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/EnigmaProfileTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/EnigmaProfileTest.java @@ -6,7 +6,7 @@ import org.quiltmc.enigma.api.EnigmaProfile; import org.quiltmc.enigma.api.service.JarIndexerService; import org.quiltmc.enigma.api.service.NameProposalService; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.util.Either; diff --git a/enigma/src/test/java/org/quiltmc/enigma/TestJarIndexBridgeMethods.java b/enigma/src/test/java/org/quiltmc/enigma/TestJarIndexBridgeMethods.java index 8b4202b57..e5de80399 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/TestJarIndexBridgeMethods.java +++ b/enigma/src/test/java/org/quiltmc/enigma/TestJarIndexBridgeMethods.java @@ -7,7 +7,7 @@ import org.quiltmc.enigma.api.class_provider.CachingClassProvider; import org.quiltmc.enigma.api.class_provider.JarClassProvider; import org.quiltmc.enigma.api.class_provider.ObfuscationFixClassProvider; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Decompilers; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.hamcrest.Matchers; diff --git a/enigma/src/test/java/org/quiltmc/enigma/TokenChecker.java b/enigma/src/test/java/org/quiltmc/enigma/TokenChecker.java index e202d9f11..e7644288b 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/TokenChecker.java +++ b/enigma/src/test/java/org/quiltmc/enigma/TokenChecker.java @@ -5,7 +5,7 @@ import org.quiltmc.enigma.api.class_provider.ClassProvider; import org.quiltmc.enigma.api.class_provider.JarClassProvider; import org.quiltmc.enigma.api.source.Decompiler; -import org.quiltmc.enigma.api.source.DecompilerService; +import org.quiltmc.enigma.api.service.DecompilerService; import org.quiltmc.enigma.api.source.Source; import org.quiltmc.enigma.api.source.SourceIndex; import org.quiltmc.enigma.api.source.SourceSettings; diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java index e85b9f2ca..8095ea3be 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java @@ -20,10 +20,10 @@ public class TestComments { @Test public void testParseAndWrite() throws IOException, MappingParseException { - MappingSaveParameters params = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); + MappingSaveParameters params = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, "intermediary", "named"); EntryTree mappings = EnigmaMappingsReader.DIRECTORY.read( DIRECTORY); - new TinyV2Writer().write("intermediary", "named", mappings, MappingDelta.added(mappings), DIRECTORY.resolve("convertedtiny.tiny"), ProgressListener.createEmpty(), params); + new TinyV2Writer().write(mappings, MappingDelta.added(mappings), DIRECTORY.resolve("convertedtiny.tiny"), ProgressListener.createEmpty(), params); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java index e550a3d44..0fc70a1d7 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; @@ -25,18 +26,19 @@ public class TestDeterministicWrite { @Test public void testTinyV2() throws Exception { Path dir = Files.createTempDirectory("enigmaDeterministicTinyV2-"); + Enigma enigma = Enigma.create(); EntryTree mappings = randomMappingTree(1L); String prev = null; for (int i = 0; i < 32; i++) { Path file = dir.resolve(i + ".tiny"); - MappingFormat.TINY_V2.write(mappings, file, ProgressListener.createEmpty(), null); + enigma.getReadWriteService("tiny").get().write(mappings, file, ProgressListener.createEmpty(), null); String content = Files.readString(file); if (prev != null) Assertions.assertEquals(prev, content, "Iteration " + i + " has a different result from the previous one"); prev = content; - mappings = MappingFormat.TINY_V2.read(file, ProgressListener.createEmpty()); + mappings = enigma.getReadWriteService("tiny").get().read(file, ProgressListener.createEmpty()); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java index 61ccace9e..2493d1993 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.translation.mapping; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.source.TokenType; @@ -24,7 +25,8 @@ * Tests that a MappingFormat can write out a fixed set of mappings and read them back without losing any information. */ public class TestReadWriteCycle { - private final MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); + private final MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, null, null); + private final Enigma enigma = Enigma.create(); private final Pair testClazz = new Pair<>( new ClassEntry("a/b/c"), @@ -101,21 +103,21 @@ private void testReadWriteCycle(ReadWriteService readWriteService, String tmpNam @Test public void testEnigmaFile() throws IOException, MappingParseException { - this.testReadWriteCycle(MappingFormat.ENIGMA_FILE, ".enigma"); + this.testReadWriteCycle(this.enigma.getReadWriteService("mappings").get(), ".mapping"); } @Test public void testEnigmaDir() throws IOException, MappingParseException { - this.testReadWriteCycle(MappingFormat.ENIGMA_DIRECTORY, ".tmp"); + this.testReadWriteCycle(this.enigma.getReadWriteService("mappings", true).get(), ".tmp"); } @Test public void testEnigmaZip() throws IOException, MappingParseException { - this.testReadWriteCycle(MappingFormat.ENIGMA_ZIP, ".zip"); + this.testReadWriteCycle(this.enigma.getReadWriteService("zip").get(), ".zip"); } @Test public void testTinyV2() throws IOException, MappingParseException { - this.testReadWriteCycle(MappingFormat.TINY_V2, ".tiny"); + this.testReadWriteCycle(this.enigma.getReadWriteService("tiny").get(), ".tiny"); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java index 63c90a810..b83501492 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.translation.mapping; +import org.quiltmc.enigma.api.EnigmaProfile; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; @@ -16,10 +17,10 @@ public final class TestV2Main { public static void main(String... args) throws Exception { Path path = TestTinyV2InnerClasses.MAPPINGS; - MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false); + MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, "obf", "deobf"); EntryTree tree = EnigmaMappingsReader.DIRECTORY.read(path); - new TinyV2Writer().write("obf", "deobf", tree, MappingDelta.added(tree), Paths.get("currentYarn.tiny"), ProgressListener.createEmpty(), parameters); + new TinyV2Writer().write(tree, MappingDelta.added(tree), Paths.get("currentYarn.tiny"), ProgressListener.createEmpty(), parameters); } } From ce11f45344679780056bfbd92954976e79234d7e Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 10:25:31 -0500 Subject: [PATCH 09/45] decompiler service active by default --- .../java/org/quiltmc/enigma/api/service/DecompilerService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java index 3ae00f0ec..46d9a0979 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java @@ -5,7 +5,7 @@ import org.quiltmc.enigma.api.source.SourceSettings; public interface DecompilerService extends EnigmaService { - EnigmaServiceType TYPE = new EnigmaServiceType<>("decompiler", false); + EnigmaServiceType TYPE = new EnigmaServiceType<>("decompiler", true); Decompiler create(ClassProvider classProvider, SourceSettings settings); } From 6f3519a5e6e7e179440882de11d0dadcc06dec52 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 12:19:18 -0500 Subject: [PATCH 10/45] implement active by default --- .../java/org/quiltmc/enigma/api/Enigma.java | 15 ++++++- .../org/quiltmc/enigma/api/EnigmaProfile.java | 10 ++++- .../api/service/EnigmaServiceContext.java | 6 +++ .../quiltmc/enigma/ActiveByDefaultTest.java | 43 +++++++++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 0664ab70f..50284c860 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -29,6 +29,7 @@ import org.objectweb.asm.Opcodes; import org.tinylog.Logger; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -251,6 +252,11 @@ private static class PluginContext implements EnigmaPluginContext { public void registerService(EnigmaServiceType serviceType, EnigmaServiceFactory factory) { List serviceProfiles = this.profile.getServiceProfiles(serviceType); + if (serviceProfiles.isEmpty() && serviceType.activeByDefault()) { + this.services.put(serviceType, factory.create(this.getServiceContext(null))); + return; + } + for (EnigmaProfile.Service serviceProfile : serviceProfiles) { T service = factory.create(this.getServiceContext(serviceProfile)); if (serviceProfile.matches(service.getId())) { @@ -260,11 +266,11 @@ public void registerService(EnigmaServiceType servi } } - private EnigmaServiceContext getServiceContext(EnigmaProfile.Service serviceProfile) { + private EnigmaServiceContext getServiceContext(@Nullable EnigmaProfile.Service serviceProfile) { return new EnigmaServiceContext<>() { @Override public Optional>> getArgument(String key) { - return serviceProfile.getArgument(key); + return serviceProfile == null ? Optional.empty() : serviceProfile.getArgument(key); } @Override @@ -284,6 +290,11 @@ EnigmaServices buildServices() { for (EnigmaServiceType type : builtServices.keySet()) { List serviceProfiles = this.profile.getServiceProfiles(type); + if (serviceProfiles.isEmpty() && type.activeByDefault()) { + orderedServices.putAll(type, builtServices.get(type)); + continue; + } + for (EnigmaProfile.Service service : serviceProfiles) { for (EnigmaService registeredService : builtServices.get(type)) { if (service.matches(registeredService.getId())) { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java index 73204e737..178c5a274 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProfile.java @@ -96,11 +96,11 @@ private static ServiceContainer loadServiceContainer(JsonElement json, Type type } public List getServiceProfiles(EnigmaServiceType serviceType) { - return this.serviceProfiles.get(serviceType.key); + return this.serviceProfiles.get(serviceType.key()); } public MappingSaveParameters getMappingSaveParameters() { - //noinspection ConstantConditions + //noinspection ConstantConditions - this field is parsed by GSON return this.mappingSaveParameters == null ? EnigmaProfile.DEFAULT_MAPPING_SAVE_PARAMETERS : this.mappingSaveParameters; } @@ -109,6 +109,12 @@ private EnigmaProfile withSourcePath(Path sourcePath) { return this; } + /** + * Resolves the path relative to the parent directory of this profile. + * If the profile was not loaded from a file, the path is returned as-is. + * @param path the path to resolve + * @return the resolved path + */ public Path resolvePath(Path path) { if (this.sourcePath == null) { return path; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceContext.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceContext.java index c562bebf5..2c02e80bf 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceContext.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceContext.java @@ -21,6 +21,12 @@ default Optional> getMultipleArguments(String key) { return this.getArgument(key).flatMap(e -> e.right()); } + /** + * Resolves the path relative to the parent directory of the loaded {@link org.quiltmc.enigma.api.EnigmaProfile enigma profile}. + * If there is no profile or the profile was not loaded from a file, the path is returned as-is. + * @param path the path to resolve + * @return the resolved path + */ default Path getPath(String path) { return Path.of(path); } diff --git a/enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java b/enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java new file mode 100644 index 000000000..b47960d85 --- /dev/null +++ b/enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java @@ -0,0 +1,43 @@ +package org.quiltmc.enigma; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.EnigmaProfile; +import org.quiltmc.enigma.api.service.DecompilerService; +import org.quiltmc.enigma.api.service.NameProposalService; +import org.quiltmc.enigma.api.service.ReadWriteService; + +import java.io.Reader; +import java.io.StringReader; + +public class ActiveByDefaultTest { + @Test + public void testServicesLoaded() { + Enigma enigma = Enigma.builder().build(); + Assertions.assertFalse(enigma.getServices().get(DecompilerService.TYPE).isEmpty()); + Assertions.assertFalse(enigma.getServices().get(ReadWriteService.TYPE).isEmpty()); + Assertions.assertTrue(enigma.getServices().get(NameProposalService.TYPE).isEmpty()); + } + + @Test + public void testProfile() { + Reader r = new StringReader(""" + { + "services": { + "decompiler": { + "id": "enigma:vineflower" + } + } + }"""); + EnigmaProfile profile = EnigmaProfile.parse(r); + + // only vf should load since decompilers are explicitly defined + Enigma enigma = Enigma.builder().setProfile(profile).build(); + Assertions.assertEquals(1, enigma.getServices().get(DecompilerService.TYPE).size()); + Assertions.assertEquals("enigma:vineflower", enigma.getServices().get(DecompilerService.TYPE).get(0).getId()); + + // read write services should all be loaded + Assertions.assertFalse(enigma.getServices().get(ReadWriteService.TYPE).isEmpty()); + } +} From 01f4a30c5258054e85b9622ef96baf66081a4c61 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 12:49:13 -0500 Subject: [PATCH 11/45] cli tests passing! --- .../quiltmc/enigma/command/CommandsUtil.java | 13 ++++------- .../enigma/network/DedicatedEnigmaServer.java | 22 ++++++++++++------- .../org/quiltmc/enigma/gui/GuiController.java | 1 - .../java/org/quiltmc/enigma/api/Enigma.java | 19 ++++++++++++++-- .../org/quiltmc/enigma/TestTranslator.java | 2 +- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java index 3c085c4db..2ee99cd99 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java @@ -1,42 +1,37 @@ package org.quiltmc.enigma.command; -import com.google.common.io.MoreFiles; import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; -import javax.annotation.Nullable; import java.nio.file.Path; public class CommandsUtil { public static ReadWriteService getReadWriteService(Enigma enigma, Path file) { - String extension = MoreFiles.getFileExtension(file); - var service = enigma.getReadWriteService(extension); + var service = enigma.getReadWriteService(file); if (service.isEmpty()) { - throw new UnsupportedOperationException("No reader/writer found for file type \"" + extension + "\""); + throw new UnsupportedOperationException("No reader/writer found for file \"" + file + "\""); } return service.get(); } public static MappingsReader getReader(Enigma enigma, Path file) { - String extension = MoreFiles.getFileExtension(file); ReadWriteService service = getReadWriteService(enigma, file); if (!service.supportsReading()) { - throw new UnsupportedOperationException("Read/write service for file type \"" + extension + "\" does not support reading!"); + throw new UnsupportedOperationException("Read/write service for file \"" + file + "\" does not support reading!"); } return service; } public static MappingsWriter getWriter(Enigma enigma, Path file) { - String extension = MoreFiles.getFileExtension(file); ReadWriteService service = getReadWriteService(enigma, file); if (!service.supportsWriting()) { - throw new UnsupportedOperationException("Read/write service for file type \"" + extension + "\" does not support writing!"); + throw new UnsupportedOperationException("Read/write service for file \"" + file + "\" does not support writing!"); } return service; diff --git a/enigma-server/src/main/java/org/quiltmc/enigma/network/DedicatedEnigmaServer.java b/enigma-server/src/main/java/org/quiltmc/enigma/network/DedicatedEnigmaServer.java index 5a69fb13c..824cedeb6 100644 --- a/enigma-server/src/main/java/org/quiltmc/enigma/network/DedicatedEnigmaServer.java +++ b/enigma-server/src/main/java/org/quiltmc/enigma/network/DedicatedEnigmaServer.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.network; +import com.google.common.io.MoreFiles; import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpec; @@ -9,8 +10,8 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.class_provider.ClasspathClassProvider; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.translation.mapping.EntryRemapper; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.util.Utils; @@ -21,6 +22,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; @@ -28,7 +30,7 @@ public class DedicatedEnigmaServer extends EnigmaServer { private final EnigmaProfile profile; - private final MappingFormat mappingFormat; + private final ReadWriteService readWriteService; private final Path mappingsFile; private final PrintWriter log; private final BlockingQueue tasks = new LinkedBlockingDeque<>(); @@ -37,7 +39,7 @@ public DedicatedEnigmaServer( byte[] jarChecksum, char[] password, EnigmaProfile profile, - MappingFormat mappingFormat, + ReadWriteService readWriteService, Path mappingsFile, PrintWriter log, EntryRemapper mappings, @@ -45,7 +47,7 @@ public DedicatedEnigmaServer( ) { super(jarChecksum, password, mappings, port); this.profile = profile; - this.mappingFormat = mappingFormat; + this.readWriteService = readWriteService; this.mappingsFile = mappingsFile; this.log = log; } @@ -115,18 +117,22 @@ public static void main(String[] args) { Logger.info("Indexing Jar..."); EnigmaProject project = enigma.openJar(jar, new ClasspathClassProvider(), ProgressListener.createEmpty()); - MappingFormat mappingFormat = MappingFormat.parseFromFile(mappingsFile); + Optional readWriteService = enigma.getReadWriteService(mappingsFile); + if (readWriteService.isEmpty()) { + throw new IOException("Cannot read mapping file: unknown file type \"" + MoreFiles.getFileExtension(mappingsFile) + "\"!"); + } + EntryRemapper mappings; if (!Files.exists(mappingsFile)) { mappings = EntryRemapper.mapped(project.getJarIndex(), project.getMappingsIndex(), project.getRemapper().getJarProposedMappings(), new HashEntryTree<>(), enigma.getNameProposalServices()); } else { Logger.info("Reading mappings..."); - mappings = EntryRemapper.mapped(project.getJarIndex(), project.getMappingsIndex(), project.getRemapper().getJarProposedMappings(), mappingFormat.read(mappingsFile), enigma.getNameProposalServices()); + mappings = EntryRemapper.mapped(project.getJarIndex(), project.getMappingsIndex(), project.getRemapper().getJarProposedMappings(), readWriteService.get().read(mappingsFile), enigma.getNameProposalServices()); } PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile)); - server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port); + server = new DedicatedEnigmaServer(checksum, password, profile, readWriteService.get(), mappingsFile, log, mappings, port); server.start(); Logger.info("Server started"); } catch (IOException | MappingParseException e) { @@ -154,7 +160,7 @@ public synchronized void stop() { } private void saveMappings() { - this.mappingFormat.write(this.getRemapper().getMappings(), this.getRemapper().takeMappingDelta(), this.mappingsFile, ProgressListener.createEmpty(), this.profile.getMappingSaveParameters()); + this.readWriteService.write(this.getRemapper().getMappings(), this.getRemapper().takeMappingDelta(), this.mappingsFile, ProgressListener.createEmpty(), this.profile.getMappingSaveParameters()); this.log.flush(); } diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java index cc914b0f2..d00cfc707 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java @@ -53,7 +53,6 @@ import org.quiltmc.enigma.util.EntryUtil; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.ResolutionStrategy; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 50284c860..50d23c72e 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -44,6 +44,7 @@ import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; +import java.util.stream.Stream; public class Enigma { public static final String NAME = "Enigma"; @@ -166,7 +167,7 @@ public Optional getReadWriteService(FileType fileType) { return this.getReadWriteServices().stream().filter(service -> service.getFileType().equals(fileType)).findFirst(); } - public Optional parseReadWriteService(Path path) { + public Optional getReadWriteService(Path path) { return this.parseFileType(path).flatMap(this::getReadWriteService); } @@ -181,7 +182,21 @@ public Optional parseFileType(Path path) { if (Files.isDirectory(path)) { try { - File firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())).findFirst().orElseThrow(); + File firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())) + .flatMap(file -> { + if (file.isDirectory()) { + var files = file.listFiles(); + if (files != null) { + return Arrays.stream(files); + } + } else { + return Stream.of(file); + } + + return Stream.empty(); + }) + .findFirst() + .orElseThrow(); for (FileType type : supportedTypes) { if (!type.isDirectory()) { diff --git a/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java b/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java index 816ad521d..22a1c4c3c 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java +++ b/enigma/src/test/java/org/quiltmc/enigma/TestTranslator.java @@ -29,7 +29,7 @@ public class TestTranslator { public static void beforeClass() throws Exception { enigma = Enigma.create(); project = enigma.openJar(JAR, new ClasspathClassProvider(), ProgressListener.createEmpty()); - mappings = enigma.parseReadWriteService(Path.of("/translation.mappings")).get().read( + mappings = enigma.getReadWriteService(Path.of("/translation.mappings")).get().read( TestUtil.getResource("/translation.mappings"), ProgressListener.createEmpty()); project.setMappings(mappings, ProgressListener.createEmpty()); From 1fb7da510694ed5e3cae6ec9091f9939baad5b13 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 12:51:16 -0500 Subject: [PATCH 12/45] core tests passing! --- .../enigma/translation/mapping/TestDeterministicWrite.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java index 0fc70a1d7..9f7a8aba2 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java @@ -5,6 +5,8 @@ import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.api.translation.representation.ArgumentDescriptor; @@ -33,7 +35,7 @@ public void testTinyV2() throws Exception { String prev = null; for (int i = 0; i < 32; i++) { Path file = dir.resolve(i + ".tiny"); - enigma.getReadWriteService("tiny").get().write(mappings, file, ProgressListener.createEmpty(), null); + enigma.getReadWriteService("tiny").get().write(mappings, file, ProgressListener.createEmpty(), new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, null, null)); String content = Files.readString(file); if (prev != null) Assertions.assertEquals(prev, content, "Iteration " + i + " has a different result from the previous one"); From 068b6cd1de39027a4858924bb4bb2f5c15c4e30c Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 13:42:37 -0500 Subject: [PATCH 13/45] GUI compiles (does not work) --- .../main/java/org/quiltmc/enigma/gui/Gui.java | 2 +- .../org/quiltmc/enigma/gui/GuiController.java | 45 +++++++++++-------- .../quiltmc/enigma/gui/element/MenuBar.java | 27 ++++++----- .../enigma/gui/util/ExtensionFileFilter.java | 23 +++++----- .../org/quiltmc/enigma/TestPackageRename.java | 3 +- 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java index ed80c3b86..89cc109af 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/Gui.java @@ -486,7 +486,7 @@ public void showDiscardDiag(IntFunction callback, String... options) { } public CompletableFuture saveMapping() { - ExtensionFileFilter.setupFileChooser(this.mappingsFileChooser, this.controller.getLoadedMappingFormat()); + ExtensionFileFilter.setupFileChooser(this.getController().getGui(), this.mappingsFileChooser, this.controller.getReadWriteService()); if (this.mappingsFileChooser.getSelectedFile() != null || this.mappingsFileChooser.showSaveDialog(this.mainWindow.getFrame()) == JFileChooser.APPROVE_OPTION) { return this.controller.saveMappings(ExtensionFileFilter.getSavePath(this.mappingsFileChooser)); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java index d00cfc707..032ca0aab 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/GuiController.java @@ -12,6 +12,7 @@ import org.quiltmc.enigma.api.analysis.tree.ClassReferenceTreeNode; import org.quiltmc.enigma.api.analysis.EntryReference; import org.quiltmc.enigma.api.analysis.tree.FieldReferenceTreeNode; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.gui.dialog.CrashDialog; import org.quiltmc.enigma.gui.network.IntegratedEnigmaClient; import org.quiltmc.enigma.impl.analysis.IndexTreeBuilder; @@ -98,7 +99,7 @@ public class GuiController implements ClientPacketHandler { private StatsGenerator statsGenerator; private Path loadedMappingPath; - private MappingFormat loadedMappingFormat; + private ReadWriteService readWriteService; private ClassHandleProvider chp; @@ -145,10 +146,16 @@ public void closeJar() { } public CompletableFuture openMappings(Path path) { - return this.openMappings(MappingFormat.parseFromFile(path), path); + var readWriteService = this.enigma.getReadWriteService(path); + if (readWriteService.isEmpty() || !readWriteService.get().supportsReading()) { + Logger.error("Could not open mappings: no reader found for file \"{}\"", path); + return CompletableFuture.supplyAsync(() -> null); + } + + return this.openMappings(readWriteService.get(), path); } - public CompletableFuture openMappings(MappingFormat format, Path path) { + public CompletableFuture openMappings(ReadWriteService readWriteService, Path path) { if (this.project == null || !new File(path.toUri()).exists()) { return CompletableFuture.supplyAsync(() -> null); } @@ -159,10 +166,10 @@ public CompletableFuture openMappings(MappingFormat format, Path path) { return ProgressDialog.runOffThread(this.gui, progress -> { try { - EntryTree mappings = format.read(path); + EntryTree mappings = readWriteService.read(path); this.project.setMappings(mappings, progress); - this.loadedMappingFormat = format; + this.readWriteService = readWriteService; this.loadedMappingPath = path; this.refreshClasses(); @@ -191,7 +198,7 @@ public void openMappings(EntryTree mappings) { } public CompletableFuture saveMappings(Path path) { - return this.saveMappings(path, this.loadedMappingFormat); + return this.saveMappings(path, this.readWriteService); } /** @@ -202,14 +209,14 @@ public CompletableFuture saveMappings(Path path) { * join on the future in gui, but rather call {@code thenXxx} methods. * * @param path the path of the save - * @param format the format of the save + * @param service the writer for the mapping type * @return the future of saving */ - public CompletableFuture saveMappings(Path path, MappingFormat format) { + public CompletableFuture saveMappings(Path path, ReadWriteService service) { if (this.project == null) { return CompletableFuture.completedFuture(null); - } else if (format.getWriter() == null) { - String nonWriteableMessage = I18n.translateFormatted("menu.file.save.non_writeable", I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); + } else if (!service.supportsWriting()) { + String nonWriteableMessage = I18n.translateFormatted("menu.file.save.non_writeable", I18n.translate("mapping_format." + service.getId().toLowerCase(Locale.ROOT))); JOptionPane.showMessageDialog(this.gui.getFrame(), nonWriteableMessage, I18n.translate("menu.file.save.cannot_save"), JOptionPane.ERROR_MESSAGE); return CompletableFuture.completedFuture(null); } @@ -221,13 +228,13 @@ public CompletableFuture saveMappings(Path path, MappingFormat format) { MappingDelta delta = mapper.takeMappingDelta(); boolean saveAll = !path.equals(this.loadedMappingPath); - this.loadedMappingFormat = format; + this.readWriteService = service; this.loadedMappingPath = path; if (saveAll) { - format.write(mapper.getMappings(), path, progress, saveParameters); + service.write(mapper.getMappings(), path, progress, saveParameters); } else { - format.write(mapper.getMappings(), delta, path, progress, saveParameters); + service.write(mapper.getMappings(), delta, path, progress, saveParameters); } }); } @@ -247,16 +254,16 @@ public void reloadAll() { if (jarPath != null) { this.closeJar(); CompletableFuture f = this.openJar(jarPath); - if (this.loadedMappingFormat != null && this.loadedMappingPath != null) { - f.whenComplete((v, t) -> this.openMappings(this.loadedMappingFormat, this.loadedMappingPath)); + if (this.readWriteService != null && this.loadedMappingPath != null) { + f.whenComplete((v, t) -> this.openMappings(this.readWriteService, this.loadedMappingPath)); } } } public void reloadMappings() { - if (this.loadedMappingFormat != null && this.loadedMappingPath != null) { + if (this.readWriteService != null && this.loadedMappingPath != null) { this.closeMappings(); - this.openMappings(this.loadedMappingFormat, this.loadedMappingPath); + this.openMappings(this.readWriteService, this.loadedMappingPath); } } @@ -617,8 +624,8 @@ public StatsGenerator getStatsGenerator() { } @Nullable - public MappingFormat getLoadedMappingFormat() { - return this.loadedMappingFormat; + public ReadWriteService getReadWriteService() { + return this.readWriteService; } public void createClient(String username, String ip, int port, char[] password) throws IOException { diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java index 1797528b8..53ccb984d 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/MenuBar.java @@ -1,6 +1,6 @@ package org.quiltmc.enigma.gui.element; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.gui.ConnectionState; import org.quiltmc.enigma.gui.Gui; import org.quiltmc.enigma.gui.NotificationManager; @@ -43,6 +43,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -472,18 +473,22 @@ private void onGithubClicked() { private void onOpenMappingsClicked() { this.gui.mappingsFileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); - List types = Arrays.stream(MappingFormat.values()).filter(format -> format.getReader() != null).toList(); - ExtensionFileFilter.setupFileChooser(this.gui.mappingsFileChooser, types.toArray(new MappingFormat[0])); + List types = this.gui.getController().getEnigma().getReadWriteServices().stream().filter(ReadWriteService::supportsReading).toList(); + ExtensionFileFilter.setupFileChooser(this.gui, this.gui.mappingsFileChooser, types.toArray(new ReadWriteService[0])); if (this.gui.mappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { File selectedFile = this.gui.mappingsFileChooser.getSelectedFile(); Config.main().stats.lastSelectedDir.setValue(this.gui.mappingsFileChooser.getCurrentDirectory().toString(), true); - MappingFormat format = MappingFormat.parseFromFile(selectedFile.toPath()); - if (format.getReader() != null) { - this.gui.getController().openMappings(format, selectedFile.toPath()); + Optional format = this.gui.getController().getEnigma().getReadWriteService(selectedFile.toPath()); + if (format.isPresent() && format.get().supportsReading()) { + this.gui.getController().openMappings(format.get(), selectedFile.toPath()); } else { - String nonParseableMessage = I18n.translateFormatted("menu.file.open.non_parseable", I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); + String nonParseableMessage = I18n.translateFormatted("menu.file.open.non_parseable.unsupported_format", selectedFile); + if (format.isPresent()) { + nonParseableMessage = I18n.translateFormatted("menu.file.open.non_parseable", I18n.translate("mapping_format." + format.get().getId().toLowerCase(Locale.ROOT))); + } + JOptionPane.showMessageDialog(this.gui.getFrame(), nonParseableMessage, I18n.translate("menu.file.open.cannot_open"), JOptionPane.ERROR_MESSAGE); } } @@ -550,12 +555,12 @@ private static Path findCommonPath(Path a, Path b) { } private static void prepareSaveMappingsAsMenu(JMenu saveMappingsAsMenu, JMenuItem saveMappingsItem, Gui gui) { - for (MappingFormat format : MappingFormat.values()) { - if (format.getWriter() != null) { - JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); + for (ReadWriteService format : gui.getController().getEnigma().getReadWriteServices()) { + if (format.supportsWriting()) { + JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.getId().toLowerCase(Locale.ROOT))); item.addActionListener(event -> { JFileChooser fileChooser = gui.mappingsFileChooser; - ExtensionFileFilter.setupFileChooser(fileChooser, format); + ExtensionFileFilter.setupFileChooser(gui, fileChooser, format); if (fileChooser.getCurrentDirectory() == null) { fileChooser.setCurrentDirectory(new File(Config.main().stats.lastSelectedDir.value())); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java index a90f7603b..9f7c77c43 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java @@ -1,6 +1,7 @@ package org.quiltmc.enigma.gui.util; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; +import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.gui.Gui; import org.quiltmc.enigma.util.I18n; import javax.swing.JFileChooser; @@ -66,23 +67,23 @@ public String getDescription() { * and adds and selects a new filter based on the provided mapping format. * * @param fileChooser the file chooser to set up - * @param formats the mapping formats to use. if empty, defaults to {@link MappingFormat#ENIGMA_DIRECTORY} + * @param services the read/write services to use. if empty, defaults to the current writer */ - public static void setupFileChooser(JFileChooser fileChooser, MappingFormat... formats) { - if (formats.length == 0) { - formats = new MappingFormat[]{MappingFormat.ENIGMA_DIRECTORY}; + public static void setupFileChooser(Gui gui, JFileChooser fileChooser, ReadWriteService... services) { + if (services.length == 0) { + services = new ReadWriteService[]{gui.getController().getReadWriteService()}; } // Remove previous custom filters. fileChooser.resetChoosableFileFilters(); - for (MappingFormat format : formats) { - if (format.getFileType().isDirectory()) { + for (ReadWriteService service : services) { + if (service.getFileType().isDirectory()) { fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); } else { fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - String formatName = I18n.translate("mapping_format." + format.name().toLowerCase()); - var filter = new ExtensionFileFilter(formatName, format.getFileType().getExtensions()); + String formatName = I18n.translate("mapping_format." + service.getId().toLowerCase()); + var filter = new ExtensionFileFilter(formatName, service.getFileType().getExtensions()); // Add our new filter to the list... fileChooser.addChoosableFileFilter(filter); // ...and choose it as the default. @@ -90,8 +91,8 @@ public static void setupFileChooser(JFileChooser fileChooser, MappingFormat... f } } - if (formats.length > 1) { - List extensions = Arrays.stream(formats).flatMap(format -> format.getFileType().getExtensions().stream()).distinct().toList(); + if (services.length > 1) { + List extensions = Arrays.stream(services).flatMap(format -> format.getFileType().getExtensions().stream()).distinct().toList(); var filter = new ExtensionFileFilter(I18n.translate("mapping_format.all_formats"), extensions); fileChooser.addChoosableFileFilter(filter); fileChooser.setFileFilter(filter); diff --git a/enigma-swing/src/test/java/org/quiltmc/enigma/TestPackageRename.java b/enigma-swing/src/test/java/org/quiltmc/enigma/TestPackageRename.java index c7805db3b..9329f8306 100644 --- a/enigma-swing/src/test/java/org/quiltmc/enigma/TestPackageRename.java +++ b/enigma-swing/src/test/java/org/quiltmc/enigma/TestPackageRename.java @@ -8,7 +8,6 @@ import org.quiltmc.enigma.gui.util.PackageRenamer; import org.quiltmc.enigma.api.translation.TranslateResult; import org.quiltmc.enigma.api.translation.Translator; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingFormat; import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -122,7 +121,7 @@ private static ClassSelectorPopupMenu setupMenu() throws InterruptedException { gui.setShowsProgressBars(false); CountDownLatch latch = new CountDownLatch(1); - gui.getController().openJar(JAR).thenRun(() -> gui.getController().openMappings(MappingFormat.ENIGMA_DIRECTORY, MAPPINGS).thenRun(latch::countDown)); + gui.getController().openJar(JAR).thenRun(() -> gui.getController().openMappings(MAPPINGS).thenRun(latch::countDown)); latch.await(); deobfuscator = gui.getController().getProject().getRemapper().getDeobfuscator(); From 369afa431cd88ad08fc41f02b4d3fa1d6b2ad944 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 18:32:10 -0500 Subject: [PATCH 14/45] default to enigma format for directories --- .../java/org/quiltmc/enigma/api/Enigma.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 50d23c72e..62f711623 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -173,7 +173,7 @@ public Optional getReadWriteService(Path path) { /** * Determines the mapping format of the provided path. Checks all formats according to their {@link FileType} file extensions. - * If the path is a directory, it will check the first file in the directory. + * If the path is a directory, it will check the first file in the directory. For directories, defaults to the enigma mappings format. * @param path the path to analyse * @return the mapping format of the path */ @@ -182,7 +182,7 @@ public Optional parseFileType(Path path) { if (Files.isDirectory(path)) { try { - File firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())) + Optional firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())) .flatMap(file -> { if (file.isDirectory()) { var files = file.listFiles(); @@ -195,19 +195,22 @@ public Optional parseFileType(Path path) { return Stream.empty(); }) - .findFirst() - .orElseThrow(); + .findFirst(); - for (FileType type : supportedTypes) { - if (!type.isDirectory()) { - continue; - } + if (firstFile.isPresent()) { + for (FileType type : supportedTypes) { + if (!type.isDirectory()) { + continue; + } - String extension = MoreFiles.getFileExtension(firstFile.toPath()).toLowerCase(); - if (type.getExtensions().contains(extension)) { - return Optional.of(type); + String extension = MoreFiles.getFileExtension(firstFile.get().toPath()).toLowerCase(); + if (type.getExtensions().contains(extension)) { + return Optional.of(type); + } } } + + return this.getFileType("mappings", true); } catch (Exception e) { Logger.error(e, "Failed to determine mapping format of directory {}", path); } From 3771c99d1cb938d113ae4cb053de5bfde9851b96 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 18:46:16 -0500 Subject: [PATCH 15/45] checkstyle --- .../quiltmc/enigma/command/CommandsUtil.java | 2 +- .../InsertProposedMappingsCommand.java | 1 - .../java/org/quiltmc/enigma/api/Enigma.java | 24 ++++---- .../enigma/api/service/EnigmaServiceType.java | 4 +- .../mapping/serde/MappingSaveParameters.java | 8 +-- .../serde/enigma/EnigmaMappingsWriter.java | 1 - .../mapping/serde/srg/SrgMappingsWriter.java | 1 - .../mapping/serde/tinyv2/TinyV2Reader.java | 1 + .../mapping/serde/tinyv2/TinyV2Writer.java | 2 +- .../impl/plugin/BuiltinMappingFormats.java | 55 ++----------------- .../plugin/EnumFieldNameFindingVisitor.java | 10 ++-- 11 files changed, 32 insertions(+), 77 deletions(-) diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java index 2ee99cd99..b7c503940 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/CommandsUtil.java @@ -11,7 +11,7 @@ public class CommandsUtil { public static ReadWriteService getReadWriteService(Enigma enigma, Path file) { var service = enigma.getReadWriteService(file); if (service.isEmpty()) { - throw new UnsupportedOperationException("No reader/writer found for file \"" + file + "\""); + throw new UnsupportedOperationException("No reader/writer found for file \"" + file + "\""); } return service.get(); diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java index ca73896e2..fdd7a279e 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InsertProposedMappingsCommand.java @@ -6,7 +6,6 @@ import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.EnigmaPlugin; import org.quiltmc.enigma.api.service.NameProposalService; -import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsWriter; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 62f711623..a3f0f538a 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -151,7 +151,7 @@ public Optional getFileType(String extension, boolean isDirectory) { } public Optional getReadWriteService(String extension) { - return getReadWriteService(extension, false); + return this.getReadWriteService(extension, false); } public Optional getReadWriteService(String extension, boolean isDirectory) { @@ -183,19 +183,19 @@ public Optional parseFileType(Path path) { if (Files.isDirectory(path)) { try { Optional firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())) - .flatMap(file -> { - if (file.isDirectory()) { - var files = file.listFiles(); - if (files != null) { - return Arrays.stream(files); + .flatMap(file -> { + if (file.isDirectory()) { + var files = file.listFiles(); + if (files != null) { + return Arrays.stream(files); + } + } else { + return Stream.of(file); } - } else { - return Stream.of(file); - } - return Stream.empty(); - }) - .findFirst(); + return Stream.empty(); + }) + .findFirst(); if (firstFile.isPresent()) { for (FileType type : supportedTypes) { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java index b4572b420..bb651ff42 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java @@ -1,8 +1,8 @@ package org.quiltmc.enigma.api.service; public record EnigmaServiceType( - String key, - boolean activeByDefault + String key, + boolean activeByDefault ) { /** * The unique key of this service type. diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java index 2da63d8de..86a07af59 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingSaveParameters.java @@ -5,10 +5,10 @@ import javax.annotation.Nullable; public record MappingSaveParameters( - @SerializedName("file_name_format") MappingFileNameFormat fileNameFormat, - @SerializedName("write_proposed_names") boolean writeProposedNames, - @SerializedName("obfuscated_namespace") @Nullable String obfuscatedNamespace, - @SerializedName("deobfuscated_namespace") @Nullable String deobfuscatedNamespace + @SerializedName("file_name_format") MappingFileNameFormat fileNameFormat, + @SerializedName("write_proposed_names") boolean writeProposedNames, + @SerializedName("obfuscated_namespace") @Nullable String obfuscatedNamespace, + @SerializedName("deobfuscated_namespace") @Nullable String deobfuscatedNamespace ) { /** * Controls how individual files will be named in directory-based mapping formats. diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java index c38385539..b28ed3d09 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsWriter.java @@ -38,7 +38,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import javax.annotation.Nonnull; -import javax.annotation.Nullable; public enum EnigmaMappingsWriter implements MappingsWriter { FILE { diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java index 25d49814f..8f47f0baf 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/srg/SrgMappingsWriter.java @@ -19,7 +19,6 @@ import org.quiltmc.enigma.util.I18n; import org.tinylog.Logger; -import javax.annotation.Nullable; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Reader.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Reader.java index d447da5eb..8253b8eb8 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Reader.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Reader.java @@ -26,6 +26,7 @@ import java.util.List; public final class TinyV2Reader implements MappingsReader { + public static final MappingsReader INSTANCE = new TinyV2Reader(); private static final String MINOR_VERSION = "0"; // 0 indent private static final int IN_HEADER = 0; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java index 1bd4aaaa9..3cfe02588 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -18,7 +18,6 @@ import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import org.tinylog.Logger; -import javax.annotation.Nullable; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; @@ -30,6 +29,7 @@ import java.util.stream.StreamSupport; public final class TinyV2Writer implements MappingsWriter { + public static final MappingsWriter INSTANCE = new TinyV2Writer(); private static final String MINOR_VERSION = "0"; public TinyV2Writer() { diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java index efda1471f..5af441324 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -1,79 +1,36 @@ package org.quiltmc.enigma.impl.plugin; import org.quiltmc.enigma.api.EnigmaPluginContext; -import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.service.ReadWriteService; -import org.quiltmc.enigma.api.translation.mapping.EntryMapping; -import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.FileType; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; -import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsWriter; import org.quiltmc.enigma.api.translation.mapping.serde.proguard.ProguardMappingsReader; import org.quiltmc.enigma.api.translation.mapping.serde.srg.SrgMappingsWriter; import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Reader; import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; -import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.Path; public class BuiltinMappingFormats { public static void register(EnigmaPluginContext ctx) { FileType.File enigmaMapping = new FileType.File("mapping", "mappings"); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(EnigmaMappingsReader.FILE, EnigmaMappingsWriter.FILE, enigmaMapping, "enigma_file") + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.FILE, EnigmaMappingsWriter.FILE, enigmaMapping, "enigma_file") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(EnigmaMappingsReader.DIRECTORY, EnigmaMappingsWriter.DIRECTORY, new FileType.Directory(enigmaMapping), "enigma_directory") + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.DIRECTORY, EnigmaMappingsWriter.DIRECTORY, new FileType.Directory(enigmaMapping), "enigma_directory") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(EnigmaMappingsReader.ZIP, EnigmaMappingsWriter.ZIP, new FileType.File("zip"), "enigma_zip") + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.ZIP, EnigmaMappingsWriter.ZIP, new FileType.File("zip"), "enigma_zip") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> new ReadWriteService() { - static final TinyV2Reader reader = new TinyV2Reader(); - static final TinyV2Writer writer = new TinyV2Writer(); - - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - writer.write(mappings, delta, path, progress, saveParameters); - } - - @Override - public EntryTree read(Path path, ProgressListener progress) throws MappingParseException, IOException { - return reader.read(path, progress); - } - - @Override - public FileType getFileType() { - return new FileType.File("tiny"); - } - - @Override - public boolean supportsReading() { - return true; - } - - @Override - public boolean supportsWriting() { - return true; - } - - @Override - public String getId() { - return "tiny_v2"; - } - } + ctx1 -> ReadWriteService.create(TinyV2Reader.INSTANCE, TinyV2Writer.INSTANCE, new FileType.File("tinyv2"), "tinyv2") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(null, SrgMappingsWriter.INSTANCE, new FileType.File("tsrg"), "srg_file") + ctx1 -> ReadWriteService.create(null, SrgMappingsWriter.INSTANCE, new FileType.File("tsrg"), "srg_file") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(ProguardMappingsReader.INSTANCE, null, new FileType.File("txt"), "proguard") + ctx1 -> ReadWriteService.create(ProguardMappingsReader.INSTANCE, null, new FileType.File("txt"), "proguard") ); } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java index bd7c28412..a9e5baeba 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/EnumFieldNameFindingVisitor.java @@ -51,7 +51,7 @@ public void visit(int version, int access, String name, String signature, String @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { if ((access & Opcodes.ACC_ENUM) != 0 - && !this.enumFields.add(Pair.of(name, descriptor))) { + && !this.enumFields.add(Pair.of(name, descriptor))) { throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); } @@ -93,10 +93,10 @@ private void collectResults() throws Exception { String s = null; if (instr2.getOpcode() == Opcodes.PUTSTATIC - && ((FieldInsnNode) instr2).owner.equals(owner) - && this.enumFields.contains(Pair.of(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) - && instr1.getOpcode() == Opcodes.INVOKESPECIAL - && "".equals(((MethodInsnNode) instr1).name)) { + && ((FieldInsnNode) instr2).owner.equals(owner) + && this.enumFields.contains(Pair.of(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) + && instr1.getOpcode() == Opcodes.INVOKESPECIAL + && "".equals(((MethodInsnNode) instr1).name)) { for (int j = 0; j < frames[i - 1].getStackSize(); j++) { SourceValue sv = frames[i - 1].getStack(j); for (AbstractInsnNode ci : sv.insns) { From 31159b5a7f56e1b3d8177a98040d939c14c43b80 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 23:09:44 -0500 Subject: [PATCH 16/45] fix test checkstyle, remove bad API methods --- .../java/org/quiltmc/enigma/api/Enigma.java | 19 +----------------- .../enigma/MappingFormatDetectionTest.java | 20 ++++++++++--------- .../mapping/TestDeterministicWrite.java | 4 ++-- .../mapping/TestReadWriteCycle.java | 15 ++++++++++---- .../translation/mapping/TestV2Main.java | 1 - 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index a3f0f538a..708d620ed 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -146,23 +146,6 @@ public List getSupportedFileTypes() { return this.getReadWriteServices().stream().map(ReadWriteService::getFileType).toList(); } - public Optional getFileType(String extension, boolean isDirectory) { - return this.getSupportedFileTypes().stream().filter(type -> type.getExtensions().contains(extension) && type.isDirectory() == isDirectory).findFirst(); - } - - public Optional getReadWriteService(String extension) { - return this.getReadWriteService(extension, false); - } - - public Optional getReadWriteService(String extension, boolean isDirectory) { - var fileType = this.getFileType(extension, isDirectory); - if (fileType.isPresent()) { - return this.getReadWriteService(fileType.get()); - } - - return Optional.empty(); - } - public Optional getReadWriteService(FileType fileType) { return this.getReadWriteServices().stream().filter(service -> service.getFileType().equals(fileType)).findFirst(); } @@ -210,7 +193,7 @@ public Optional parseFileType(Path path) { } } - return this.getFileType("mappings", true); + return this.getSupportedFileTypes().stream().filter(type -> type.isDirectory() && type.getExtensions().contains("mapping")).findFirst(); } catch (Exception e) { Logger.error(e, "Failed to determine mapping format of directory {}", path); } diff --git a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java index 140b42474..3f48e105d 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java @@ -1,21 +1,23 @@ package org.quiltmc.enigma; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; import java.io.File; import java.util.Objects; +import java.util.Optional; public class MappingFormatDetectionTest { @Test void testFormatDetection() { -// File formatsDir = new File(TestUtil.getResource("/formats").toUri()); -// -// for (File file : Objects.requireNonNull(formatsDir.listFiles())) { -// MappingFormat parsedFormat = MappingFormat.parseFromFile(file.toPath()); -// MappingFormat expectedFormat = MappingFormat.valueOf(file.getName().toUpperCase().split("_EXAMPLE_MAPPING")[0]); -// -// Assertions.assertSame(expectedFormat, parsedFormat, "Failed to detect format for " + file.getName()); -// } + File formatsDir = new File(TestUtil.getResource("/formats").toUri()); + + for (File file : Objects.requireNonNull(formatsDir.listFiles())) { + Optional parsedFormat = Enigma.create().parseFileType(file.toPath()); + // todo + + //Assertions.assertSame(expectedFormat, parsedFormat, "Failed to detect format for " + file.getName()); + } } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java index 9f7a8aba2..87b1cf53c 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestDeterministicWrite.java @@ -35,12 +35,12 @@ public void testTinyV2() throws Exception { String prev = null; for (int i = 0; i < 32; i++) { Path file = dir.resolve(i + ".tiny"); - enigma.getReadWriteService("tiny").get().write(mappings, file, ProgressListener.createEmpty(), new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, null, null)); + enigma.getReadWriteService(file).get().write(mappings, file, ProgressListener.createEmpty(), new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, null, null)); String content = Files.readString(file); if (prev != null) Assertions.assertEquals(prev, content, "Iteration " + i + " has a different result from the previous one"); prev = content; - mappings = enigma.getReadWriteService("tiny").get().read(file, ProgressListener.createEmpty()); + mappings = enigma.getReadWriteService(file).get().read(file, ProgressListener.createEmpty()); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java index 2493d1993..edd6471ec 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestReadWriteCycle.java @@ -5,6 +5,7 @@ import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.source.TokenType; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; @@ -20,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.util.function.Predicate; /** * Tests that a MappingFormat can write out a fixed set of mappings and read them back without losing any information. @@ -103,21 +105,26 @@ private void testReadWriteCycle(ReadWriteService readWriteService, String tmpNam @Test public void testEnigmaFile() throws IOException, MappingParseException { - this.testReadWriteCycle(this.enigma.getReadWriteService("mappings").get(), ".mapping"); + this.testReadWriteCycle(this.getService(file -> file.getExtensions().contains("mapping") && !file.isDirectory()), ".mapping"); } @Test public void testEnigmaDir() throws IOException, MappingParseException { - this.testReadWriteCycle(this.enigma.getReadWriteService("mappings", true).get(), ".tmp"); + this.testReadWriteCycle(this.getService(file -> file.getExtensions().contains("mapping") && file.isDirectory()), ".tmp"); } @Test public void testEnigmaZip() throws IOException, MappingParseException { - this.testReadWriteCycle(this.enigma.getReadWriteService("zip").get(), ".zip"); + this.testReadWriteCycle(this.getService(file -> file.getExtensions().contains("zip")), ".zip"); } @Test public void testTinyV2() throws IOException, MappingParseException { - this.testReadWriteCycle(this.enigma.getReadWriteService("tiny").get(), ".tiny"); + this.testReadWriteCycle(this.getService(file -> file.getExtensions().contains("tiny")), ".tiny"); + } + + @SuppressWarnings("all") + private ReadWriteService getService(Predicate predicate) { + return this.enigma.getReadWriteService(this.enigma.getSupportedFileTypes().stream().filter(predicate).findFirst().get()).get(); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java index b83501492..56a55a9a1 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java @@ -1,6 +1,5 @@ package org.quiltmc.enigma.translation.mapping; -import org.quiltmc.enigma.api.EnigmaProfile; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; From 0a30236d24951b6720caabc9f0b1d2b5ee700526 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 21 Apr 2024 23:14:56 -0500 Subject: [PATCH 17/45] add "tiny" extension for tiny format --- .../org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java index 5af441324..af2852523 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -24,7 +24,7 @@ public static void register(EnigmaPluginContext ctx) { ctx1 -> ReadWriteService.create(EnigmaMappingsReader.ZIP, EnigmaMappingsWriter.ZIP, new FileType.File("zip"), "enigma_zip") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(TinyV2Reader.INSTANCE, TinyV2Writer.INSTANCE, new FileType.File("tinyv2"), "tinyv2") + ctx1 -> ReadWriteService.create(TinyV2Reader.INSTANCE, TinyV2Writer.INSTANCE, new FileType.File("tinyv2", "tiny"), "tiny_v2") ); ctx.registerService(ReadWriteService.TYPE, ctx1 -> ReadWriteService.create(null, SrgMappingsWriter.INSTANCE, new FileType.File("tsrg"), "srg_file") From 1b6e1c9d0cdb2a843372e694d7d7f9a3ba913e17 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Mon, 22 Apr 2024 15:39:06 -0500 Subject: [PATCH 18/45] more docs --- .../java/org/quiltmc/enigma/api/Enigma.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 708d620ed..6866597a6 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -138,18 +138,35 @@ public List getNameProposalServices() { return proposalServices; } + /** + * {@return all registered read/write services} + */ public List getReadWriteServices() { return this.services.get(ReadWriteService.TYPE); } + /** + * Gets all supported {@link FileType file types} for reading or writing. + * @return the list of supported file types + */ public List getSupportedFileTypes() { return this.getReadWriteServices().stream().map(ReadWriteService::getFileType).toList(); } + /** + * Gets the {@link ReadWriteService read/write service} for the provided {@link FileType file type}. + * @param fileType the file type to get the service for + * @return the read/write service for the file type + */ public Optional getReadWriteService(FileType fileType) { return this.getReadWriteServices().stream().filter(service -> service.getFileType().equals(fileType)).findFirst(); } + /** + * Parses the {@link FileType file type} of the provided path and returns the corresponding {@link ReadWriteService read/write service} + * @param path the path to analyse + * @return the read/write service for the file type of the path + */ public Optional getReadWriteService(Path path) { return this.parseFileType(path).flatMap(this::getReadWriteService); } From d342120c4db80cdf13505aedc85762976ce8c56f Mon Sep 17 00:00:00 2001 From: ix0rai Date: Mon, 22 Apr 2024 16:14:24 -0500 Subject: [PATCH 19/45] add validation for plugin registration --- .../java/org/quiltmc/enigma/api/Enigma.java | 40 +++++++++++++++++-- .../enigma/api/service/EnigmaService.java | 2 +- .../impl/plugin/BuiltinMappingFormats.java | 12 +++--- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 6866597a6..ef69bf100 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -271,19 +271,24 @@ public void registerService(EnigmaServiceType servi List serviceProfiles = this.profile.getServiceProfiles(serviceType); if (serviceProfiles.isEmpty() && serviceType.activeByDefault()) { - this.services.put(serviceType, factory.create(this.getServiceContext(null))); + this.putService(serviceType, factory.create(this.getServiceContext(null))); return; } for (EnigmaProfile.Service serviceProfile : serviceProfiles) { T service = factory.create(this.getServiceContext(serviceProfile)); if (serviceProfile.matches(service.getId())) { - this.services.put(serviceType, service); + this.putService(serviceType, service); break; } } } + private void putService(EnigmaServiceType serviceType, EnigmaService service) { + this.validateRegistration(services.build(), serviceType, service); + this.services.put(serviceType, service); + } + private EnigmaServiceContext getServiceContext(@Nullable EnigmaProfile.Service serviceProfile) { return new EnigmaServiceContext<>() { @Override @@ -323,7 +328,36 @@ EnigmaServices buildServices() { } } - return new EnigmaServices(orderedServices.build()); + var builtOrderedServices = orderedServices.build(); + return new EnigmaServices(builtOrderedServices); + } + + private void validateRegistration(ImmutableListMultimap, EnigmaService> services, EnigmaServiceType serviceType, EnigmaService service) { + this.validatePluginId(service.getId()); + + for (EnigmaService otherService : services.get(serviceType)) { + // all services + if (service.getId().equals(otherService.getId())) { + throw new IllegalStateException("Multiple services of type " + serviceType + " have the same ID: \"" + service.getId() + "\""); + } + + // read write services + if (service instanceof ReadWriteService rwService + && otherService instanceof ReadWriteService otherRwService + && rwService.getFileType().isDirectory() == otherRwService.getFileType().isDirectory()) { + for (String extension : rwService.getFileType().getExtensions()) { + if (otherRwService.getFileType().getExtensions().contains(extension)) { + throw new IllegalStateException("Multiple read/write services found supporting the same extension: " + extension + " (id: " + service.getId() + ", other id: " + otherService.getId() + ")"); + } + } + } + } + } + + private void validatePluginId(String id) { + if (!id.matches("([a-z0-9_]+):([a-z0-9_]+((/[a-z0-9_]+)+)?)")) { + throw new IllegalArgumentException("Invalid plugin id: \"" + id + "\"\n" + "Refer to Javadoc on EnigmaService#getId for how to properly form a service ID."); + } } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java index 93dc776ff..9a9a75b6a 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java @@ -8,7 +8,7 @@ public interface EnigmaService { *

  • Be all lowercase, with words separated by underscores. Slashes are allowed only after the namespace.
  • *
  • Be constant and unique: it should never change.
  • * - *

    Examples: {@code enigma:cfr}, {@code enigma:enum_name_proposer}, {@code your_plugin:custom_indexer}

    + *

    Examples: {@code enigma:cfr}, {@code enigma:enum_proposers/name}, {@code your_plugin:custom_indexer}

    * * @return the constant ID */ diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java index af2852523..40a3024e8 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/plugin/BuiltinMappingFormats.java @@ -15,22 +15,22 @@ public static void register(EnigmaPluginContext ctx) { FileType.File enigmaMapping = new FileType.File("mapping", "mappings"); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(EnigmaMappingsReader.FILE, EnigmaMappingsWriter.FILE, enigmaMapping, "enigma_file") + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.FILE, EnigmaMappingsWriter.FILE, enigmaMapping, "enigma:enigma_file") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(EnigmaMappingsReader.DIRECTORY, EnigmaMappingsWriter.DIRECTORY, new FileType.Directory(enigmaMapping), "enigma_directory") + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.DIRECTORY, EnigmaMappingsWriter.DIRECTORY, new FileType.Directory(enigmaMapping), "enigma:enigma_directory") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(EnigmaMappingsReader.ZIP, EnigmaMappingsWriter.ZIP, new FileType.File("zip"), "enigma_zip") + ctx1 -> ReadWriteService.create(EnigmaMappingsReader.ZIP, EnigmaMappingsWriter.ZIP, new FileType.File("zip"), "enigma:enigma_zip") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(TinyV2Reader.INSTANCE, TinyV2Writer.INSTANCE, new FileType.File("tinyv2", "tiny"), "tiny_v2") + ctx1 -> ReadWriteService.create(TinyV2Reader.INSTANCE, TinyV2Writer.INSTANCE, new FileType.File("tinyv2", "tiny"), "enigma:tiny_v2") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(null, SrgMappingsWriter.INSTANCE, new FileType.File("tsrg"), "srg_file") + ctx1 -> ReadWriteService.create(null, SrgMappingsWriter.INSTANCE, new FileType.File("tsrg"), "enigma:srg_file") ); ctx.registerService(ReadWriteService.TYPE, - ctx1 -> ReadWriteService.create(ProguardMappingsReader.INSTANCE, null, new FileType.File("txt"), "proguard") + ctx1 -> ReadWriteService.create(ProguardMappingsReader.INSTANCE, null, new FileType.File("txt"), "enigma:proguard") ); } } From eea654de3ffbccb8faea6ce6ec8ad4afdbba44d8 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Mon, 22 Apr 2024 22:44:46 -0500 Subject: [PATCH 20/45] update gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 29 +++++++++++++---------- gradlew.bat | 20 ++++++++-------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
    NnX)EH+Nua)3Y(c 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f..1af9e0930 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..25da30dbd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ 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. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From b09b191a86795b2f59b6b7d0b6e6879760906710 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Mon, 22 Apr 2024 22:59:30 -0500 Subject: [PATCH 21/45] checkstyle --- enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index ef69bf100..57957e374 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -285,7 +285,7 @@ public void registerService(EnigmaServiceType servi } private void putService(EnigmaServiceType serviceType, EnigmaService service) { - this.validateRegistration(services.build(), serviceType, service); + this.validateRegistration(this.services.build(), serviceType, service); this.services.put(serviceType, service); } From d0a4f40559d22a00b6d578b7c1bc2dc7fe0989df Mon Sep 17 00:00:00 2001 From: ix0rai Date: Mon, 22 Apr 2024 23:13:26 -0500 Subject: [PATCH 22/45] address gradle deprecation --- build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 43ed89fea..a6e2c098a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,6 @@ subprojects { apply plugin: 'java' apply plugin: 'maven-publish' - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - repositories { mavenLocal() mavenCentral() @@ -44,6 +41,8 @@ subprojects { java { withSourcesJar() + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } tasks.withType(JavaCompile).configureEach { From a921600e4807df21ae787db77619f5728a6c337e Mon Sep 17 00:00:00 2001 From: ix0rai Date: Mon, 22 Apr 2024 23:22:33 -0500 Subject: [PATCH 23/45] update gradle again, update proguard --- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3e4b1d15c..f7f5d5bae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ vineflower = "1.10.0" cfr = "0.2.2" procyon = "0.6.0" -proguard = "7.3.2" +proguard = "7.4.0" junit = "5.9.3" hamcrest = "2.2" jimfs = "1.2" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..b82aa23a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 6a4074db48caae2295e085f1a6f52657be30a60f Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 23 Apr 2024 09:27:02 -0500 Subject: [PATCH 24/45] fix gradle deprecations --- enigma-cli/build.gradle | 7 ++++--- enigma-server/build.gradle | 7 ++++--- enigma-swing/build.gradle | 12 ++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/enigma-cli/build.gradle b/enigma-cli/build.gradle index c45ce1dc6..152d22ca5 100644 --- a/enigma-cli/build.gradle +++ b/enigma-cli/build.gradle @@ -9,9 +9,10 @@ dependencies { testImplementation(testFixtures(project(':enigma'))) } -mainClassName = 'org.quiltmc.enigma.command.Main' - -jar.manifest.attributes 'Main-Class': mainClassName +application { + mainClass = 'org.quiltmc.enigma.command.Main' + jar.manifest.attributes 'Main-Class': mainClass +} publishing { publications { diff --git a/enigma-server/build.gradle b/enigma-server/build.gradle index fe04e9059..d9c865b89 100644 --- a/enigma-server/build.gradle +++ b/enigma-server/build.gradle @@ -11,9 +11,10 @@ dependencies { testImplementation testFixtures(project(':enigma')) } -mainClassName = 'org.quiltmc.enigma.network.DedicatedEnigmaServer' - -jar.manifest.attributes 'Main-Class': mainClassName +application { + mainClass = 'org.quiltmc.enigma.network.DedicatedEnigmaServer' + jar.manifest.attributes 'Main-Class': mainClass +} publishing { publications { diff --git a/enigma-swing/build.gradle b/enigma-swing/build.gradle index faaa6102e..b400dcaa7 100644 --- a/enigma-swing/build.gradle +++ b/enigma-swing/build.gradle @@ -19,10 +19,10 @@ dependencies { testImplementation(testFixtures(project(':enigma'))) } -mainClassName = 'org.quiltmc.enigma.gui.Main' - -jar.manifest.attributes 'Main-Class': mainClassName - +application { + mainClass = 'org.quiltmc.enigma.gui.Main' + jar.manifest.attributes 'Main-Class': mainClass +} static String convertToGradleTaskName(String name) { String newName = new String(name); @@ -44,7 +44,7 @@ def registerTestTask(String name) { group("test") dependsOn(":enigma:${taskName}TestObf") dependsOn(":enigma:processResources") - mainClass = mainClassName + mainClass = mainClass classpath = sourceSets.test.runtimeClasspath def jar = project(":enigma").file("build/test-obf/${name}.jar") @@ -59,7 +59,7 @@ def registerTestTask(String name) { tasks.register("${taskName}TestGui2", JavaExec.class) { group("test") dependsOn(":enigma:${taskName}TestObf") - mainClass = mainClassName + mainClass = mainClass classpath = sourceSets.test.runtimeClasspath def jar = project(":enigma").file("build/test-obf/${name}.jar") From 6098587789c2d3e7aa16e99868db68dc8c200f80 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 23 Apr 2024 16:05:58 -0500 Subject: [PATCH 25/45] actions should pass again --- build.gradle | 1 + enigma-swing/build.gradle | 12 +++++++----- gradle/libs.versions.toml | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index a6e2c098a..7fc5eaed2 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ subprojects { testImplementation libs.junit testRuntimeOnly libs.junit.engine + testRuntimeOnly libs.junit.launcher testImplementation libs.hamcrest } diff --git a/enigma-swing/build.gradle b/enigma-swing/build.gradle index b400dcaa7..0a647c7ed 100644 --- a/enigma-swing/build.gradle +++ b/enigma-swing/build.gradle @@ -19,12 +19,14 @@ dependencies { testImplementation(testFixtures(project(':enigma'))) } +final String mainClassName = 'org.quiltmc.enigma.gui.Main' application { - mainClass = 'org.quiltmc.enigma.gui.Main' + mainClass = mainClassName jar.manifest.attributes 'Main-Class': mainClass } + static String convertToGradleTaskName(String name) { - String newName = new String(name); + String newName = new String(name) for (int i = 0; i < name.length(); i++) { if (name.charAt(i) == '_') { @@ -38,13 +40,13 @@ static String convertToGradleTaskName(String name) { } def registerTestTask(String name) { - String taskName = convertToGradleTaskName(name); + String taskName = convertToGradleTaskName(name) tasks.register("${taskName}TestGui", JavaExec.class) { group("test") dependsOn(":enigma:${taskName}TestObf") dependsOn(":enigma:processResources") - mainClass = mainClass + mainClass = 'org.quiltmc.enigma.gui.Main' classpath = sourceSets.test.runtimeClasspath def jar = project(":enigma").file("build/test-obf/${name}.jar") @@ -59,7 +61,7 @@ def registerTestTask(String name) { tasks.register("${taskName}TestGui2", JavaExec.class) { group("test") dependsOn(":enigma:${taskName}TestObf") - mainClass = mainClass + mainClass = 'org.quiltmc.enigma.gui.Main' classpath = sourceSets.test.runtimeClasspath def jar = project(":enigma").file("build/test-obf/${name}.jar") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f7f5d5bae..bf9f11f26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,6 +50,7 @@ procyon = { module = "org.bitbucket.mstrobel:procyon-compilertools", version.ref proguard = { module = "com.guardsquare:proguard-base", version.ref = "proguard" } junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit_engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } +junit_launcher = { module = "org.junit.platform:junit-platform-launcher" } hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } jimfs = { module = "com.google.jimfs:jimfs", version.ref = "jimfs" } From c15b6a753700cae2f08978d3175e2e14c81c296d Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 23 Apr 2024 19:36:18 -0500 Subject: [PATCH 26/45] test plugin id validation --- .../quiltmc/enigma/PluginValidationTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java diff --git a/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java b/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java new file mode 100644 index 000000000..7f46edc6e --- /dev/null +++ b/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java @@ -0,0 +1,79 @@ +package org.quiltmc.enigma; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.EnigmaPlugin; +import org.quiltmc.enigma.api.EnigmaPluginContext; +import org.quiltmc.enigma.api.ProgressListener; +import org.quiltmc.enigma.api.service.ReadWriteService; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.MappingDelta; +import org.quiltmc.enigma.api.translation.mapping.serde.FileType; +import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; + +import java.nio.file.Path; +import java.util.List; + +public class PluginValidationTest { + @Test + public void testIdValidation() { + Enigma.builder().setPlugins(List.of(new IdTestPlugin())).build(); + } + + private static class IdTestPlugin implements EnigmaPlugin { + @Override + public void init(EnigmaPluginContext ctx) { + // empty + Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "")); + // no namespace + Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind")); + // slashes in wrong place + Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind/grind:ground")); + // uppercase chars + Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind:Ground")); + // invalid chars + Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind:ground!")); + // valid + this.registerService(ctx, "grind:ground"); + this.registerService(ctx, "grind:ground_"); + this.registerService(ctx, "grind:ground_grind/g_rind2"); + this.registerService(ctx, "grind:ground/grind"); + this.registerService(ctx, "grind:ground/grind/grind"); + } + + private void registerService(EnigmaPluginContext ctx, String id) { + ctx.registerService(ReadWriteService.TYPE, ctx1 -> new ReadWriteService() { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + } + + @Override + public EntryTree read(Path path, ProgressListener progress) { + return null; + } + + @Override + public FileType getFileType() { + return new FileType.File(id); + } + + @Override + public boolean supportsReading() { + return false; + } + + @Override + public boolean supportsWriting() { + return false; + } + + @Override + public String getId() { + return id; + } + }); + } + } +} From 872769bd2f79b3b47d7010e8aaa05f1574001095 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 23 Apr 2024 19:45:51 -0500 Subject: [PATCH 27/45] test all types of plugin validation --- .../quiltmc/enigma/PluginValidationTest.java | 116 +++++++++++------- 1 file changed, 73 insertions(+), 43 deletions(-) diff --git a/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java b/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java index 7f46edc6e..b41413c21 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java @@ -22,58 +22,88 @@ public void testIdValidation() { Enigma.builder().setPlugins(List.of(new IdTestPlugin())).build(); } + @Test + public void testDuplicateServices() { + Enigma.builder().setPlugins(List.of(new DuplicateIdTestPlugin())).build(); + } + + @Test + public void testDuplicateFileTypes() { + Enigma.builder().setPlugins(List.of(new DuplicateFileTypeTestPlugin())).build(); + } + + private static void registerService(EnigmaPluginContext ctx, String id) { + registerService(ctx, id, id); + } + + private static void registerService(EnigmaPluginContext ctx, String id, String fileType) { + ctx.registerService(ReadWriteService.TYPE, ctx1 -> new ReadWriteService() { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + } + + @Override + public EntryTree read(Path path, ProgressListener progress) { + return null; + } + + @Override + public FileType getFileType() { + return new FileType.File(fileType); + } + + @Override + public boolean supportsReading() { + return false; + } + + @Override + public boolean supportsWriting() { + return false; + } + + @Override + public String getId() { + return id; + } + }); + } + + private static class DuplicateFileTypeTestPlugin implements EnigmaPlugin { + @Override + public void init(EnigmaPluginContext ctx) { + registerService(ctx, "test:grind", "gaming"); + Assertions.assertThrows(IllegalStateException.class, () -> registerService(ctx, "test:slay", "gaming")); + } + } + + private static class DuplicateIdTestPlugin implements EnigmaPlugin { + @Override + public void init(EnigmaPluginContext ctx) { + registerService(ctx, "grind:ground"); + Assertions.assertThrows(IllegalStateException.class, () -> registerService(ctx, "grind:ground")); + } + } + private static class IdTestPlugin implements EnigmaPlugin { @Override public void init(EnigmaPluginContext ctx) { // empty - Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "")); + Assertions.assertThrows(IllegalArgumentException.class, () -> registerService(ctx, "")); // no namespace - Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind")); + Assertions.assertThrows(IllegalArgumentException.class, () -> registerService(ctx, "grind")); // slashes in wrong place - Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind/grind:ground")); + Assertions.assertThrows(IllegalArgumentException.class, () -> registerService(ctx, "grind/grind:ground")); // uppercase chars - Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind:Ground")); + Assertions.assertThrows(IllegalArgumentException.class, () -> registerService(ctx, "grind:Ground")); // invalid chars - Assertions.assertThrows(IllegalArgumentException.class, () -> this.registerService(ctx, "grind:ground!")); + Assertions.assertThrows(IllegalArgumentException.class, () -> registerService(ctx, "grind:ground!")); // valid - this.registerService(ctx, "grind:ground"); - this.registerService(ctx, "grind:ground_"); - this.registerService(ctx, "grind:ground_grind/g_rind2"); - this.registerService(ctx, "grind:ground/grind"); - this.registerService(ctx, "grind:ground/grind/grind"); - } - - private void registerService(EnigmaPluginContext ctx, String id) { - ctx.registerService(ReadWriteService.TYPE, ctx1 -> new ReadWriteService() { - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - } - - @Override - public EntryTree read(Path path, ProgressListener progress) { - return null; - } - - @Override - public FileType getFileType() { - return new FileType.File(id); - } - - @Override - public boolean supportsReading() { - return false; - } - - @Override - public boolean supportsWriting() { - return false; - } - - @Override - public String getId() { - return id; - } - }); + registerService(ctx, "grind:ground"); + registerService(ctx, "grind:ground_"); + registerService(ctx, "grind:ground_grind/g_rind2"); + registerService(ctx, "grind:ground/grind"); + registerService(ctx, "grind:ground/grind/grind"); } } } From 53c181dc6728682214760288cc104b24e87972f6 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 23 Apr 2024 19:49:22 -0500 Subject: [PATCH 28/45] refactor test names --- .../{ActiveByDefaultTest.java => TestActiveByDefault.java} | 2 +- .../{PluginValidationTest.java => TestPluginValidation.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename enigma/src/test/java/org/quiltmc/enigma/{ActiveByDefaultTest.java => TestActiveByDefault.java} (97%) rename enigma/src/test/java/org/quiltmc/enigma/{PluginValidationTest.java => TestPluginValidation.java} (99%) diff --git a/enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java b/enigma/src/test/java/org/quiltmc/enigma/TestActiveByDefault.java similarity index 97% rename from enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java rename to enigma/src/test/java/org/quiltmc/enigma/TestActiveByDefault.java index b47960d85..b08aa9dbb 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/ActiveByDefaultTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/TestActiveByDefault.java @@ -11,7 +11,7 @@ import java.io.Reader; import java.io.StringReader; -public class ActiveByDefaultTest { +public class TestActiveByDefault { @Test public void testServicesLoaded() { Enigma enigma = Enigma.builder().build(); diff --git a/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java b/enigma/src/test/java/org/quiltmc/enigma/TestPluginValidation.java similarity index 99% rename from enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java rename to enigma/src/test/java/org/quiltmc/enigma/TestPluginValidation.java index b41413c21..9b7bd3e83 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/PluginValidationTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/TestPluginValidation.java @@ -16,7 +16,7 @@ import java.nio.file.Path; import java.util.List; -public class PluginValidationTest { +public class TestPluginValidation { @Test public void testIdValidation() { Enigma.builder().setPlugins(List.of(new IdTestPlugin())).build(); From f6ac5f9465fe45234004bda5c927281f8af7fa21 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Tue, 23 Apr 2024 21:19:40 -0500 Subject: [PATCH 29/45] IN MY DOCUMENTATION ERA --- .../org/quiltmc/enigma/api/EnigmaPlugin.java | 6 ++++++ .../enigma/api/service/DecompilerService.java | 5 +++++ .../enigma/api/service/EnigmaService.java | 3 +++ .../enigma/api/service/JarIndexerService.java | 5 +++++ .../api/service/NameProposalService.java | 2 ++ .../api/service/ObfuscationTestService.java | 5 +++++ .../enigma/api/service/ReadWriteService.java | 18 ++++++++++++++++++ 7 files changed, 44 insertions(+) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaPlugin.java b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaPlugin.java index 6b7a3417a..c98f5fd39 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaPlugin.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaPlugin.java @@ -1,5 +1,11 @@ package org.quiltmc.enigma.api; +/** + * An enigma plugin represents a collection of {@link org.quiltmc.enigma.api.service.EnigmaService services} that perform different functions. + */ public interface EnigmaPlugin { + /** + * Initializes the plugin, registering all services. + */ void init(EnigmaPluginContext ctx); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java index 46d9a0979..c4f49684b 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java @@ -4,6 +4,11 @@ import org.quiltmc.enigma.api.source.Decompiler; import org.quiltmc.enigma.api.source.SourceSettings; +/** + * Decompiler services provide implementations of {@link Decompiler} in order to convert bytecode into human-readable source code. + *
    + * Decompiler services are active by default, and as such do not need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. + */ public interface DecompilerService extends EnigmaService { EnigmaServiceType TYPE = new EnigmaServiceType<>("decompiler", true); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java index 9a9a75b6a..45c0de7be 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaService.java @@ -1,5 +1,8 @@ package org.quiltmc.enigma.api.service; +/** + * An enigma service is a component that provides a specific feature or piece of functionality to enigma. + */ public interface EnigmaService { /** * The ID of this service. This should satisfy a few criteria: diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java index 66c8596a3..3ee29b112 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java @@ -7,6 +7,11 @@ import java.util.Set; +/** + * Jar indexer services analyse jar files as they're opened to collect information about their contents. + *
    + * Jar indexer services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. + */ public interface JarIndexerService extends EnigmaService { EnigmaServiceType TYPE = new EnigmaServiceType<>("jar_indexer", false); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java index 432d766ee..68cf610d2 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java @@ -11,6 +11,8 @@ /** * A name proposal service suggests default names for entries based on context from their types and surrounding mappings. + *
    + * Obfuscation test services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface NameProposalService extends EnigmaService { EnigmaServiceType TYPE = new EnigmaServiceType<>("name_proposal", false); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java index 9e76e581b..372e17b4e 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java @@ -2,6 +2,11 @@ import org.quiltmc.enigma.api.translation.representation.entry.Entry; +/** + * An obfuscation test service allows a plugin to override the deobfuscation status of an entry. + *
    + * Obfuscation test services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. + */ public interface ObfuscationTestService extends EnigmaService { EnigmaServiceType TYPE = new EnigmaServiceType<>("obfuscation_test", false); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java index 83c5604a0..854576292 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java @@ -14,13 +14,31 @@ import java.io.IOException; import java.nio.file.Path; +/** + * A read/write service defines a reader and/or a writer for mappings. + *
    + * The service is keyed by a {@link FileType file type}, which is specified by a file extension. There should be no more than one read/write service per file type. + *
    + * Read/write services are active by default, and as such do not need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. + */ public interface ReadWriteService extends EnigmaService, MappingsWriter, MappingsReader { EnigmaServiceType TYPE = new EnigmaServiceType<>("read_write", true); + /** + * A unique file type for this service to read/write. + * This can either represent a directory or a single file. + * @return the file type + */ FileType getFileType(); + /** + * {@return whether this service supports reading mappings} + */ boolean supportsReading(); + /** + * {@return whether this service supports writing mappings} + */ boolean supportsWriting(); static ReadWriteService create(@Nullable MappingsReader reader, @Nullable MappingsWriter writer, FileType fileType, String id) { From 3c5ed9c81f24995f56b16b62af14bd6f59bf17c2 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sat, 27 Apr 2024 11:19:06 -0500 Subject: [PATCH 30/45] fix some issues --- enigma-swing/build.gradle | 4 ++-- .../java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java | 4 ++-- enigma/src/main/resources/lang/en_us.json | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/enigma-swing/build.gradle b/enigma-swing/build.gradle index 0a647c7ed..5a009aa94 100644 --- a/enigma-swing/build.gradle +++ b/enigma-swing/build.gradle @@ -46,7 +46,7 @@ def registerTestTask(String name) { group("test") dependsOn(":enigma:${taskName}TestObf") dependsOn(":enigma:processResources") - mainClass = 'org.quiltmc.enigma.gui.Main' + mainClass = application.mainClass classpath = sourceSets.test.runtimeClasspath def jar = project(":enigma").file("build/test-obf/${name}.jar") @@ -61,7 +61,7 @@ def registerTestTask(String name) { tasks.register("${taskName}TestGui2", JavaExec.class) { group("test") dependsOn(":enigma:${taskName}TestObf") - mainClass = 'org.quiltmc.enigma.gui.Main' + mainClass = application.mainClass classpath = sourceSets.test.runtimeClasspath def jar = project(":enigma").file("build/test-obf/${name}.jar") diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java index 9f7c77c43..d460ef842 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/ExtensionFileFilter.java @@ -25,8 +25,8 @@ public final class ExtensionFileFilter extends FileFilter { public ExtensionFileFilter(String formatName, List extensions) { this.formatName = formatName; this.extensions = extensions.stream().peek(s -> { - if (s.contains(".")) { - throw new IllegalArgumentException("extensions cannot contain dots!"); + if (s.startsWith(".")) { + throw new IllegalArgumentException("extensions cannot start with dots!"); } }).map(s -> "." + s).toList(); } diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index ab8efe7da..f5e24706f 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -29,6 +29,7 @@ "menu.file.save.non_writeable": "Format \"%s\" is not writeable!", "menu.file.save.cannot_save": "Cannot save mappings!", "menu.file.open.non_parseable": "Format \"%s\" is not parseable!", + "menu.file.open.non_parseable.unsupported_format": "Format \"%s\" is not parseable: unsupported format!", "menu.file.open.cannot_open": "Cannot open mappings!", "menu.file.reload_mappings": "Reload Mappings", "menu.file.reload_all": "Reload Jar/Mappings", From 3874707eaafffaaf6c650e180820cd4e40f46c9d Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 17:07:25 -0500 Subject: [PATCH 31/45] reintroduce EnigmaServiceType.create --- .../org/quiltmc/enigma/api/service/DecompilerService.java | 2 +- .../org/quiltmc/enigma/api/service/EnigmaServiceType.java | 8 ++++++++ .../org/quiltmc/enigma/api/service/JarIndexerService.java | 2 +- .../quiltmc/enigma/api/service/NameProposalService.java | 2 +- .../enigma/api/service/ObfuscationTestService.java | 2 +- .../org/quiltmc/enigma/api/service/ReadWriteService.java | 2 +- 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java index c4f49684b..fa5c4092a 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/DecompilerService.java @@ -10,7 +10,7 @@ * Decompiler services are active by default, and as such do not need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface DecompilerService extends EnigmaService { - EnigmaServiceType TYPE = new EnigmaServiceType<>("decompiler", true); + EnigmaServiceType TYPE = EnigmaServiceType.create("decompiler", true); Decompiler create(ClassProvider classProvider, SourceSettings settings); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java index bb651ff42..a5be33bb4 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/EnigmaServiceType.java @@ -4,6 +4,14 @@ public record EnigmaServiceType( String key, boolean activeByDefault ) { + public static EnigmaServiceType create(String key, boolean activeByDefault) { + return new EnigmaServiceType<>(key, activeByDefault); + } + + public static EnigmaServiceType create(String key) { + return new EnigmaServiceType<>(key, false); + } + /** * The unique key of this service type. */ diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java index 3ee29b112..12c79ce72 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/JarIndexerService.java @@ -13,7 +13,7 @@ * Jar indexer services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface JarIndexerService extends EnigmaService { - EnigmaServiceType TYPE = new EnigmaServiceType<>("jar_indexer", false); + EnigmaServiceType TYPE = EnigmaServiceType.create("jar_indexer"); void acceptJar(Set scope, ClassProvider classProvider, JarIndex jarIndex); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java index 68cf610d2..37fd97897 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java @@ -15,7 +15,7 @@ * Obfuscation test services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface NameProposalService extends EnigmaService { - EnigmaServiceType TYPE = new EnigmaServiceType<>("name_proposal", false); + EnigmaServiceType TYPE = EnigmaServiceType.create("name_proposal"); /** * Runs when a new JAR file is opened. Note that at this point, no mapping context will exist in the remapper. diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java index 372e17b4e..ca883773d 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ObfuscationTestService.java @@ -8,7 +8,7 @@ * Obfuscation test services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface ObfuscationTestService extends EnigmaService { - EnigmaServiceType TYPE = new EnigmaServiceType<>("obfuscation_test", false); + EnigmaServiceType TYPE = EnigmaServiceType.create("obfuscation_test"); boolean testDeobfuscated(Entry entry); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java index 854576292..9cdfe09f3 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/ReadWriteService.java @@ -22,7 +22,7 @@ * Read/write services are active by default, and as such do not need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface ReadWriteService extends EnigmaService, MappingsWriter, MappingsReader { - EnigmaServiceType TYPE = new EnigmaServiceType<>("read_write", true); + EnigmaServiceType TYPE = EnigmaServiceType.create("read_write", true); /** * A unique file type for this service to read/write. From 85d3a1970adacbc218e806248c965e723083c548 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 17:11:48 -0500 Subject: [PATCH 32/45] private tinyWriter constructor --- .../api/translation/mapping/serde/tinyv2/TinyV2Writer.java | 2 +- .../quiltmc/enigma/translation/mapping/TestComments.java | 6 ++++-- .../org/quiltmc/enigma/translation/mapping/TestV2Main.java | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java index 3cfe02588..ec658ff6b 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -32,7 +32,7 @@ public final class TinyV2Writer implements MappingsWriter { public static final MappingsWriter INSTANCE = new TinyV2Writer(); private static final String MINOR_VERSION = "0"; - public TinyV2Writer() { + private TinyV2Writer() { } private static int getEntryKind(Entry e) { diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java index 8095ea3be..0dc32035d 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestComments.java @@ -1,6 +1,7 @@ package org.quiltmc.enigma.translation.mapping; import org.quiltmc.enigma.TestUtil; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; @@ -8,7 +9,6 @@ import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; -import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.junit.jupiter.api.Test; @@ -24,6 +24,8 @@ public void testParseAndWrite() throws IOException, MappingParseException { EntryTree mappings = EnigmaMappingsReader.DIRECTORY.read( DIRECTORY); - new TinyV2Writer().write(mappings, MappingDelta.added(mappings), DIRECTORY.resolve("convertedtiny.tiny"), ProgressListener.createEmpty(), params); + Path file = DIRECTORY.resolve("convertedtiny.tiny"); + + Enigma.create().getReadWriteService(file).get().write(mappings, MappingDelta.added(mappings), file, ProgressListener.createEmpty(), params); } } diff --git a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java index 56a55a9a1..130db80c5 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java +++ b/enigma/src/test/java/org/quiltmc/enigma/translation/mapping/TestV2Main.java @@ -1,12 +1,12 @@ package org.quiltmc.enigma.translation.mapping; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.MappingDelta; import org.quiltmc.enigma.api.translation.mapping.serde.MappingFileNameFormat; import org.quiltmc.enigma.api.translation.mapping.serde.MappingSaveParameters; import org.quiltmc.enigma.api.translation.mapping.serde.enigma.EnigmaMappingsReader; -import org.quiltmc.enigma.api.translation.mapping.serde.tinyv2.TinyV2Writer; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import java.nio.file.Path; @@ -19,7 +19,8 @@ public static void main(String... args) throws Exception { MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, "obf", "deobf"); EntryTree tree = EnigmaMappingsReader.DIRECTORY.read(path); + Path file = Paths.get("currentYarn.tiny"); - new TinyV2Writer().write(tree, MappingDelta.added(tree), Paths.get("currentYarn.tiny"), ProgressListener.createEmpty(), parameters); + Enigma.create().getReadWriteService(file).get().write(tree, MappingDelta.added(tree), file, ProgressListener.createEmpty(), parameters); } } From 17dda8351d6fd09796bdf45010e47631581a3008 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 17:48:58 -0500 Subject: [PATCH 33/45] improve MappingFileNameFormat docs --- .../translation/mapping/serde/MappingFileNameFormat.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java index 8c9462b6e..2e1b5a005 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingFileNameFormat.java @@ -7,12 +7,14 @@ */ public enum MappingFileNameFormat { /** - * Names files based on their obfuscated names. + * Names files based on the mappings' obfuscated names. + * Example: if a class is mapped from {@code a} to {@code GreatClass}, its file will be named {@code GreatClass}. */ @SerializedName("by_obf") BY_OBF, /** - * Names files based on their deobfuscated names. + * Names files based on the mappings' deobfuscated names. + * Example: if a class is mapped from {@code a} to {@code GreatClass}, its file will be named {@code a}. */ @SerializedName("by_deobf") BY_DEOBF From 70320bec607740f368b21d7fb1be5d199fba05b2 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 17:49:17 -0500 Subject: [PATCH 34/45] Update enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java Co-authored-by: Iota <47987888+IotaBread@users.noreply.github.com> --- .../org/quiltmc/enigma/api/service/NameProposalService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java index 37fd97897..a00ae418a 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/service/NameProposalService.java @@ -12,7 +12,7 @@ /** * A name proposal service suggests default names for entries based on context from their types and surrounding mappings. *
    - * Obfuscation test services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. + * Name proposal services are not active by default, and need to be specified in the {@link org.quiltmc.enigma.api.EnigmaProfile profile}. */ public interface NameProposalService extends EnigmaService { EnigmaServiceType TYPE = EnigmaServiceType.create("name_proposal"); From 9857315ba162de33ac4b023e1daf19475d38abee Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 18:06:36 -0500 Subject: [PATCH 35/45] Update enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java Co-authored-by: Iota <47987888+IotaBread@users.noreply.github.com> --- .../java/org/quiltmc/enigma/command/InvertMappingsCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java index 882b0d49f..ed5fe916c 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/InvertMappingsCommand.java @@ -44,7 +44,7 @@ public String getDescription() { public static void run(Path sourceFile, Path resultFile, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws MappingParseException, IOException { MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); - Enigma enigma = Enigma.builder().build(); + Enigma enigma = createEnigma(); var readService = CommandsUtil.getReader(enigma, sourceFile); var writeService = CommandsUtil.getWriter(enigma, resultFile); From e3040aa8d81bc47e68060fb1603a5b3ab4d1851a Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 18:07:58 -0500 Subject: [PATCH 36/45] Update enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java Co-authored-by: Iota <47987888+IotaBread@users.noreply.github.com> --- .../quiltmc/enigma/command/FillClassMappingsCommandTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java b/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java index 34300ea33..c77c7bca6 100644 --- a/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java +++ b/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java @@ -50,7 +50,7 @@ public void test() throws Exception { Path resultFile = Files.createTempFile("fillClassMappings", ".mappings"); FillClassMappingsCommand.run(JAR, MAPPINGS, resultFile, false, null, null); - MappingsReader reader = CommandsUtil.getReader(Enigma.create(), resultFile); + MappingsReader reader = CommandsUtil.getReader(createEnigma(), resultFile); EntryTree result = reader.read(resultFile); assertEquals("A_Anonymous", getName(result, A)); From 0c079ac1892c314c553c1261e058e487e84b742c Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 18:09:44 -0500 Subject: [PATCH 37/45] only create enigma instances once in commands --- .../quiltmc/enigma/command/ComposeMappingsCommand.java | 8 +++++--- .../quiltmc/enigma/command/ConvertMappingsCommand.java | 6 ++++-- .../quiltmc/enigma/command/FillClassMappingsCommand.java | 6 ++++-- .../enigma/command/MapSpecializedMethodsCommand.java | 6 ++++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java index 36724eb92..263b485cb 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ComposeMappingsCommand.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.command; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.serde.MappingsReader; import org.quiltmc.enigma.util.MappingOperations; @@ -48,14 +49,15 @@ public String getDescription() { public static void run(Path leftFile, Path rightFile, Path resultFile, String keepMode, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws IOException, MappingParseException { MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); + Enigma enigma = createEnigma(); - MappingsReader leftReader = CommandsUtil.getReader(createEnigma(), leftFile); + MappingsReader leftReader = CommandsUtil.getReader(enigma, leftFile); EntryTree left = leftReader.read(leftFile); - MappingsReader rightReader = CommandsUtil.getReader(createEnigma(), rightFile); + MappingsReader rightReader = CommandsUtil.getReader(enigma, rightFile); EntryTree right = rightReader.read(rightFile); EntryTree result = MappingOperations.compose(left, right, keepMode.equals("left") || keepMode.equals("both"), keepMode.equals("right") || keepMode.equals("both")); - MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), resultFile); + MappingsWriter writer = CommandsUtil.getWriter(enigma, resultFile); Utils.delete(resultFile); writer.write(result, resultFile, ProgressListener.createEmpty(), saveParameters); } diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java index 6b9d76ce7..2b2a7bc66 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/ConvertMappingsCommand.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.command; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; @@ -45,12 +46,13 @@ public String getDescription() { public static void run(Path source, Path output, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws MappingParseException, IOException { MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); + Enigma enigma = createEnigma(); - MappingsReader reader = CommandsUtil.getReader(createEnigma(), source); + MappingsReader reader = CommandsUtil.getReader(enigma, source); EntryTree mappings = reader.read(source); Utils.delete(output); - MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), output); + MappingsWriter writer = CommandsUtil.getWriter(enigma, output); writer.write(mappings, output, ProgressListener.createEmpty(), saveParameters); } } diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java index e19728882..947d5ebea 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/FillClassMappingsCommand.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.command; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.analysis.index.jar.JarIndex; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; @@ -55,15 +56,16 @@ public String getDescription() { public static void run(Path jar, Path source, Path result, boolean fillAll, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws Exception { boolean debug = shouldDebug(new FillClassMappingsCommand().getName()); JarIndex jarIndex = loadJar(jar); + Enigma enigma = createEnigma(); Logger.info("Reading mappings..."); MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); - EntryTree sourceMappings = readMappings(createEnigma(), source, ProgressListener.createEmpty()); + EntryTree sourceMappings = readMappings(enigma, source, ProgressListener.createEmpty()); EntryTree resultMappings = exec(jarIndex, sourceMappings, fillAll, debug); Logger.info("Writing mappings..."); - MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), result); + MappingsWriter writer = CommandsUtil.getWriter(enigma, result); Utils.delete(result); writer.write(resultMappings, result, ProgressListener.createEmpty(), saveParameters); diff --git a/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java b/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java index 6ae3dba52..22aea0304 100644 --- a/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java +++ b/enigma-cli/src/main/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommand.java @@ -1,5 +1,6 @@ package org.quiltmc.enigma.command; +import org.quiltmc.enigma.api.Enigma; import org.quiltmc.enigma.api.analysis.index.jar.BridgeMethodIndex; import org.quiltmc.enigma.api.analysis.index.jar.JarIndex; import org.quiltmc.enigma.api.translation.MappingTranslator; @@ -56,15 +57,16 @@ public String getDescription() { public static void run(Path jar, Path sourcePath, Path output, @Nullable String obfuscatedNamespace, @Nullable String deobfuscatedNamespace) throws IOException, MappingParseException { boolean debug = shouldDebug(new MapSpecializedMethodsCommand().getName()); JarIndex jarIndex = loadJar(jar); + Enigma enigma = createEnigma(); MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF, false, obfuscatedNamespace, deobfuscatedNamespace); - MappingsReader reader = CommandsUtil.getReader(createEnigma(), sourcePath); + MappingsReader reader = CommandsUtil.getReader(enigma, sourcePath); EntryTree source = reader.read(sourcePath); EntryTree result = run(jarIndex, source, debug); Utils.delete(output); - MappingsWriter writer = CommandsUtil.getWriter(createEnigma(), output); + MappingsWriter writer = CommandsUtil.getWriter(enigma, output); writer.write(result, output, saveParameters); if (debug) { From 430b66f849eac69c5f69877e4046f13070ad46b1 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 18:15:31 -0500 Subject: [PATCH 38/45] Update enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java Co-authored-by: Iota <47987888+IotaBread@users.noreply.github.com> --- .../enigma/command/MapSpecializedMethodsCommandTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java b/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java index 8161e4b47..dda44e54a 100644 --- a/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java +++ b/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java @@ -63,7 +63,7 @@ public void test() throws Exception { Path resultFile = Files.createTempFile("mapSpecializedMethods", ".mappings"); MapSpecializedMethodsCommand.run(JAR, MAPPINGS, resultFile, null, null); - MappingsReader reader = CommandsUtil.getReader(Enigma.create(), resultFile); + MappingsReader reader = CommandsUtil.getReader(createEnigma(), resultFile); EntryTree result = reader.read(resultFile, ProgressListener.createEmpty()); assertNotNull(result.findNode(BASE_CLASS)); From eb381ed1010303a984f2fb4f66cf7dc490733868 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Wed, 1 May 2024 18:29:40 -0500 Subject: [PATCH 39/45] use WalkFileTree --- .../command/FillClassMappingsCommandTest.java | 2 +- .../java/org/quiltmc/enigma/api/Enigma.java | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java b/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java index c77c7bca6..34300ea33 100644 --- a/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java +++ b/enigma-cli/src/test/java/org/quiltmc/enigma/command/FillClassMappingsCommandTest.java @@ -50,7 +50,7 @@ public void test() throws Exception { Path resultFile = Files.createTempFile("fillClassMappings", ".mappings"); FillClassMappingsCommand.run(JAR, MAPPINGS, resultFile, false, null, null); - MappingsReader reader = CommandsUtil.getReader(createEnigma(), resultFile); + MappingsReader reader = CommandsUtil.getReader(Enigma.create(), resultFile); EntryTree result = reader.read(resultFile); assertEquals("A_Anonymous", getName(result, A)); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 57957e374..3e1fd52ec 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -32,8 +32,11 @@ import javax.annotation.Nullable; import java.io.File; import java.io.IOException; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -44,6 +47,7 @@ import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; public class Enigma { @@ -182,28 +186,27 @@ public Optional parseFileType(Path path) { if (Files.isDirectory(path)) { try { - Optional firstFile = Arrays.stream(Objects.requireNonNull(path.toFile().listFiles())) - .flatMap(file -> { - if (file.isDirectory()) { - var files = file.listFiles(); - if (files != null) { - return Arrays.stream(files); - } - } else { - return Stream.of(file); - } - - return Stream.empty(); - }) - .findFirst(); - - if (firstFile.isPresent()) { + final AtomicReference> firstFile = new AtomicReference<>(); + Files.walkFileTree(path, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + super.visitFile(file, attrs); + if (attrs.isRegularFile()) { + firstFile.set(Optional.of(file.toFile())); + return FileVisitResult.TERMINATE; + } + + return FileVisitResult.CONTINUE; + } + }); + + if (firstFile.get().isPresent()) { for (FileType type : supportedTypes) { if (!type.isDirectory()) { continue; } - String extension = MoreFiles.getFileExtension(firstFile.get().toPath()).toLowerCase(); + String extension = MoreFiles.getFileExtension(firstFile.get().get().toPath()).toLowerCase(); if (type.getExtensions().contains(extension)) { return Optional.of(type); } From d0f1eda43b034061c73274a089f15d6da7af9763 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Thu, 2 May 2024 09:35:54 -0500 Subject: [PATCH 40/45] superfluous variable in build.gradle --- enigma-swing/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/enigma-swing/build.gradle b/enigma-swing/build.gradle index 5a009aa94..37c93075d 100644 --- a/enigma-swing/build.gradle +++ b/enigma-swing/build.gradle @@ -19,9 +19,8 @@ dependencies { testImplementation(testFixtures(project(':enigma'))) } -final String mainClassName = 'org.quiltmc.enigma.gui.Main' application { - mainClass = mainClassName + mainClass = 'org.quiltmc.enigma.gui.Main' jar.manifest.attributes 'Main-Class': mainClass } From 629f5aa97ea17c6a93cb79e7a144075cecd92264 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Thu, 2 May 2024 09:36:49 -0500 Subject: [PATCH 41/45] new default namespaces --- .../api/translation/mapping/serde/tinyv2/TinyV2Writer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java index ec658ff6b..24ace2f4d 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -77,12 +77,12 @@ private static Comparator> mappingComparator() { public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { String obfNamespace = parameters.obfuscatedNamespace(); if (obfNamespace == null) { - obfNamespace = "intermediary"; + obfNamespace = "obfuscated"; } String deobfNamespace = parameters.deobfuscatedNamespace(); if (deobfNamespace == null) { - deobfNamespace = "named"; + deobfNamespace = "deobfuscated"; } List> classes = StreamSupport.stream(mappings.spliterator(), false) From e14101ab5aa06a39db7636effd81d1f1a990c6db Mon Sep 17 00:00:00 2001 From: ix0rai Date: Thu, 2 May 2024 15:45:25 -0500 Subject: [PATCH 42/45] reimplement format detection test (no longer automatic :( ) --- .../enigma/MappingFormatDetectionTest.java | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java index 3f48e105d..7163124ee 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java @@ -1,7 +1,10 @@ package org.quiltmc.enigma; +import com.google.common.io.MoreFiles; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.quiltmc.enigma.api.Enigma; +import org.quiltmc.enigma.api.service.ReadWriteService; import org.quiltmc.enigma.api.translation.mapping.serde.FileType; import java.io.File; @@ -12,12 +15,33 @@ public class MappingFormatDetectionTest { @Test void testFormatDetection() { File formatsDir = new File(TestUtil.getResource("/formats").toUri()); + Enigma enigma = Enigma.create(); for (File file : Objects.requireNonNull(formatsDir.listFiles())) { - Optional parsedFormat = Enigma.create().parseFileType(file.toPath()); - // todo + Optional parsedFormat = enigma.getReadWriteService(file.toPath()); + if (parsedFormat.isEmpty()) { + Assertions.fail("Failed to detect format for " + file.getName()); + } - //Assertions.assertSame(expectedFormat, parsedFormat, "Failed to detect format for " + file.getName()); - } + String expectedId; + if (file.isDirectory()) { + Assertions.assertInstanceOf(FileType.Directory.class, parsedFormat.get().getFileType(), "Failed to detect directory format for " + file.getName()); + + expectedId = switch (file.getName()) { + case "enigma_directory_example_mapping" -> "enigma:enigma_directory"; + default -> throw new IllegalStateException("Unexpected read/write service ID: " + parsedFormat.get().getId()); + }; + } else { + expectedId = switch (MoreFiles.getFileExtension(file.toPath())) { + case "tiny" -> "enigma:tiny_v2"; + case "tsrg" -> "enigma:srg_file"; + case "txt" -> "enigma:proguard"; + case "zip" -> "enigma:enigma_zip"; + case "mapping" -> "enigma:enigma_file"; + default -> throw new IllegalStateException("Unexpected read/write service ID: " + parsedFormat.get().getId()); + }; + } + + Assertions.assertEquals(expectedId, parsedFormat.get().getId()); } } } From 1c2b78d1883eab2d217616048707b73c63d23615 Mon Sep 17 00:00:00 2001 From: ix0rai Date: Thu, 2 May 2024 15:50:22 -0500 Subject: [PATCH 43/45] checkstyle and NPE fix --- .../enigma/command/MapSpecializedMethodsCommandTest.java | 2 +- enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java | 5 ++--- .../java/org/quiltmc/enigma/MappingFormatDetectionTest.java | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java b/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java index dda44e54a..8161e4b47 100644 --- a/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java +++ b/enigma-cli/src/test/java/org/quiltmc/enigma/command/MapSpecializedMethodsCommandTest.java @@ -63,7 +63,7 @@ public void test() throws Exception { Path resultFile = Files.createTempFile("mapSpecializedMethods", ".mappings"); MapSpecializedMethodsCommand.run(JAR, MAPPINGS, resultFile, null, null); - MappingsReader reader = CommandsUtil.getReader(createEnigma(), resultFile); + MappingsReader reader = CommandsUtil.getReader(Enigma.create(), resultFile); EntryTree result = reader.read(resultFile, ProgressListener.createEmpty()); assertNotNull(result.findNode(BASE_CLASS)); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 3e1fd52ec..6599d153b 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -38,17 +38,14 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Stream; public class Enigma { public static final String NAME = "Enigma"; @@ -187,6 +184,8 @@ public Optional parseFileType(Path path) { if (Files.isDirectory(path)) { try { final AtomicReference> firstFile = new AtomicReference<>(); + firstFile.set(Optional.empty()); + Files.walkFileTree(path, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { diff --git a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java index 7163124ee..aa2340be8 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java @@ -42,6 +42,7 @@ void testFormatDetection() { }; } - Assertions.assertEquals(expectedId, parsedFormat.get().getId()); } + Assertions.assertEquals(expectedId, parsedFormat.get().getId()); + } } } From 08fefc636eb3b1dcaefc188fd58e064e832c65ff Mon Sep 17 00:00:00 2001 From: ix0rai Date: Thu, 2 May 2024 15:52:51 -0500 Subject: [PATCH 44/45] test format not found --- .../java/org/quiltmc/enigma/MappingFormatDetectionTest.java | 4 ++++ enigma/src/test/resources/formats/invalid_mapping.garbage | 1 + 2 files changed, 5 insertions(+) create mode 100644 enigma/src/test/resources/formats/invalid_mapping.garbage diff --git a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java index aa2340be8..262bc0a9d 100644 --- a/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java +++ b/enigma/src/test/java/org/quiltmc/enigma/MappingFormatDetectionTest.java @@ -20,6 +20,10 @@ void testFormatDetection() { for (File file : Objects.requireNonNull(formatsDir.listFiles())) { Optional parsedFormat = enigma.getReadWriteService(file.toPath()); if (parsedFormat.isEmpty()) { + if (file.getName().equals("invalid_mapping.garbage")) { + continue; + } + Assertions.fail("Failed to detect format for " + file.getName()); } diff --git a/enigma/src/test/resources/formats/invalid_mapping.garbage b/enigma/src/test/resources/formats/invalid_mapping.garbage new file mode 100644 index 000000000..5cde4fac1 --- /dev/null +++ b/enigma/src/test/resources/formats/invalid_mapping.garbage @@ -0,0 +1 @@ +hjgjghjkkghjggukuyyuiloyu From eedbc6d709a3b3045c442c8e258b8d395ad719fa Mon Sep 17 00:00:00 2001 From: ix0rai Date: Sun, 2 Jun 2024 22:29:45 -0500 Subject: [PATCH 45/45] remove pointless method --- .../org/quiltmc/enigma/gui/util/LanguageChangeListener.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/LanguageChangeListener.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/LanguageChangeListener.java index 818a909b8..805888d73 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/LanguageChangeListener.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/util/LanguageChangeListener.java @@ -2,8 +2,4 @@ public interface LanguageChangeListener { void retranslateUi(); - - default boolean isValid() { - return true; - } }