From d0b6f4b62a3271b8c6bcfa831d0d8f25851ee428 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 10 Sep 2025 12:53:26 +0100 Subject: Add a way for plugins to invalidate data (mappings, javadocs, decompile), and add a way for them to listen for this. --- .../main/java/cuchaz/enigma/gui/GuiController.java | 51 ++++++++++++++++++---- .../src/main/java/cuchaz/enigma/EnigmaProject.java | 29 ++++++++++++ .../cuchaz/enigma/api/DataInvalidationEvent.java | 31 +++++++++++++ .../enigma/api/DataInvalidationListener.java | 6 +++ .../java/cuchaz/enigma/api/view/ProjectView.java | 19 ++++++++ .../enigma/classhandle/ClassHandleProvider.java | 20 +++++++++ 6 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/DataInvalidationListener.java diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 3609427d..a9795bce 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -46,6 +46,8 @@ import cuchaz.enigma.analysis.MethodInheritanceTreeNode; import cuchaz.enigma.analysis.MethodReferenceTreeNode; import cuchaz.enigma.analysis.StructureTreeNode; import cuchaz.enigma.analysis.StructureTreeOptions; +import cuchaz.enigma.api.DataInvalidationEvent; +import cuchaz.enigma.api.DataInvalidationListener; import cuchaz.enigma.api.service.ObfuscationTestService; import cuchaz.enigma.api.view.GuiView; import cuchaz.enigma.api.view.entry.EntryReferenceView; @@ -94,7 +96,7 @@ import cuchaz.enigma.utils.Utils; import cuchaz.enigma.utils.validation.PrintValidatable; import cuchaz.enigma.utils.validation.ValidationContext; -public class GuiController implements ClientPacketHandler, GuiView { +public class GuiController implements ClientPacketHandler, GuiView, DataInvalidationListener { private final Gui gui; public final Enigma enigma; @@ -132,6 +134,7 @@ public class GuiController implements ClientPacketHandler, GuiView { return ProgressDialog.runOffThread(gui.getFrame(), progress -> { project = enigma.openJars(jarPaths, new ClasspathClassProvider(), progress); + project.addDataInvalidationListener(this); indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); chp = new ClassHandleProvider(project, UiConfig.getDecompiler().service); SwingUtilities.invokeLater(() -> { @@ -177,7 +180,7 @@ public class GuiController implements ClientPacketHandler, GuiView { loadedMappingPath = path; refreshClasses(); - chp.invalidateJavadoc(); + project.invalidateData(DataInvalidationEvent.InvalidationType.JAVADOC); } catch (MappingParseException e) { JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); } @@ -192,7 +195,7 @@ public class GuiController implements ClientPacketHandler, GuiView { project.setMappings(mappings); refreshClasses(); - chp.invalidateJavadoc(); + project.invalidateData(DataInvalidationEvent.InvalidationType.JAVADOC); } public MappingFormat getLoadedMappingFormat() { @@ -252,7 +255,7 @@ public class GuiController implements ClientPacketHandler, GuiView { this.gui.setMappingsFile(null); refreshClasses(); - chp.invalidateJavadoc(); + project.invalidateData(DataInvalidationEvent.InvalidationType.JAVADOC); } public void reloadAll() { @@ -573,12 +576,11 @@ public class GuiController implements ClientPacketHandler, GuiView { this.gui.moveClassTree(target, prev.targetName() == null, mapping.targetName() == null); } - if (!Objects.equals(prev.targetName(), mapping.targetName())) { - this.chp.invalidateMapped(); - } - if (!Objects.equals(prev.javadoc(), mapping.javadoc())) { - this.chp.invalidateJavadoc(target.getTopLevelClass()); + project.invalidateData(target.getTopLevelClass().getFullName(), DataInvalidationEvent.InvalidationType.JAVADOC); + // invalidateJavadoc implies invalidateMapped, so no need to check for that too + } else if (!Objects.equals(prev.targetName(), mapping.targetName())) { + project.invalidateData(DataInvalidationEvent.InvalidationType.MAPPINGS); } gui.showStructure(gui.getActiveEditor()); @@ -677,4 +679,35 @@ public class GuiController implements ClientPacketHandler, GuiView { public void updateUserList(List users) { gui.setUserList(users); } + + @Override + public void onDataInvalidated(DataInvalidationEvent event) { + Objects.requireNonNull(project, "Invalidating data when no project is open"); + + if (event.getClasses() == null) { + switch (event.getType()) { + case MAPPINGS -> chp.invalidateMapped(); + case JAVADOC -> chp.invalidateJavadoc(); + case DECOMPILE -> chp.invalidate(); + } + } else { + switch (event.getType()) { + case MAPPINGS -> { + for (String clazz : event.getClasses()) { + chp.invalidateMapped(new ClassEntry(clazz)); + } + } + case JAVADOC -> { + for (String clazz : event.getClasses()) { + chp.invalidateJavadoc(new ClassEntry(clazz)); + } + } + case DECOMPILE -> { + for (String clazz : event.getClasses()) { + chp.invalidate(new ClassEntry(clazz)); + } + } + } + } + } } diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java index 96ad433b..1e4afa2c 100644 --- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java @@ -6,6 +6,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -17,11 +18,14 @@ import java.util.jar.JarOutputStream; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.api.DataInvalidationEvent; +import cuchaz.enigma.api.DataInvalidationListener; import cuchaz.enigma.api.service.NameProposalService; import cuchaz.enigma.api.service.ObfuscationTestService; import cuchaz.enigma.api.view.ProjectView; @@ -57,6 +61,8 @@ public class EnigmaProject implements ProjectView { private EntryRemapper mapper; + private final List dataInvalidationListeners = new ArrayList<>(); + public EnigmaProject(Enigma enigma, List jarPaths, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) { if (jarChecksum.length != 20) { throw new IllegalArgumentException(); @@ -334,6 +340,29 @@ public class EnigmaProject implements ProjectView { return (T) mapper.extendedDeobfuscate((Translatable) entry).getValue(); } + @Override + public void addDataInvalidationListener(DataInvalidationListener listener) { + dataInvalidationListeners.add(listener); + } + + @Override + public void invalidateData(@Nullable Collection classes, DataInvalidationEvent.InvalidationType type) { + DataInvalidationEvent event = new DataInvalidationEvent() { + @Override + @Nullable + public Collection getClasses() { + return classes; + } + + @Override + public InvalidationType getType() { + return type; + } + }; + + dataInvalidationListeners.forEach(listener -> listener.onDataInvalidated(event)); + } + public static final class SourceExport { public final Collection decompiled; diff --git a/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java b/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java new file mode 100644 index 00000000..812ad397 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java @@ -0,0 +1,31 @@ +package cuchaz.enigma.api; + +import java.util.Collection; + +import org.jetbrains.annotations.Nullable; + +public interface DataInvalidationEvent { + /** + * The classes for which the invalidation applies, or {@code null} if the invalidation applies to all classes. + */ + @Nullable + Collection getClasses(); + + InvalidationType getType(); + + enum InvalidationType { + /** + * Only mappings are being invalidated. + */ + MAPPINGS, + /** + * Javadocs are being invalidated. This also implies {@link #MAPPINGS}. + */ + JAVADOC, + /** + * Context passed to the decompiler, such as the bytecode input or other parameters, is being invalidated. This + * also implies {@link #JAVADOC} and {@link #MAPPINGS}. + */ + DECOMPILE, + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationListener.java b/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationListener.java new file mode 100644 index 00000000..a07b109d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationListener.java @@ -0,0 +1,6 @@ +package cuchaz.enigma.api; + +@FunctionalInterface +public interface DataInvalidationListener { + void onDataInvalidated(DataInvalidationEvent event); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java b/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java index e07645a7..eae776bc 100644 --- a/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java +++ b/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java @@ -1,7 +1,26 @@ package cuchaz.enigma.api.view; +import java.util.Collection; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import cuchaz.enigma.api.DataInvalidationEvent; +import cuchaz.enigma.api.DataInvalidationListener; import cuchaz.enigma.api.view.entry.EntryView; public interface ProjectView { T deobfuscate(T entry); + + void addDataInvalidationListener(DataInvalidationListener listener); + + default void invalidateData(DataInvalidationEvent.InvalidationType type) { + invalidateData((Collection) null, type); + } + + default void invalidateData(String className, DataInvalidationEvent.InvalidationType type) { + invalidateData(List.of(className), type); + } + + void invalidateData(@Nullable Collection classes, DataInvalidationEvent.InvalidationType type); } diff --git a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java index f4bbb1da..f441bf49 100644 --- a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java +++ b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java @@ -163,6 +163,26 @@ public final class ClassHandleProvider { }); } + public void invalidate() { + withLock(lock.readLock(), () -> { + handles.values().forEach(Entry::invalidate); + }); + } + + public void invalidate(ClassEntry entry) { + withLock(lock.readLock(), () -> { + Entry e = handles.get(entry); + + if (e != null) { + e.invalidate(); + } + + if (entry.isInnerClass()) { + this.invalidate(entry.getOuterClass()); + } + }); + } + private void deleteEntry(Entry entry) { withLock(lock.writeLock(), () -> { handles.remove(entry.entry); -- cgit v1.2.3