diff --git a/build.gradle b/build.gradle index 05f2cf7..4966a42 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } group 'cn.enaium' -version '0.3.0' +version '0.4.0' sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/src/main/java/cn/enaium/joe/JavaOctetEditor.java b/src/main/java/cn/enaium/joe/JavaOctetEditor.java index d694244..2cea340 100644 --- a/src/main/java/cn/enaium/joe/JavaOctetEditor.java +++ b/src/main/java/cn/enaium/joe/JavaOctetEditor.java @@ -22,6 +22,7 @@ import cn.enaium.joe.gui.panel.file.tree.FileTreePanel; import cn.enaium.joe.gui.panel.menu.FileMenu; import cn.enaium.joe.gui.panel.menu.HelpMenu; +import cn.enaium.joe.gui.panel.menu.SearchMenu; import cn.enaium.joe.jar.Jar; import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; @@ -58,6 +59,7 @@ public void run() { window.setJMenuBar(new JMenuBar() {{ add(new FileMenu()); add(new HelpMenu()); + add(new SearchMenu()); }}); window.setContentPane(new JPanel(new BorderLayout()) { diff --git a/src/main/java/cn/enaium/joe/gui/panel/CodeArea.java b/src/main/java/cn/enaium/joe/dialog/SearchDialog.java similarity index 52% rename from src/main/java/cn/enaium/joe/gui/panel/CodeArea.java rename to src/main/java/cn/enaium/joe/dialog/SearchDialog.java index 938899b..35dd778 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/CodeArea.java +++ b/src/main/java/cn/enaium/joe/dialog/SearchDialog.java @@ -14,26 +14,22 @@ * limitations under the License. */ -package cn.enaium.joe.gui.panel; +package cn.enaium.joe.dialog; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.Theme; +import cn.enaium.joe.gui.panel.search.Result; -import java.io.IOException; +import java.awt.*; /** * @author Enaium */ -public class CodeArea extends RSyntaxTextArea { - public CodeArea() { - setCodeFoldingEnabled(true); - setEditable(false); - Theme theme = null; - try { - theme = Theme.load(getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/dark.xml")); - } catch (IOException e) { - throw new RuntimeException(e); - } - theme.apply(this); +public class SearchDialog extends Dialog { + public Result result = new Result(); + + public SearchDialog() { + super("Search"); + setLayout(new BorderLayout()); + setSize(400, 300); + add(result, BorderLayout.CENTER); } } diff --git a/src/main/java/cn/enaium/joe/dialog/search/SearchLdcDialog.java b/src/main/java/cn/enaium/joe/dialog/search/SearchLdcDialog.java new file mode 100644 index 0000000..61560b6 --- /dev/null +++ b/src/main/java/cn/enaium/joe/dialog/search/SearchLdcDialog.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.dialog.search; + +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.dialog.SearchDialog; +import cn.enaium.joe.gui.panel.search.ResultNode; +import cn.enaium.joe.jar.Jar; +import cn.enaium.joe.util.ASyncUtil; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import javax.swing.*; +import java.awt.*; +import java.util.Map; + +/** + * @author Enaium + */ +public class SearchLdcDialog extends SearchDialog { + public SearchLdcDialog() { + setTitle("Search LDC"); + add(new JPanel(new FlowLayout()) {{ + JTextField text = new JTextField(15); + add(text); + add(new JButton("Search") {{ + addActionListener(e -> { + if (!text.getText().replace(" ", "").isEmpty()) { + Jar jar = JavaOctetEditor.getInstance().jar; + ASyncUtil.execute(() -> { + float loaded = 0; + float total = 0; + for (Map.Entry stringClassNodeEntry : jar.classes.entrySet()) { + for (MethodNode method : stringClassNodeEntry.getValue().methods) { + total += method.instructions.size(); + } + } + + for (Map.Entry stringClassNodeEntry : jar.classes.entrySet()) { + for (MethodNode method : stringClassNodeEntry.getValue().methods) { + + for (AbstractInsnNode instruction : method.instructions) { + if (instruction instanceof LdcInsnNode) { + String ldc = ((LdcInsnNode) instruction).cst.toString(); + if (ldc.contains(text.getText())) { + ((DefaultListModel) result.getList().getModel()).addElement(new ResultNode(stringClassNodeEntry.getValue(), ldc)); + } + } + JavaOctetEditor.getInstance().bottomPanel.setProcess((int) ((loaded++ / total) * 100f)); + } + } + } + JavaOctetEditor.getInstance().bottomPanel.setProcess(0); + }); + } + }); + }}); + }}, BorderLayout.SOUTH); + } +} diff --git a/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java b/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java new file mode 100644 index 0000000..663444e --- /dev/null +++ b/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java @@ -0,0 +1,99 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.gui.panel; + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.RTextScrollPane; +import org.fife.ui.rtextarea.SearchContext; +import org.fife.ui.rtextarea.SearchEngine; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +/** + * @author Enaium + */ +public class CodeAreaPanel extends JPanel implements ActionListener { + + private RSyntaxTextArea textArea; + private JTextField searchField; + private JCheckBox regexCB; + private JCheckBox matchCaseCB; + + public CodeAreaPanel() { + super(new BorderLayout()); + textArea = new RSyntaxTextArea(); + textArea.setCodeFoldingEnabled(true); + textArea.setEditable(false); + Theme theme = null; + try { + theme = Theme.load(getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/dark.xml")); + } catch (IOException e) { + throw new RuntimeException(e); + } + theme.apply(textArea); + + add(new RTextScrollPane(textArea), BorderLayout.CENTER); + JToolBar toolBar = new JToolBar(); + searchField = new JTextField(30); + toolBar.add(searchField); + final JButton nextButton = new JButton("Find Next"); + nextButton.setActionCommand("FindNext"); + nextButton.addActionListener(this); + toolBar.add(nextButton); + searchField.addActionListener(e -> nextButton.doClick(0)); + JButton prevButton = new JButton("Find Previous"); + prevButton.setActionCommand("FindPrev"); + prevButton.addActionListener(this); + toolBar.add(prevButton); + regexCB = new JCheckBox("Regex"); + toolBar.add(regexCB); + matchCaseCB = new JCheckBox("Match Case"); + toolBar.add(matchCaseCB); + add(toolBar, BorderLayout.NORTH); + } + + @Override + public void actionPerformed(ActionEvent e) { + + // "FindNext" => search forward, "FindPrev" => search backward + String command = e.getActionCommand(); + boolean forward = "FindNext".equals(command); + + // Create an object defining our search parameters. + SearchContext context = new SearchContext(); + String text = searchField.getText(); + if (text.length() == 0) { + return; + } + context.setSearchFor(text); + context.setMatchCase(matchCaseCB.isSelected()); + context.setRegularExpression(regexCB.isSelected()); + context.setSearchForward(forward); + context.setWholeWord(false); + + SearchEngine.find(textArea, context); + } + + public RSyntaxTextArea getTextArea() { + return textArea; + } +} diff --git a/src/main/java/cn/enaium/joe/gui/panel/LeftPanel.java b/src/main/java/cn/enaium/joe/gui/panel/LeftPanel.java index dff74b7..a2514f6 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/LeftPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/LeftPanel.java @@ -19,6 +19,7 @@ import cn.enaium.joe.JavaOctetEditor; import cn.enaium.joe.gui.panel.file.tree.FileTreePanel; import cn.enaium.joe.jar.Jar; +import cn.enaium.joe.util.JTreeUtil; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -50,10 +51,25 @@ public void keyPressed(KeyEvent e) { if (!jTextField.getText().replace(" ", "").isEmpty()) { Jar searchedJar = jar.copy(); - searchedJar.classes = searchedJar.classes.entrySet().stream().filter(stringClassNodeEntry -> stringClassNodeEntry.getKey().toLowerCase(Locale.ROOT).contains(jTextField.getText().toLowerCase(Locale.ROOT))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - searchedJar.resources = searchedJar.resources.entrySet().stream().filter(stringEntry -> stringEntry.getKey().toLowerCase(Locale.ROOT).contains(jTextField.getText().toLowerCase(Locale.ROOT))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + searchedJar.classes = searchedJar.classes.entrySet().stream().filter(stringClassNodeEntry -> { + String key = stringClassNodeEntry.getKey(); + + if (!key.contains("/")) { + key = key.substring(key.lastIndexOf("/") + 1); + } + + return key.toLowerCase(Locale.ROOT).contains(jTextField.getText().toLowerCase(Locale.ROOT)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + searchedJar.resources = searchedJar.resources.entrySet().stream().filter(stringEntry -> { + String key = stringEntry.getKey(); + if (!key.contains("/")) { + key = key.substring(key.lastIndexOf("/") + 1); + } + return key.toLowerCase(Locale.ROOT).contains(jTextField.getText().toLowerCase(Locale.ROOT)); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); JavaOctetEditor.getInstance().fileTreePanel.refresh(searchedJar); + JTreeUtil.setTreeExpandedState(JavaOctetEditor.getInstance().fileTreePanel, true); } else { JavaOctetEditor.getInstance().fileTreePanel.refresh(jar); } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/ASMifierTablePanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/ASMifierTablePanel.java index 82a7d6e..66f284b 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/ASMifierTablePanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/ASMifierTablePanel.java @@ -17,7 +17,7 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab; import cn.enaium.joe.JavaOctetEditor; -import cn.enaium.joe.gui.panel.CodeArea; +import cn.enaium.joe.gui.panel.CodeAreaPanel; import cn.enaium.joe.util.ASyncUtil; import javassist.ClassPool; import javassist.CtClass; @@ -44,18 +44,7 @@ public class ASMifierTablePanel extends ClassNodeTabPanel { public ASMifierTablePanel(ClassNode classNode) { super(classNode); setLayout(new BorderLayout()); - CodeArea codeArea = new CodeArea(); - codeArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); - codeArea.setEditable(true); - StringWriter stringWriter = new StringWriter(); - ASyncUtil.execute(() -> { - classNode.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(stringWriter))); - }, () -> { - String trim = getMiddle(getMiddle(stringWriter.toString())).trim(); - codeArea.setText(trim.substring(0, trim.lastIndexOf("\n"))); - codeArea.setCaretPosition(0); - }); - add(new RTextScrollPane(codeArea) {{ + CodeAreaPanel codeAreaPanel = new CodeAreaPanel(){{ getTextArea().addKeyListener(new KeyAdapter() { boolean control = false; @@ -87,7 +76,7 @@ public void keyPressed(KeyEvent e) { classPool.importPackage("org.objectweb.asm.TypePath"); CtClass ctClass = classPool.makeClass(ASMifier.class.getSimpleName()); ctClass.addInterface(classPool.get("org.objectweb.asm.Opcodes")); - ctClass.addMethod(CtMethod.make("public static byte[] dump() throws Exception {" + codeArea.getText() + "return classWriter.toByteArray();}", ctClass)); + ctClass.addMethod(CtMethod.make("public static byte[] dump() throws Exception {" + getTextArea().getText() + "return classWriter.toByteArray();}", ctClass)); byte[] dumps = (byte[]) new Loader(classPool).loadClass(ASMifier.class.getSimpleName()).getMethod("dump").invoke(null); ClassNode newClassNode = new ClassNode(); new ClassReader(dumps).accept(newClassNode, ClassReader.EXPAND_FRAMES); @@ -107,7 +96,18 @@ public void keyReleased(KeyEvent e) { } } }); - }}); + }}; + codeAreaPanel.getTextArea().setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); + codeAreaPanel.getTextArea().setEditable(true); + StringWriter stringWriter = new StringWriter(); + ASyncUtil.execute(() -> { + classNode.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(stringWriter))); + }, () -> { + String trim = getMiddle(getMiddle(stringWriter.toString())).trim(); + codeAreaPanel.getTextArea().setText(trim.substring(0, trim.lastIndexOf("\n"))); + codeAreaPanel.getTextArea().setCaretPosition(0); + }); + add(codeAreaPanel); } public String getMiddle(String s) { diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/DecompileTabPanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/DecompileTabPanel.java index fe7e7dc..cfbb063 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/DecompileTabPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/DecompileTabPanel.java @@ -1,18 +1,12 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab; -import cn.enaium.joe.gui.panel.CodeArea; +import cn.enaium.joe.gui.panel.CodeAreaPanel; import cn.enaium.joe.util.ASyncUtil; -import org.benf.cfr.reader.PluginRunner; -import org.benf.cfr.reader.api.ClassFileSource; -import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import cn.enaium.joe.util.CfrUtil; import org.fife.ui.rtextarea.RTextScrollPane; -import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import java.awt.*; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; /** * @author Enaium @@ -21,39 +15,12 @@ public class DecompileTabPanel extends ClassNodeTabPanel { public DecompileTabPanel(ClassNode classNode) { super(classNode); setLayout(new BorderLayout()); - CodeArea codeArea = new CodeArea(); - codeArea.setSyntaxEditingStyle("text/java"); + CodeAreaPanel codeAreaPanel = new CodeAreaPanel(); + codeAreaPanel.getTextArea().setSyntaxEditingStyle("text/java"); ASyncUtil.execute(() -> { - ClassFileSource cfs = new ClassFileSource() { - @Override - public void informAnalysisRelativePathDetail(String a, String b) { - } - - @Override - public String getPossiblyRenamedPath(String path) { - return path; - } - - @Override - public Pair getClassFileContent(String path) throws IOException { - String name = path.substring(0, path.length() - 6); - if (name.equals(classNode.name)) { - ClassWriter classWriter = new ClassWriter(0); - classNode.accept(classWriter); - return Pair.make(classWriter.toByteArray(), name); - } - return null; - } - - @Override - public Collection addJar(String arg0) { - throw new RuntimeException(); - } - }; - - codeArea.setText(new PluginRunner(new HashMap<>(), cfs).getDecompilationFor(classNode.name)); + codeAreaPanel.getTextArea().setText(CfrUtil.getSource(classNode)); }); - codeArea.setCaretPosition(0); - add(new RTextScrollPane(codeArea)); + codeAreaPanel.getTextArea().setCaretPosition(0); + add(codeAreaPanel); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/TraceBytecodeTabPanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/TraceBytecodeTabPanel.java index 89ae1ff..e1300b3 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/TraceBytecodeTabPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/TraceBytecodeTabPanel.java @@ -1,6 +1,6 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab; -import cn.enaium.joe.gui.panel.CodeArea; +import cn.enaium.joe.gui.panel.CodeAreaPanel; import cn.enaium.joe.util.ASyncUtil; import org.fife.ui.rtextarea.RTextScrollPane; import org.objectweb.asm.tree.ClassNode; @@ -18,15 +18,15 @@ public class TraceBytecodeTabPanel extends ClassNodeTabPanel { public TraceBytecodeTabPanel(ClassNode classNode) { super(classNode); setLayout(new BorderLayout()); - CodeArea codeArea = new CodeArea(); - codeArea.setSyntaxEditingStyle("text/custom"); + CodeAreaPanel codeAreaPanel = new CodeAreaPanel(); + codeAreaPanel.getTextArea().setSyntaxEditingStyle("text/custom"); final StringWriter stringWriter = new StringWriter(); ASyncUtil.execute(() -> { classNode.accept(new TraceClassVisitor(new PrintWriter(stringWriter))); }, () -> { - codeArea.setText(new String(stringWriter.toString().getBytes(StandardCharsets.UTF_8))); - codeArea.setCaretPosition(0); + codeAreaPanel.getTextArea().setText(new String(stringWriter.toString().getBytes(StandardCharsets.UTF_8))); + codeAreaPanel.getTextArea().setCaretPosition(0); }); - add(new RTextScrollPane(codeArea)); + add(codeAreaPanel); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tree/FileTreePanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tree/FileTreePanel.java index 0664b59..cdcdeb7 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tree/FileTreePanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tree/FileTreePanel.java @@ -123,6 +123,8 @@ public void refresh(Jar jar) { } sort(model, classesRoot); + hasMap.clear(); + for (Map.Entry stringEntry : jar.resources.entrySet()) { String[] split = stringEntry.getKey().split("/"); DefaultTreeNode prev = null; diff --git a/src/main/java/cn/enaium/joe/gui/panel/menu/FileMenu.java b/src/main/java/cn/enaium/joe/gui/panel/menu/FileMenu.java index 6916f5d..dacc87b 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/menu/FileMenu.java +++ b/src/main/java/cn/enaium/joe/gui/panel/menu/FileMenu.java @@ -17,6 +17,7 @@ package cn.enaium.joe.gui.panel.menu; import cn.enaium.joe.gui.panel.menu.file.LoadMenuItem; +import cn.enaium.joe.gui.panel.menu.file.SaveAllSourceMenuItem; import cn.enaium.joe.gui.panel.menu.file.SaveMenuItem; import javax.swing.*; @@ -29,5 +30,7 @@ public FileMenu() { super("File"); add(new LoadMenuItem()); add(new SaveMenuItem()); + add(new JSeparator()); + add(new SaveAllSourceMenuItem()); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/menu/SearchMenu.java b/src/main/java/cn/enaium/joe/gui/panel/menu/SearchMenu.java new file mode 100644 index 0000000..ebba3a2 --- /dev/null +++ b/src/main/java/cn/enaium/joe/gui/panel/menu/SearchMenu.java @@ -0,0 +1,31 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.gui.panel.menu; + +import cn.enaium.joe.gui.panel.menu.search.LdcMenuItem; + +import javax.swing.*; + +/** + * @author Enaium + */ +public class SearchMenu extends JMenu { + public SearchMenu() { + super("Search"); + add(new LdcMenuItem()); + } +} diff --git a/src/main/java/cn/enaium/joe/gui/panel/menu/file/SaveAllSourceMenuItem.java b/src/main/java/cn/enaium/joe/gui/panel/menu/file/SaveAllSourceMenuItem.java new file mode 100644 index 0000000..4c0299a --- /dev/null +++ b/src/main/java/cn/enaium/joe/gui/panel/menu/file/SaveAllSourceMenuItem.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.gui.panel.menu.file; + +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.jar.Jar; +import cn.enaium.joe.util.ASyncUtil; +import cn.enaium.joe.util.CfrUtil; +import cn.enaium.joe.util.JFileChooserUtil; +import org.objectweb.asm.tree.ClassNode; + +import javax.swing.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * @author Enaium + */ +public class SaveAllSourceMenuItem extends JMenuItem { + public SaveAllSourceMenuItem() { + super("Save All Sources"); + addActionListener(e -> { + Jar jar = JavaOctetEditor.getInstance().jar; + if (jar == null) { + return; + } + + File show = JFileChooserUtil.show(JFileChooserUtil.Type.SAVE); + if (show != null) { + ASyncUtil.execute(() -> { + float loaded = 0; + float files = jar.classes.size() + jar.resources.size(); + try { + ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(show.toPath())); + + for (Map.Entry stringClassNodeEntry : jar.classes.entrySet()) { + String name = stringClassNodeEntry.getKey().substring(0, stringClassNodeEntry.getKey().lastIndexOf(".")) + ".java"; + name = "src/main/java/" + name; + zipOutputStream.putNextEntry(new ZipEntry(name)); + zipOutputStream.write(CfrUtil.getSource(stringClassNodeEntry.getValue()).getBytes(StandardCharsets.UTF_8)); + JavaOctetEditor.getInstance().bottomPanel.setProcess((int) ((loaded++ / files) * 100f)); + } + + for (Map.Entry stringEntry : jar.resources.entrySet()) { + String name = stringEntry.getKey(); + name = "src/main/resources/" + name; + zipOutputStream.putNextEntry(new JarEntry(name)); + zipOutputStream.write(stringEntry.getValue()); + JavaOctetEditor.getInstance().bottomPanel.setProcess((int) ((loaded++ / files) * 100f)); + } + zipOutputStream.closeEntry(); + zipOutputStream.close(); + JavaOctetEditor.getInstance().bottomPanel.setProcess(0); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/cn/enaium/joe/gui/panel/menu/search/LdcMenuItem.java b/src/main/java/cn/enaium/joe/gui/panel/menu/search/LdcMenuItem.java new file mode 100644 index 0000000..1d4a0ca --- /dev/null +++ b/src/main/java/cn/enaium/joe/gui/panel/menu/search/LdcMenuItem.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.gui.panel.menu.search; + +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.dialog.search.SearchLdcDialog; +import cn.enaium.joe.jar.Jar; + +import javax.swing.*; + +/** + * @author Enaium + */ +public class LdcMenuItem extends JMenuItem { + public LdcMenuItem() { + super("LDC"); + addActionListener(e -> { + Jar jar = JavaOctetEditor.getInstance().jar; + if (jar == null) { + return; + } + + new SearchLdcDialog().setVisible(true); + }); + } +} diff --git a/src/main/java/cn/enaium/joe/gui/panel/search/Result.java b/src/main/java/cn/enaium/joe/gui/panel/search/Result.java new file mode 100644 index 0000000..d83d74c --- /dev/null +++ b/src/main/java/cn/enaium/joe/gui/panel/search/Result.java @@ -0,0 +1,104 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.gui.panel.search; + +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.gui.panel.file.tree.FileTreePanel; +import cn.enaium.joe.gui.panel.file.tree.node.*; +import cn.enaium.joe.util.ASyncUtil; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import javax.swing.*; +import javax.swing.table.DefaultTableModel; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.nio.charset.StandardCharsets; + +/** + * @author Enaium + */ +public class Result extends JPanel { + + private final JList list; + + public Result() { + super(new BorderLayout()); + list = new JList<>(new DefaultListModel<>()); + add(new JScrollPane(list), BorderLayout.CENTER); + + JPopupMenu jPopupMenu = new JPopupMenu(); + JMenuItem jMenuItem = new JMenuItem("Jump to Node"); + jMenuItem.addActionListener(e -> { + if (list.getSelectedValue() != null) { + ASyncUtil.execute(() -> { + SwingUtilities.invokeLater(() -> { + FileTreePanel fileTreePanel = JavaOctetEditor.getInstance().fileTreePanel; + DefaultTreeModel model = (DefaultTreeModel) fileTreePanel.getModel(); + if (selectEntry(fileTreePanel, list.getSelectedValue().getClassNode(), model, (DefaultTreeNode) model.getRoot())) { + fileTreePanel.repaint(); + } + }); + }); + } + }); + jPopupMenu.add(jMenuItem); + + list.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (SwingUtilities.isRightMouseButton(e)) { + if (list.getSelectedValue() != null) { + jPopupMenu.show(list, e.getX(), e.getY()); + } + } + } + }); + } + + public boolean selectEntry(JTree jTree, ClassNode classNode, DefaultTreeModel defaultTreeModel, DefaultTreeNode defaultTreeNode) { + for (int i = 0; i < defaultTreeModel.getChildCount(defaultTreeNode); i++) { + DefaultTreeNode child = ((DefaultTreeNode) defaultTreeModel.getChild(defaultTreeNode, i)); + if (child instanceof PackageTreeNode) { + PackageTreeNode packageTreeNode = (PackageTreeNode) child; + if (packageTreeNode instanceof ClassTreeNode) { + ClassTreeNode classTreeNode = (ClassTreeNode) packageTreeNode; + if (classNode.equals(classTreeNode.classNode)) { + TreePath treePath = new TreePath(defaultTreeModel.getPathToRoot(classTreeNode)); + jTree.setSelectionPath(treePath); + jTree.scrollPathToVisible(treePath); + return true; + } + } + } + if (!defaultTreeNode.isLeaf()) { + if (selectEntry(jTree, classNode, defaultTreeModel, child)) { + return true; + } + } + } + return false; + } + + public JList getList() { + return list; + } +} diff --git a/src/main/java/cn/enaium/joe/gui/panel/search/ResultNode.java b/src/main/java/cn/enaium/joe/gui/panel/search/ResultNode.java new file mode 100644 index 0000000..9d183e6 --- /dev/null +++ b/src/main/java/cn/enaium/joe/gui/panel/search/ResultNode.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.gui.panel.search; + +import org.objectweb.asm.tree.ClassNode; + +/** + * @author Enaium + */ +public class ResultNode { + private final ClassNode classNode; + private final String result; + + public ResultNode(ClassNode classNode, String result) { + this.classNode = classNode; + this.result = result; + } + + public ClassNode getClassNode() { + return classNode; + } + + public String getResult() { + return result; + } + + @Override + public String toString() { + return result; + } +} diff --git a/src/main/java/cn/enaium/joe/jar/Jar.java b/src/main/java/cn/enaium/joe/jar/Jar.java index f7ce447..eaad5e9 100644 --- a/src/main/java/cn/enaium/joe/jar/Jar.java +++ b/src/main/java/cn/enaium/joe/jar/Jar.java @@ -53,7 +53,11 @@ public void load(File file) { if (jarEntry.getName().endsWith(".class")) { ClassReader classReader = new ClassReader(jarFile.getInputStream(new JarEntry(jarEntry.getName()))); ClassNode classNode = new ClassNode(); - classReader.accept(classNode, ClassReader.EXPAND_FRAMES); + try { + classReader.accept(classNode, ClassReader.EXPAND_FRAMES); + } catch (Throwable throwable) { + classReader.accept(classNode, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + } classes.put(jarEntry.getName(), classNode); } else if (!jarEntry.isDirectory()) { ByteArrayOutputStream output = new ByteArrayOutputStream(); diff --git a/src/main/java/cn/enaium/joe/util/CfrUtil.java b/src/main/java/cn/enaium/joe/util/CfrUtil.java new file mode 100644 index 0000000..f73e0b5 --- /dev/null +++ b/src/main/java/cn/enaium/joe/util/CfrUtil.java @@ -0,0 +1,62 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.util; + +import org.benf.cfr.reader.PluginRunner; +import org.objectweb.asm.tree.ClassNode; +import org.benf.cfr.reader.api.ClassFileSource; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.objectweb.asm.ClassWriter; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; + +/** + * @author Enaium + */ +public class CfrUtil { + public static String getSource(ClassNode classNode) { + ClassFileSource cfs = new ClassFileSource() { + @Override + public void informAnalysisRelativePathDetail(String a, String b) { + } + + @Override + public String getPossiblyRenamedPath(String path) { + return path; + } + + @Override + public Pair getClassFileContent(String path) throws IOException { + String name = path.substring(0, path.length() - 6); + if (name.equals(classNode.name)) { + ClassWriter classWriter = new ClassWriter(0); + classNode.accept(classWriter); + return Pair.make(classWriter.toByteArray(), name); + } + return null; + } + + @Override + public Collection addJar(String arg0) { + throw new RuntimeException(); + } + }; + return new PluginRunner(new HashMap<>(), cfs).getDecompilationFor(classNode.name); + } +} diff --git a/src/main/java/cn/enaium/joe/util/JTreeUtil.java b/src/main/java/cn/enaium/joe/util/JTreeUtil.java new file mode 100644 index 0000000..740e47a --- /dev/null +++ b/src/main/java/cn/enaium/joe/util/JTreeUtil.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.util; + +import cn.enaium.joe.gui.panel.file.tree.node.ClassTreeNode; +import cn.enaium.joe.gui.panel.file.tree.node.MethodTreeNode; +import cn.enaium.joe.gui.panel.file.tree.node.PackageTreeNode; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; + +public class JTreeUtil { + public static void setTreeExpandedState(JTree tree, boolean expanded) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot(); + setNodeExpandedState(tree, node, expanded); + } + + private static void setNodeExpandedState(JTree tree, DefaultMutableTreeNode node, boolean expanded) { + Enumeration children = node.children(); + while (children.hasMoreElements()) { + setNodeExpandedState(tree, (DefaultMutableTreeNode) children.nextElement(), expanded); + } + + if (!expanded && node.isRoot()) { + return; + } + + if (node instanceof PackageTreeNode) { + PackageTreeNode packageTreeNode = (PackageTreeNode) node; + if (packageTreeNode instanceof ClassTreeNode) { + return; + } + } + + TreePath path = new TreePath(node.getPath()); + if (expanded) { + tree.expandPath(path); + } else { + tree.collapsePath(path); + } + } +} \ No newline at end of file