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 390542675..684e42761 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 @@ -196,6 +196,23 @@ public void openMappings(EntryTree mappings) { this.chp.invalidateJavadoc(); } + public void reloadMappingsForClass(ClassEntry entry) { + try { + EntryTree mappings = this.readWriteService.readClass(this.loadedMappingPath, entry, ProgressListener.createEmpty()); + this.reloadMappingsForClass(entry, mappings); + } catch (MappingParseException e) { + JOptionPane.showMessageDialog(this.gui.getFrame(), e.getMessage()); + } catch (Exception e) { + CrashDialog.show(e); + } + } + + public void reloadMappingsForClass(ClassEntry entry, EntryTree mappings) { + this.project.updateMappingsForClass(entry, mappings, new ProgressDialog(this.gui.getFrame())); + this.gui.reloadStats(entry, true); + this.chp.invalidateJavadoc(); + } + public void regenerateAndUpdateStatIcons() { if (Config.main().features.enableClassTreeStatIcons.value()) { ProgressListener progressListener = ProgressListener.createEmpty(); diff --git a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ClassSelectorPopupMenu.java b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ClassSelectorPopupMenu.java index 2d5e58d9b..f16697023 100644 --- a/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ClassSelectorPopupMenu.java +++ b/enigma-swing/src/main/java/org/quiltmc/enigma/gui/element/ClassSelectorPopupMenu.java @@ -26,6 +26,7 @@ public class ClassSelectorPopupMenu { private final JMenuItem renameClass = new JMenuItem(); private final JMenuItem regenerateStats = new JMenuItem(); private final JMenuItem toggleMapping = new JMenuItem(); + private final JMenuItem reloadMappings = new JMenuItem(); private final JMenuItem expandAll = new JMenuItem(); private final JMenuItem collapseAll = new JMenuItem(); @@ -40,6 +41,7 @@ public ClassSelectorPopupMenu(Gui gui, ClassesDocker docker) { } this.ui.add(this.regenerateStats); + this.ui.add(this.reloadMappings); this.ui.add(this.toggleMapping); this.ui.addSeparator(); this.ui.add(this.expandAll); @@ -67,6 +69,7 @@ public ClassSelectorPopupMenu(Gui gui, ClassesDocker docker) { }); this.regenerateStats.addActionListener(a -> this.gui.reloadStats(this.selector.getSelectedClassObf(), false)); + this.reloadMappings.addActionListener(l -> this.gui.getController().reloadMappingsForClass(this.selector.getSelectedClassObf())); this.expandAll.addActionListener(a -> this.selector.expandAll()); this.collapseAll.addActionListener(a -> this.selector.collapseAll()); @@ -134,6 +137,9 @@ public void show(ClassSelector selector, int x, int y) { // only enable regenerate stats if selected path is a class this.regenerateStats.setEnabled(selected != null); + // only enable reload mappings if selected path is a class and a read-write service exists to reload from + this.reloadMappings.setEnabled(selected != null && this.gui.getController().getReadWriteService() != null); + // update toggle mapping text to match this.toggleMapping.setEnabled(selected != null); if (selected != null) { @@ -155,5 +161,6 @@ public void retranslateUi() { this.collapseAll.setText(I18n.translate("popup_menu.class_selector.collapse_all")); this.toggleMapping.setText(I18n.translate("popup_menu.mark_deobfuscated")); this.regenerateStats.setText(I18n.translate("popup_menu.class_selector.regenerate_stats")); + this.reloadMappings.setText(I18n.translate("popup_menu.class_selector.reload_mappings")); } } 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 8cc423f3a..69a0a0272 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java @@ -9,6 +9,8 @@ import org.quiltmc.enigma.api.analysis.index.mapping.MappingsIndex; import org.quiltmc.enigma.api.service.ObfuscationTestService; import org.quiltmc.enigma.api.source.TokenType; +import org.quiltmc.enigma.api.translation.mapping.tree.DeltaTrackingTree; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTreeUtil; import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.impl.bytecode.translator.TranslationClassVisitor; @@ -21,8 +23,6 @@ import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.EntryRemapper; import org.quiltmc.enigma.impl.translation.mapping.MappingsChecker; -import org.quiltmc.enigma.api.translation.mapping.tree.DeltaTrackingTree; -import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; import org.quiltmc.enigma.api.translation.representation.entry.ClassDefEntry; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import org.quiltmc.enigma.api.translation.representation.entry.Entry; @@ -102,6 +102,16 @@ public void setMappings(@Nullable EntryTree mappings, ProgressList this.remapper.insertDynamicallyProposedMappings(null, null, null); } + public void updateMappingsForClass(ClassEntry classEntry, EntryTree mappings, ProgressListener progress) { + EntryTree jarProposedMappings = this.remapper != null ? this.remapper.getJarProposedMappings() : new HashEntryTree<>(); + EntryTree mergedTree = EntryTreeUtil.merge(jarProposedMappings, mappings); + this.mappingsIndex.reindexClass(classEntry, this.remapper.getMappings(), mergedTree, progress); + + this.remapper = EntryRemapper.mapped(this.enigma, this.jarIndex, this.mappingsIndex, jarProposedMappings, mappings, this.enigma.getNameProposalServices()); + + this.remapper.insertDynamicallyProposedMappings(null, null, null); + } + public Enigma getEnigma() { return this.enigma; } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/mapping/MappingsIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/mapping/MappingsIndex.java index 0e06c9e68..afa858f4d 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/mapping/MappingsIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/mapping/MappingsIndex.java @@ -72,6 +72,23 @@ public void indexMappings(EntryTree mappings, ProgressListener pro nodes.addAll(node.getNodesRecursively()); } + this.indexNodes(nodes, true); + } + + public void reindexClass(ClassEntry classEntry, EntryTree oldMappings, EntryTree newMappings, ProgressListener progress) { + this.progress = progress; + oldMappings.remove(classEntry); + + EntryTreeNode classNode = newMappings.findNode(classEntry); + if (classNode == null) { + return; // Mappings were either removed or never existed in the first place + } + + oldMappings.insert(classNode); + this.indexNodes(classNode.getNodesRecursively(), false); + } + + private void indexNodes(Collection> nodes, boolean postProcess) { this.work = nodes.isEmpty() ? 1 : nodes.size(); this.progress.init(this.work, I18n.translate("progress.mappings.indexing.mappings")); @@ -94,7 +111,9 @@ public void indexMappings(EntryTree mappings, ProgressListener pro this.progress.step(this.work++, I18n.translate("progress.mappings.indexing.mappings")); } - this.processIndex(this); + if (postProcess) { + this.processIndex(this); + } this.progress = null; this.work = 0; 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 9cdfe09f3..0b5def8d8 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 @@ -9,6 +9,7 @@ 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.api.translation.representation.entry.ClassEntry; import javax.annotation.Nullable; import java.io.IOException; @@ -61,6 +62,15 @@ public EntryTree read(Path path, ProgressListener progress) throws return reader.read(path, progress); } + @Override + public EntryTree readClass(Path path, ClassEntry classEntry, ProgressListener progress) throws MappingParseException, IOException { + if (reader == null) { + throw new UnsupportedOperationException("This service does not support reading!"); + } + + return reader.readClass(path, classEntry, progress); + } + @Override public boolean supportsReading() { return reader != null; diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsReader.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsReader.java index f1bf2c2a6..17a719105 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsReader.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/MappingsReader.java @@ -3,6 +3,7 @@ import org.quiltmc.enigma.api.ProgressListener; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; import java.io.IOException; import java.nio.file.Path; @@ -13,4 +14,12 @@ public interface MappingsReader { default EntryTree read(Path path) throws MappingParseException, IOException { return this.read(path, ProgressListener.createEmpty()); } + + /** + * Reads at least the mappings for the given class, but may read more depending on the implementation. + * @implSpec The default implementation calls {@link #read(Path, ProgressListener)}, which reads all mappings. + */ + default EntryTree readClass(Path path, ClassEntry classEntry, ProgressListener progress) throws MappingParseException, IOException { + return this.read(path, progress); + } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsReader.java b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsReader.java index a4cc6dbbb..4580eaff2 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsReader.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/translation/mapping/serde/enigma/EnigmaMappingsReader.java @@ -78,6 +78,20 @@ public EntryTree read(Path root, ProgressListener progress) throws return mappings; } + + @Override + public EntryTree readClass(Path path, ClassEntry classEntry, ProgressListener progress) throws MappingParseException, IOException { + progress.init(1, I18n.translate("progress.mappings.enigma_directory.loading")); + + EntryTree mappings = new HashEntryTree<>(); + Path mappingsFile = path.resolve(classEntry.getFullName() + ".mapping"); + if (Files.exists(mappingsFile) && Files.isRegularFile(mappingsFile)) { + readFile(mappingsFile, mappings); + } + + progress.step(1, I18n.translate("progress.mappings.enigma_directory.done")); + return mappings; + } }, ZIP { @Override @@ -86,6 +100,13 @@ public EntryTree read(Path zip, ProgressListener progress) throws return DIRECTORY.read(fs.getPath("/"), progress); } } + + @Override + public EntryTree readClass(Path zip, ClassEntry classEntry, ProgressListener progress) throws MappingParseException, IOException { + try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) { + return DIRECTORY.readClass(fs.getPath("/"), classEntry, progress); + } + } }; /** diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index 166b24270..830dd0465 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -132,6 +132,7 @@ "popup_menu.class_selector.move_package": "Move Package", "popup_menu.class_selector.rename_class": "Rename Class", "popup_menu.class_selector.regenerate_stats": "Regenerate Stats", + "popup_menu.class_selector.reload_mappings": "Reload Mappings", "popup_menu.class_selector.expand_all": "Expand All", "popup_menu.class_selector.collapse_all": "Collapse All", "popup_menu.class_selector.package_rename.rename_title": "Renaming package '%s'...",