From f89bb82412d8d0e5561a06f3ed09d4758aa57b88 Mon Sep 17 00:00:00 2001 From: Canrad <1517807724@qq.com> Date: Sun, 29 Jun 2025 15:59:25 +0800 Subject: [PATCH] Add toggle for assembly/decompiled view in deobfuscation preview --- .../recaf/ui/window/DeobfuscationWindow.java | 98 ++++++++++++++----- .../main/resources/translations/en_US.lang | 2 + .../main/resources/translations/zh_CN.lang | 2 + 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/window/DeobfuscationWindow.java b/recaf-ui/src/main/java/software/coley/recaf/ui/window/DeobfuscationWindow.java index 55c597c61..fa4db4c4f 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/window/DeobfuscationWindow.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/window/DeobfuscationWindow.java @@ -13,20 +13,8 @@ import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Label; -import javafx.scene.control.ListView; -import javafx.scene.control.SplitPane; -import javafx.scene.control.Tab; -import javafx.scene.control.TabPane; -import javafx.scene.control.TreeCell; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; +import javafx.scene.control.*; +import javafx.scene.layout.*; import javafx.scene.text.TextAlignment; import org.kordamp.ikonli.Ikon; import org.kordamp.ikonli.carbonicons.CarbonIcons; @@ -81,6 +69,10 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import software.coley.recaf.services.assembler.AssemblerPipelineManager; +import software.coley.recaf.services.assembler.JvmAssemblerPipeline; +import software.coley.recaf.path.ClassPathNode; +import software.coley.recaf.path.DirectoryPathNode; /** * Pane for previewing deobfuscation transformers. @@ -96,6 +88,7 @@ public class DeobfuscationWindow extends RecafStage { private final ObservableList transformerOrder = FXCollections.observableArrayList(); private final BooleanProperty hasSelection = new SimpleBooleanProperty(); private final WorkspaceManager workspaceManager; + private final AssemblerPipelineManager assemblerPipelineManager; @Inject @SuppressWarnings("unchecked") @@ -107,11 +100,13 @@ public DeobfuscationWindow(@Nonnull TransformationManager transformationManager, @Nonnull CellConfigurationService configurationService, @Nonnull Actions actions, @Nonnull WorkspaceExplorerConfig explorerConfig, - @Nonnull Instance searchBarProvider) { + @Nonnull Instance searchBarProvider, + @Nonnull AssemblerPipelineManager assemblerPipelineManager) { this.transformationManager = transformationManager; this.transformationApplierService = transformationApplierService; this.workspaceManager = workspaceManager; this.decompilerManager = decompilerManager; + this.assemblerPipelineManager = assemblerPipelineManager; BorderPane transformerTreePane = new BorderPane(); { @@ -316,12 +311,36 @@ protected void updateItem(CategorizedTransformer item, boolean isEmpty) { title.getStyleClass().add(Styles.TITLE_4); title.setPadding(new Insets(0, 0, 5, 5)); + HBox titleBox = new HBox(); + titleBox.setAlignment(Pos.CENTER_LEFT); + titleBox.getChildren().add(title); + + TransformPreview beforePreview = new TransformPreview(languageAssociation, searchBarProvider.get(), false); + TransformPreview afterPreview = new TransformPreview(languageAssociation, searchBarProvider.get(), true); + + // Create a shared toggleButton and add it to the title bar + ToggleButton toggleButton = new ToggleButton(); + toggleButton.setText(Lang.get("deobf.preview.switch-decompiler")); + toggleButton.setSelected(true); + toggleButton.selectedProperty().addListener((obs, oldVal, newVal) -> { + boolean showAssembly = newVal; + toggleButton.setText(showAssembly ? Lang.get("deobf.preview.switch-decompiler") : Lang.get("deobf.preview.disassemble-decompiler")); + // update preview + beforePreview.setShowAssembly(showAssembly); + afterPreview.setShowAssembly(showAssembly); + }); + + // Use Region elastic padding to push the toggleButton to the far right + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + titleBox.getChildren().add(spacer); + titleBox.getChildren().add(toggleButton); + titleBox.setSpacing(10); + Tab beforeTab = new Tab(); Tab afterTab = new Tab(); beforeTab.setClosable(false); afterTab.setClosable(false); - TransformPreview beforePreview = new TransformPreview(languageAssociation, searchBarProvider.get(), false); - TransformPreview afterPreview = new TransformPreview(languageAssociation, searchBarProvider.get(), true); beforeTab.setContent(beforePreview); afterTab.setContent(afterPreview); beforeTab.textProperty().bind(Lang.getBinding("misc.before")); @@ -374,7 +393,7 @@ protected void updateItem(CategorizedTransformer item, boolean isEmpty) { StackPane wrapper = new StackPane(tabs, tools); StackPane.setMargin(tools, new Insets(20)); wrapper.setAlignment(Pos.BOTTOM_RIGHT); - transformPreviewPane.setTop(title); + transformPreviewPane.setTop(titleBox); transformPreviewPane.setCenter(wrapper); transformPreviewPane.setBottom(tools); } @@ -417,6 +436,7 @@ private class TransformPreview extends BorderPane { private final boolean andApply; private final Editor editor; private ClassInfo classInfo; + private boolean showAssembly = true; private TransformPreview(@Nonnull FileTypeSyntaxAssociationService languageAssociation, @Nonnull SearchBar searchBar, boolean andApply) { this.andApply = andApply; @@ -433,6 +453,11 @@ public void setClassInfo(@Nonnull ClassInfo classInfo) { this.classInfo = classInfo; } + public void setShowAssembly(boolean showAssembly) { + this.showAssembly = showAssembly; + decompile(); + } + private void decompile() { if (classInfo == null) { editor.setText(Lang.get("deobf.preview.noselection")); @@ -462,15 +487,36 @@ private void decompile() { } } - // TODO: Pull out common decompile code with "AbstractDecompilePane" to do this more aesthetically - // - And by that I mean include the animation - // - And the error handling - // - But not the rest of the unrelated "editing" capabilities of the "AbstractDecompilePane" - decompilerManager.decompile(workspaceManager.getCurrent(), jvmClass).whenCompleteAsync((result, error) -> { - if (result != null) { - editor.setText(result.getText()); + if (showAssembly) { + // disassemble view + try { + JvmAssemblerPipeline pipeline = assemblerPipelineManager.newJvmAssemblerPipeline(workspaceManager.getCurrent()); + String packageName = jvmClass.getPackageName(); + DirectoryPathNode dirNode = new DirectoryPathNode(packageName); + ClassPathNode path = new ClassPathNode(dirNode, jvmClass); + var result = pipeline.disassemble(path); + if (result.isOk()) { + editor.setText(result.get()); + } else { + editor.setText("// Failed to disassemble: " + result.errors()); + } + } catch (Exception e) { + editor.setText("// Exception during disassembly: " + e.getMessage()); } - }, FxThreadUtil.executor()); + } else { + // TODO: Pull out common decompile code with "AbstractDecompilePane" to do this more aesthetically + // - And by that I mean include the animation + // - And the error handling + // - But not the rest of the unrelated "editing" capabilities of the "AbstractDecompilePane" + // decompiler view + decompilerManager.decompile(workspaceManager.getCurrent(), jvmClass).whenCompleteAsync((result, error) -> { + if (result != null) { + editor.setText(result.getText()); + } else if (error != null) { + editor.setText("// Decompile error: " + error.getMessage()); + } + }, FxThreadUtil.executor()); + } } } diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index d5be113e5..d298137b1 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -459,6 +459,8 @@ deobf.order.suc=Missing recommended successors deobf.preview.title=Preview deobf.preview.pick=Pick preview class deobf.preview.noselection=// No class is selected, pick a class to preview\n// the transformation before & after states +deobf.preview.switch-decompiler=Switch to decompiled view +deobf.preview.disassemble-decompiler=Switch to assembly view deobf.tree.generic=Generic deobf.tree.generic.anticrasher=Anti-Crasher deobf.tree.generic.optimize=Optimization diff --git a/recaf-ui/src/main/resources/translations/zh_CN.lang b/recaf-ui/src/main/resources/translations/zh_CN.lang index 7eca65c8b..5873f39c9 100644 --- a/recaf-ui/src/main/resources/translations/zh_CN.lang +++ b/recaf-ui/src/main/resources/translations/zh_CN.lang @@ -410,6 +410,8 @@ deobf.order.suc=缺少推荐的后置转换器 deobf.preview.title=预览 deobf.preview.pick=选择预览的类 deobf.preview.noselection=// 未选择任何类,请选择一个类进行预览\n// 以查看转换前后的状态 +deobf.preview.switch-decompiler=切换为反编译视图 +deobf.preview.disassemble-decompiler=切换为汇编视图 deobf.tree.generic=通用 deobf.tree.generic.anticrasher=防崩溃 deobf.tree.generic.optimize=优化