From 15c581ceb6faeb6fad5eb94f8ed9dd1c437e662b Mon Sep 17 00:00:00 2001 From: Outfluencer Date: Tue, 9 Mar 2021 00:55:38 +0100 Subject: [PATCH] Added some decompilers and decompile and disassemble async --- src/me/ByteEdit/boxes/CompilationBox.java | 63 +++--- .../ByteEdit/boxes/GlobalSearchOpenBox.java | 24 ++- src/me/ByteEdit/boxes/RenameBox.java | 42 ++-- src/me/ByteEdit/boxes/TypeOpenBox.java | 5 +- src/me/ByteEdit/decompiler/CFRDecompiler.java | 165 ++++++++++++++++ .../ByteEdit/decompiler/EnumDecompiler.java | 15 ++ .../decompiler/FernflowerDecompiler.java | 132 +++++++++++++ src/me/ByteEdit/decompiler/JDDecompiler.java | 126 ++++++++++++ .../decompiler/ProcyonDecompiler.java | 71 +++++++ .../decompiler/SingleThreadedExecutor.java | 27 +++ src/me/ByteEdit/edit/Disassembler.java | 34 +++- src/me/ByteEdit/edit/TypeOpenBox.java | 5 +- src/me/ByteEdit/main/Main.java | 179 ++++++++++++------ .../rsyntaxtextarea/DefaultTokenFactory.java | 1 + .../ui/rsyntaxtextarea/TokenMakerBase.java | 1 + .../rsyntaxtextarea/modes/JavaTokenMaker.java | 4 +- 16 files changed, 775 insertions(+), 119 deletions(-) create mode 100644 src/me/ByteEdit/decompiler/CFRDecompiler.java create mode 100644 src/me/ByteEdit/decompiler/EnumDecompiler.java create mode 100644 src/me/ByteEdit/decompiler/FernflowerDecompiler.java create mode 100644 src/me/ByteEdit/decompiler/JDDecompiler.java create mode 100644 src/me/ByteEdit/decompiler/ProcyonDecompiler.java create mode 100644 src/me/ByteEdit/decompiler/SingleThreadedExecutor.java diff --git a/src/me/ByteEdit/boxes/CompilationBox.java b/src/me/ByteEdit/boxes/CompilationBox.java index 491e074..c943f57 100644 --- a/src/me/ByteEdit/boxes/CompilationBox.java +++ b/src/me/ByteEdit/boxes/CompilationBox.java @@ -15,6 +15,7 @@ import java.io.StringWriter; import java.net.URI; import java.nio.file.Files; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import javax.swing.JComponent; import javax.swing.JFrame; @@ -26,6 +27,7 @@ import javax.tools.JavaCompiler; import javax.tools.SimpleJavaFileObject; import javax.tools.ToolProvider; +import me.ByteEdit.decompiler.SingleThreadedExecutor; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rtextarea.RTextScrollPane; @@ -105,37 +107,48 @@ public class CompilationBox extends JFrame { null, Arrays.asList(new JavaSourceFromString("Compiled", source))).call(); String res = out.toString(); if (res.isEmpty()) { - File clazz = new File(tmpFolder, "Compiled.class"); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream is = new FileInputStream(clazz); - byte[] tmp = new byte[1024]; - int r; - while ((r = is.read(tmp)) > 0) { - baos.write(tmp, 0, r); - } - is.close(); - ClassReader read = new ClassReader(baos.toByteArray()); - ClassNode node = new ClassNode(); - read.accept(node, 0); - String dis = Disassembler.disassemble(node); - File[] files = tmpFolder.listFiles(); - if (files != null) - for (File f : files) { - if (f.getName().equals("Compiled.class")) - continue; - baos = new ByteArrayOutputStream(); - is = new FileInputStream(f); + + + SingleThreadedExecutor.execute( () -> { + try { + File clazz = new File(tmpFolder, "Compiled.class"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = new FileInputStream(clazz); + byte[] tmp = new byte[1024]; + int r; while ((r = is.read(tmp)) > 0) { baos.write(tmp, 0, r); } is.close(); - read = new ClassReader(baos.toByteArray()); - node = new ClassNode(); + + + ClassReader read = new ClassReader(baos.toByteArray()); + ClassNode node = new ClassNode(); read.accept(node, 0); - dis += "\n" + Disassembler.disassemble(node); + String dis = Disassembler.disassemble(node); + File[] files = tmpFolder.listFiles(); + if (files != null) + for (File f : files) { + if (f.getName().equals("Compiled.class")) + continue; + baos = new ByteArrayOutputStream(); + is = new FileInputStream(f); + while ((r = is.read(tmp)) > 0) { + baos.write(tmp, 0, r); + } + is.close(); + read = new ClassReader(baos.toByteArray()); + node = new ClassNode(); + read.accept(node, 0); + dis += "\n" + Disassembler.disassemble(node); + } + compSuccess.textArea.setText(dis); + compSuccess.setVisible(true); + }catch (IOException e2){ + e2.printStackTrace(); } - compSuccess.textArea.setText(dis); - compSuccess.setVisible(true); + }); + } else { JOptionPane.showMessageDialog(CompilationBox.this, res, "Error!", JOptionPane.ERROR_MESSAGE); } diff --git a/src/me/ByteEdit/boxes/GlobalSearchOpenBox.java b/src/me/ByteEdit/boxes/GlobalSearchOpenBox.java index 4bc9945..e7dc129 100644 --- a/src/me/ByteEdit/boxes/GlobalSearchOpenBox.java +++ b/src/me/ByteEdit/boxes/GlobalSearchOpenBox.java @@ -1,6 +1,7 @@ package me.ByteEdit.boxes; import java.awt.Toolkit; +import java.util.concurrent.TimeUnit; import javax.swing.DefaultListModel; import javax.swing.JFrame; @@ -13,6 +14,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.text.BadLocationException; +import me.ByteEdit.decompiler.SingleThreadedExecutor; import org.objectweb.asm.tree.MethodNode; import me.ByteEdit.main.Main; @@ -38,17 +40,21 @@ public class GlobalSearchOpenBox extends JFrame { list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { - Info val = list.getSelectedValue(); - if (val != null && !val.owner.equals(Main.currentNodeName)) { - int lineFound = Main.selectFileWithSearch(val.nodePath, val.mn); - if (lineFound != -1) { - try { - Main.txtByteEditView.setCaretPosition(Main.txtByteEditView.getLineStartOffset(lineFound)); - } catch (BadLocationException e1) { - e1.printStackTrace(); + + SingleThreadedExecutor.execute( () -> { + Info val = list.getSelectedValue(); + if (val != null && !val.owner.equals(Main.currentNodeName)) { + int lineFound = Main.selectFileWithSearch(val.nodePath, val.mn); + if (lineFound != -1) { + try { + Main.txtByteEditView.setCaretPosition(Main.txtByteEditView.getLineStartOffset(lineFound)); + } catch (BadLocationException e1) { + //e1.printStackTrace(); + } } } - } + }); + } }); diff --git a/src/me/ByteEdit/boxes/RenameBox.java b/src/me/ByteEdit/boxes/RenameBox.java index 1e9755c..b8e4e18 100644 --- a/src/me/ByteEdit/boxes/RenameBox.java +++ b/src/me/ByteEdit/boxes/RenameBox.java @@ -4,6 +4,7 @@ import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.LinkedList; +import java.util.concurrent.TimeUnit; import javax.swing.JButton; import javax.swing.JFrame; @@ -13,6 +14,7 @@ import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.border.EmptyBorder; +import me.ByteEdit.decompiler.SingleThreadedExecutor; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; @@ -210,27 +212,31 @@ public class RenameBox extends JFrame { return; } } - try { // refresh - ClassNode classNode = Main.classNodes.get(Main.currentNodeName); - int prev = Main.txtByteEditView.getCaretPosition(); - String dis = Disassembler.disassemble(classNode); - String substr = Main.currentNodeName.substring(0, Main.currentNodeName.length() - 6); - for (String key : Main.classNodes.keySet()) { - if (key.contains("$")) { - String[] split = key.split("\\$"); - if (split[0].equals(substr)) { - dis += "\n" + Disassembler.disassemble(Main.classNodes.get(key)); + + SingleThreadedExecutor.execute( () -> { + try { // refresh + ClassNode classNode = Main.classNodes.get(Main.currentNodeName); + int prev = Main.txtByteEditView.getCaretPosition(); + String dis = Disassembler.disassemble(classNode); + String substr = Main.currentNodeName.substring(0, Main.currentNodeName.length() - 6); + for (String key : Main.classNodes.keySet()) { + if (key.contains("$")) { + String[] split = key.split("\\$"); + if (split[0].equals(substr)) { + dis += "\n" + Disassembler.disassemble(Main.classNodes.get(key)); + } } } + Main.txtByteEditView.setText(dis); + Main.txtByteEditView.setCaretPosition(prev); + } catch (Exception e2) { + e2.printStackTrace(); } - Main.txtByteEditView.setText(dis); - Main.txtByteEditView.setCaretPosition(prev); - } catch (Exception e2) { - e2.printStackTrace(); - } - name = newName; - desc = newDesc; - RenameBox.this.setVisible(false); + name = newName; + desc = newDesc; + RenameBox.this.setVisible(false); + }); + } }); btnRename.setBounds(345, 237, 89, 23); diff --git a/src/me/ByteEdit/boxes/TypeOpenBox.java b/src/me/ByteEdit/boxes/TypeOpenBox.java index dc216ad..2506786 100644 --- a/src/me/ByteEdit/boxes/TypeOpenBox.java +++ b/src/me/ByteEdit/boxes/TypeOpenBox.java @@ -3,6 +3,7 @@ package me.ByteEdit.boxes; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.concurrent.TimeUnit; import javax.swing.DefaultListModel; import javax.swing.JFrame; @@ -17,6 +18,7 @@ import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import me.ByteEdit.decompiler.SingleThreadedExecutor; import me.ByteEdit.main.Main; import me.ByteEdit.utils.UnicodeUtils; @@ -43,7 +45,8 @@ public class TypeOpenBox extends JFrame { public void valueChanged(ListSelectionEvent e) { String val = list.getSelectedValue(); if (val != null && !val.equals(Main.currentNodeName)) { - Main.selectFile(val); + + SingleThreadedExecutor.execute( () -> Main.selectFile(val)); } } }); diff --git a/src/me/ByteEdit/decompiler/CFRDecompiler.java b/src/me/ByteEdit/decompiler/CFRDecompiler.java new file mode 100644 index 0000000..f8e54bb --- /dev/null +++ b/src/me/ByteEdit/decompiler/CFRDecompiler.java @@ -0,0 +1,165 @@ +package me.ByteEdit.decompiler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.benf.cfr.reader.PluginRunner; +import org.benf.cfr.reader.api.ClassFileSource; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.entities.Method; +import org.benf.cfr.reader.entities.constantpool.ConstantPool; +import org.benf.cfr.reader.state.ClassFileSourceImpl; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.util.bytestream.BaseByteData; +import org.benf.cfr.reader.util.getopt.OptionsImpl; +import org.benf.cfr.reader.util.output.ToStringDumper; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +@SuppressWarnings("deprecation") +public class CFRDecompiler { + + public static final HashMap options = new HashMap<>(); + + static { + options.put("aexagg", "false"); + options.put("allowcorrecting", "true"); + options.put("arrayiter", "true"); + options.put("caseinsensitivefs", "false"); + options.put("clobber", "false"); + options.put("collectioniter", "true"); + options.put("commentmonitors", "false"); + options.put("decodeenumswitch", "true"); + options.put("decodefinally", "true"); + options.put("decodelambdas", "true"); + options.put("decodestringswitch", "true"); + options.put("dumpclasspath", "false"); + options.put("eclipse", "true"); + options.put("elidescala", "false"); + options.put("forcecondpropagate", "false"); + options.put("forceexceptionprune", "false"); + options.put("forcereturningifs", "false"); + options.put("forcetopsort", "false"); + options.put("forcetopsortaggress", "false"); + options.put("forloopaggcapture", "false"); + options.put("hidebridgemethods", "false"); + options.put("hidelangimports", "true"); + options.put("hidelongstrings", "false"); + options.put("hideutf", "true"); + options.put("innerclasses", "true"); + options.put("j14classobj", "false"); + options.put("labelledblocks", "true"); + options.put("lenient", "false"); + options.put("liftconstructorinit", "true"); + options.put("override", "true"); + options.put("pullcodecase", "false"); + options.put("recover", "true"); + options.put("recovertypeclash", "false"); + options.put("recovertypehints", "false"); + options.put("relinkconststring", "true"); + options.put("removebadgenerics", "true"); + options.put("removeboilerplate", "true"); + options.put("removedeadmethods", "true"); + options.put("removeinnerclasssynthetics", "true"); + options.put("rename", "false"); + options.put("renamedupmembers", "false"); + options.put("renameenumidents", "false"); + options.put("renameillegalidents", "false"); + options.put("showinferrable", "false"); + options.put("silent", "false"); + options.put("stringbuffer", "false"); + options.put("stringbuilder", "true"); + options.put("sugarasserts", "true"); + options.put("sugarboxing", "true"); + options.put("sugarenums", "true"); + options.put("tidymonitors", "true"); + options.put("usenametable", "true"); + } + + public static String decompile(ClassNode cn, byte[] b, MethodNode mn) { + try { + HashMap ops = options; + 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(cn.name)) { + return Pair.make(b, name); + } + return null; // cfr loads unnecessary classes + } + + @Override + public Collection addJar(String arg0) { + throw new RuntimeException(); + } + }; + PluginRunner runner = new PluginRunner(ops, cfs); + if (mn != null) { + BaseByteData data = new BaseByteData(b); + ClassFile cf = new ClassFile(data, "", initDCState(ops, cfs)); + Field cpf = Method.class.getDeclaredField("cp"); + Field descI = Method.class.getDeclaredField("descriptorIndex"); + descI.setAccessible(true); + cpf.setAccessible(true); + for (Method m : cf.getMethodByName(mn.name)) { + ConstantPool cp = (ConstantPool) cpf.get(m); + if (cp.getUTF8Entry(descI.getInt(m)).getValue().equals(mn.desc)) { + ToStringDumper tsd = new ToStringDumper(); + m.dump(tsd, true); + return tsd.toString(); + } + } + } + String decompilation = runner.getDecompilationFor(cn.name); + System.gc(); // cfr has a performance bug + return decompilation.substring(37); // small hack to remove watermark + } catch (Exception e) { + e.printStackTrace(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + } + + private static DCCommonState initDCState(Map optionsMap, ClassFileSource classFileSource) { + OptionsImpl options = new OptionsImpl(optionsMap); + if (classFileSource == null) + classFileSource = new ClassFileSourceImpl(options); + DCCommonState dcCommonState = new DCCommonState(options, classFileSource); + return dcCommonState; + } + + protected Pair getSystemClass(String name, String path) throws IOException { + InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(path); + if (is != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int n; + while ((n = is.read(buffer)) > 0) { + baos.write(buffer, 0, n); + } + return Pair.make(baos.toByteArray(), name); + } + return null; + } +} diff --git a/src/me/ByteEdit/decompiler/EnumDecompiler.java b/src/me/ByteEdit/decompiler/EnumDecompiler.java new file mode 100644 index 0000000..359d9c9 --- /dev/null +++ b/src/me/ByteEdit/decompiler/EnumDecompiler.java @@ -0,0 +1,15 @@ +package me.ByteEdit.decompiler; + +public enum EnumDecompiler { + BYTEEDIT("ByteEdit"), PROCYON("Procyon"), FERNFLOWER("FernFlower"), JD_GUI("JD-GUI"), CFR("CFR"); + + final String name; + + EnumDecompiler(String name){ + this.name = name; + } + @Override + public String toString() { + return name; + } +} diff --git a/src/me/ByteEdit/decompiler/FernflowerDecompiler.java b/src/me/ByteEdit/decompiler/FernflowerDecompiler.java new file mode 100644 index 0000000..ec04525 --- /dev/null +++ b/src/me/ByteEdit/decompiler/FernflowerDecompiler.java @@ -0,0 +1,132 @@ +package me.ByteEdit.decompiler; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.jar.Manifest; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.struct.ContextUnit; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.lazy.LazyLoader; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +public class FernflowerDecompiler implements IBytecodeProvider, IResultSaver { + + private byte[] bytes; + private String returned; + + public static final HashMap options = new HashMap<>(); + + static { + options.put("rbr", true); + options.put("rsy", false); + options.put("din", true); + options.put("dc4", true); + options.put("das", true); + options.put("hes", true); + options.put("hdc", true); + options.put("dgs", false); + options.put("ner", true); + options.put("den", true); + options.put("rgn", true); + options.put("lit", false); + options.put("asc", true); + options.put("bto", true); + options.put("nns", false); + options.put("uto", true); + options.put("udv", true); + options.put("rer", true); + options.put("fdi", true); + options.put("ren", false); + options.put("inn", true); + options.put("lac", false); + } + + public String decompile(ClassNode cn, byte[] b, MethodNode mn) { + try { + // TODO decompile method only + this.bytes = b; + HashMap map = new HashMap<>(); + for (String key : options.keySet()) { + map.put(key, options.get(key) ? "1" : "0"); + } + Fernflower f = new Fernflower(this, this, map, new IFernflowerLogger() { + @Override + public void writeMessage(String message, Throwable t) { + } + + @Override + public void writeMessage(String message, Severity severity) { + } + }); + StructContext sc = f.getStructContext(); + StructClass cl = new StructClass(b, true, sc.getLoader()); + sc.getClasses().put(cn.name, cl); + // instead of loading a file use custom bridge, created a few getters + String fakePath = new File("none.class").getAbsolutePath(); + ContextUnit unit = new ContextUnit(ContextUnit.TYPE_FOLDER, null, fakePath, true, sc.getSaver(), + sc.getDecompiledData()); + sc.getUnits().put(fakePath, unit); + unit.addClass(cl, "none.class"); + sc.getLoader().addClassLink(cn.name, new LazyLoader.Link(LazyLoader.Link.CLASS, fakePath, null)); + + f.decompileContext(); + return returned; + } catch (Exception e) { + e.printStackTrace(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + } + + @Override + public byte[] getBytecode(String externalPath, String internalPath) throws IOException { + return bytes; + } + + // we can ignore most of those methods because we do not want to save the output + // as a file + @Override + public void saveFolder(String path) { + } + + @Override + public void copyFile(String source, String path, String entryName) { + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) { + this.returned = content; + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + } + + @Override + public void saveDirEntry(String path, String archiveName, String entryName) { + } + + @Override + public void copyEntry(String source, String path, String archiveName, String entry) { + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, + String content) { + } + + @Override + public void closeArchive(String path, String archiveName) { + } +} diff --git a/src/me/ByteEdit/decompiler/JDDecompiler.java b/src/me/ByteEdit/decompiler/JDDecompiler.java new file mode 100644 index 0000000..51fe4df --- /dev/null +++ b/src/me/ByteEdit/decompiler/JDDecompiler.java @@ -0,0 +1,126 @@ +package me.ByteEdit.decompiler; + +import me.ByteEdit.main.Main; +import org.jd.core.v1.ClassFileToJavaSourceDecompiler; +import org.jd.core.v1.api.loader.Loader; +import org.jd.core.v1.api.loader.LoaderException; +import org.jd.core.v1.api.printer.Printer; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + + +public class JDDecompiler implements Loader, Printer { + + public String decompile(ClassNode cl) { + ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler(); + try { + decompiler.decompile(this, this, cl.name); + } catch (Throwable e) { + e.printStackTrace(); + String str = e.getClass().getName() + " - " + e.getMessage() + System.lineSeparator(); + for(int i = 0; i < e.getStackTrace().length; i++) { + str += e.getStackTrace()[i].toString() + System.lineSeparator(); + } + return str; + } + return toString(); + } + + @Override + public boolean canLoad(String internalName) { + return Main.classNodes.containsKey(internalName + ".class"); + } + + @Override + public byte[] load(String internalName) throws LoaderException { + ClassNode cl = Main.classNodes.get(internalName + ".class"); + ClassWriter cw = new ClassWriter(0); + cl.accept(cw); + return cw.toByteArray(); + } + + protected static final String TAB = " "; + protected static final String NEWLINE = "\n"; + + protected int indentationCount = 0; + protected StringBuilder sb = new StringBuilder(); + + @Override + public String toString() { + return sb.toString(); + } + + @Override + public void start(int maxLineNumber, int majorVersion, int minorVersion) { + } + + @Override + public void end() { + } + + @Override + public void printText(String text) { + sb.append(text); + } + + @Override + public void printNumericConstant(String constant) { + sb.append(constant); + } + + @Override + public void printStringConstant(String constant, String ownerInternalName) { + sb.append(constant); + } + + @Override + public void printKeyword(String keyword) { + sb.append(keyword); + } + + @Override + public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { + sb.append(name); + } + + @Override + public void printReference(int type, String internalTypeName, String name, String descriptor, + String ownerInternalName) { + sb.append(name); + } + + @Override + public void indent() { + this.indentationCount++; + } + + @Override + public void unindent() { + this.indentationCount--; + } + + @Override + public void startLine(int lineNumber) { + for (int i = 0; i < indentationCount; i++) + sb.append(TAB); + } + + @Override + public void endLine() { + sb.append(NEWLINE); + } + + @Override + public void extraLine(int count) { + while (count-- > 0) + sb.append(NEWLINE); + } + + @Override + public void startMarker(int type) { + } + + @Override + public void endMarker(int type) { + } +} diff --git a/src/me/ByteEdit/decompiler/ProcyonDecompiler.java b/src/me/ByteEdit/decompiler/ProcyonDecompiler.java new file mode 100644 index 0000000..a2bd8b1 --- /dev/null +++ b/src/me/ByteEdit/decompiler/ProcyonDecompiler.java @@ -0,0 +1,71 @@ +package me.ByteEdit.decompiler; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Field; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import com.strobel.assembler.InputTypeLoader; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.DecompilationOptions; +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.PlainTextOutput; + +public class ProcyonDecompiler { + + public static String decompile(ClassNode cn, byte[] b, MethodNode mn) { + try { + // TODO decompile method only + DecompilerSettings settings = new DecompilerSettings(); + try { + for (Field f : settings.getClass().getDeclaredFields()) { + if (f.getType() == boolean.class) { + f.setAccessible(true); + f.setBoolean(settings, true); + } + } + } catch (Throwable t) { + t.printStackTrace(); + } + settings.setShowSyntheticMembers(true); + MetadataSystem metadataSystem = new MetadataSystem(new ITypeLoader() { + private InputTypeLoader backLoader = new InputTypeLoader(); + + @Override + public boolean tryLoadType(String s, Buffer buffer) { + if (s.equals(cn.name)) { + buffer.putByteArray(b, 0, b.length); + buffer.position(0); + return true; + } else { + return backLoader.tryLoadType(s, buffer); + } + } + }); + TypeReference type = metadataSystem.lookupType(cn.name); + DecompilationOptions decompilationOptions = new DecompilationOptions(); + decompilationOptions.setSettings(DecompilerSettings.javaDefaults()); + decompilationOptions.setFullDecompilation(true); + TypeDefinition resolvedType = null; + if (type == null || ((resolvedType = type.resolve()) == null)) { + return "error"; + } + StringWriter stringwriter = new StringWriter(); + settings.getLanguage().decompileType(resolvedType, new PlainTextOutput(stringwriter), decompilationOptions); + String decompiledSource = stringwriter.toString(); + return decompiledSource; + } catch (Exception e) { + e.printStackTrace(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + return sw.toString(); + } + } +} diff --git a/src/me/ByteEdit/decompiler/SingleThreadedExecutor.java b/src/me/ByteEdit/decompiler/SingleThreadedExecutor.java new file mode 100644 index 0000000..6c34383 --- /dev/null +++ b/src/me/ByteEdit/decompiler/SingleThreadedExecutor.java @@ -0,0 +1,27 @@ +package me.ByteEdit.decompiler; + +import java.util.concurrent.LinkedBlockingQueue; + +public class SingleThreadedExecutor { + + + private static LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + + private static final Thread thread = new Thread(() -> { + while(true){ + try { + queue.take().run(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + }, "Single"); + static { + thread.start(); + } + + + public static void execute(Runnable r){ + queue.add(r); + } +} diff --git a/src/me/ByteEdit/edit/Disassembler.java b/src/me/ByteEdit/edit/Disassembler.java index 6f4317d..249f638 100644 --- a/src/me/ByteEdit/edit/Disassembler.java +++ b/src/me/ByteEdit/edit/Disassembler.java @@ -29,10 +29,12 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicInteger; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; +import com.strobel.decompiler.Decompiler; +import me.ByteEdit.decompiler.CFRDecompiler; +import me.ByteEdit.decompiler.FernflowerDecompiler; +import me.ByteEdit.decompiler.JDDecompiler; +import me.ByteEdit.decompiler.ProcyonDecompiler; +import org.objectweb.asm.*; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; @@ -98,10 +100,34 @@ public class Disassembler { } public static DisassembleTuple disassemble(ClassNode classNode, Object nodeToFind) { + if (classNode == null) { return new DisassembleTuple("ClassNode is null! This is not a valid java class file!"); } + + + + try { + System.out.println(Main.decompiler); + ClassWriter classWriter = new ClassWriter(0); + switch (Main.decompiler){ + case PROCYON: + classNode.accept(classWriter); + return new DisassembleTuple(ProcyonDecompiler.decompile(classNode, classWriter.toByteArray(), null)); + case FERNFLOWER: + classNode.accept(classWriter); + return new DisassembleTuple(new FernflowerDecompiler().decompile(classNode, classWriter.toByteArray(), null)); + case JD_GUI: + classNode.accept(classWriter); + return new DisassembleTuple(new JDDecompiler().decompile(classNode)); + case CFR: + classNode.accept(classWriter); + return new DisassembleTuple(CFRDecompiler.decompile(classNode, classWriter.toByteArray(), null)); + default: + break; + } + HugeStrings hs = new HugeStrings(); AtomicInteger lineFound = new AtomicInteger(-1); StringContext ctx = new StringContext(18); diff --git a/src/me/ByteEdit/edit/TypeOpenBox.java b/src/me/ByteEdit/edit/TypeOpenBox.java index 7cce359..83c735d 100644 --- a/src/me/ByteEdit/edit/TypeOpenBox.java +++ b/src/me/ByteEdit/edit/TypeOpenBox.java @@ -3,6 +3,7 @@ package me.ByteEdit.edit; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.concurrent.TimeUnit; import javax.swing.DefaultListModel; import javax.swing.JFrame; @@ -18,6 +19,7 @@ import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import me.ByteEdit.decompiler.SingleThreadedExecutor; import me.ByteEdit.main.Main; public class TypeOpenBox extends JFrame { @@ -51,7 +53,8 @@ public class TypeOpenBox extends JFrame { public void valueChanged(ListSelectionEvent e) { String val = list.getSelectedValue(); if (val != null && !val.equals(Main.currentNodeName)) { - Main.selectFile(val); + + SingleThreadedExecutor.execute( () -> Main.selectFile(val)); } } }); diff --git a/src/me/ByteEdit/main/Main.java b/src/me/ByteEdit/main/Main.java index 93ff107..2e2bb29 100644 --- a/src/me/ByteEdit/main/Main.java +++ b/src/me/ByteEdit/main/Main.java @@ -1,11 +1,6 @@ package me.ByteEdit.main; -import java.awt.Component; -import java.awt.EventQueue; -import java.awt.Font; -import java.awt.GridLayout; -import java.awt.HeadlessException; -import java.awt.Toolkit; +import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.dnd.DropTarget; @@ -13,11 +8,7 @@ import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -25,14 +16,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.HashMap; +import java.util.*; import java.util.List; import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -40,22 +30,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import javax.swing.ButtonGroup; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JRadioButtonMenuItem; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTree; -import javax.swing.KeyStroke; +import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; @@ -69,6 +44,8 @@ import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; +import me.ByteEdit.decompiler.EnumDecompiler; +import me.ByteEdit.decompiler.SingleThreadedExecutor; import org.fife.ui.autocomplete.AutoCompletion; import org.fife.ui.autocomplete.BasicCompletion; import org.fife.ui.autocomplete.CompletionProvider; @@ -104,6 +81,8 @@ import me.ByteEdit.utils.UnicodeUtils; public class Main extends JFrame { + + public static Main INSTANCE; private JPanel contentPane; private volatile boolean isChangingFile; @@ -526,7 +505,8 @@ public class Main extends JFrame { public void valueChanged(TreeSelectionEvent e) { synchronized (treeLock) { if (!isChangingFile) { - selectFile(((ByteEditTreeNode) e.getPath().getLastPathComponent()).path); + + SingleThreadedExecutor.execute( () -> selectFile(((ByteEditTreeNode) e.getPath().getLastPathComponent()).path)); } } } @@ -700,25 +680,30 @@ public class Main extends JFrame { @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == 116 && currentNodeName != null) { // F5 - try { - ClassNode classNode = classNodes.get(currentNodeName); - int prev = txtByteEditView.getCaretPosition(); - String dis = Disassembler.disassemble(classNode); - String substr = currentNodeName.substring(0, currentNodeName.length() - 6); - for (String key : Main.classNodes.keySet()) { - if (key.contains("$")) { - String[] split = key.split("\\$"); - if (split[0].equals(substr)) { - dis += "\n" + Disassembler.disassemble(classNodes.get(key)); + + SingleThreadedExecutor.execute( () -> { + try { + ClassNode classNode = classNodes.get(currentNodeName); + int prev = txtByteEditView.getCaretPosition(); + String dis = Disassembler.disassemble(classNode); + String substr = currentNodeName.substring(0, currentNodeName.length() - 6); + synchronized (Main.classNodes){ + for (String key : Main.classNodes.keySet()) { + if (key.contains("$")) { + String[] split = key.split("\\$"); + if (split[0].equals(substr)) { + dis += "\n" + Disassembler.disassemble(classNodes.get(key)); + } + } } } + txtByteEditView.setText(dis); + if (dis.length() > prev) + txtByteEditView.setCaretPosition(prev); + } catch (Exception e2) { + e2.printStackTrace(); } - txtByteEditView.setText(dis); - if (dis.length() > prev) - txtByteEditView.setCaretPosition(prev); - } catch (Exception e2) { - e2.printStackTrace(); - } + }); } } }); @@ -774,13 +759,66 @@ public class Main extends JFrame { txtByteEditView.setCodeFoldingEnabled(true); scrollPane_ByteEdit = new RTextScrollPane(); ThemeManager.registerTextArea(txtByteEditView); + JPanel panel = new JPanel(new BorderLayout()); - splitPane.setRightComponent(scrollPane_ByteEdit); + panel.add(scrollPane_ByteEdit, BorderLayout.CENTER); + final JComboBox decompilerMode = new JComboBox<>(); + DefaultComboBoxModel model = new DefaultComboBoxModel(); + Arrays.stream(EnumDecompiler.values()).forEach(model::addElement); + decompilerMode.setModel(model); + decompilerMode.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + + SingleThreadedExecutor.execute( () -> { + decompiler = (EnumDecompiler) e.getItem(); + try { + txtByteEditView.setSyntaxEditingStyle(decompiler == EnumDecompiler.BYTEEDIT ? SyntaxConstants.SYNTAX_STYLE_JAVA_DISASSEMBLE : SyntaxConstants.SYNTAX_STYLE_JAVA); + } catch (Throwable t){} + txtByteEditView.setEditable(decompiler == EnumDecompiler.BYTEEDIT); + if (currentNodeName != null) { + + + try { + ClassNode classNode = classNodes.get(currentNodeName); + int prev = txtByteEditView.getCaretPosition(); + String dis = Disassembler.disassemble(classNode); + String substr = currentNodeName.substring(0, currentNodeName.length() - 6); + for (String key : Main.classNodes.keySet()) { + if (key.contains("$")) { + String[] split = key.split("\\$"); + if (split[0].equals(substr)) { + dis += "\n" + Disassembler.disassemble(classNodes.get(key)); + } + } + } + txtByteEditView.setText(dis); + if (dis.length() > prev) + txtByteEditView.setCaretPosition(prev); + } catch (Exception e2) { + e2.printStackTrace(); + } + + + } + + }); + } + }); + panel.add(decompilerMode, BorderLayout.NORTH); + + + + splitPane.setRightComponent(panel); scrollPane_ByteEdit.setViewportView(txtByteEditView); scrollPane_ByteEdit.setLineNumbersEnabled(true); scrollPane_ByteEdit.setFoldIndicatorEnabled(true); } + + + public static EnumDecompiler decompiler = EnumDecompiler.BYTEEDIT; + private final Pattern jumpableInstructionPattern = Pattern.compile("^\t\t(?!//|\t+).+ .+"); public void goToSelected() throws BadLocationException { @@ -809,10 +847,20 @@ public class Main extends JFrame { while (classNode.outerClass != null) { classNode = classNodes.get(getFullName(classNode.outerClass)); } - int lineFound = selectFileWithSearch(getFullName(classNode.name), mn); - if (lineFound != -1) { - Main.txtByteEditView.setCaretPosition(Main.txtByteEditView.getLineStartOffset(lineFound)); - } + + + ClassNode finalClassNode = classNode; + SingleThreadedExecutor.execute( () -> { + int lineFound = selectFileWithSearch(getFullName(finalClassNode.name), mn); + if (lineFound != -1) { + try { + Main.txtByteEditView.setCaretPosition(Main.txtByteEditView.getLineStartOffset(lineFound)); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + }); + break; } } @@ -823,7 +871,8 @@ public class Main extends JFrame { if (classNode == null) { return; } - selectFile(getFullName(className)); + + SingleThreadedExecutor.execute( () -> selectFile(getFullName(className))); } else if (line.startsWith("getstatic ") || line.startsWith("putstatic ") || line.startsWith("getfield ") || line.startsWith("putfield ")) { String[] split = line.split(" "); @@ -841,10 +890,20 @@ public class Main extends JFrame { while (classNode.outerClass != null) { classNode = classNodes.get(getFullName(classNode.outerClass)); } - int lineFound = selectFileWithSearch(getFullName(classNode.name), fn); - if (lineFound != -1) { - Main.txtByteEditView.setCaretPosition(Main.txtByteEditView.getLineStartOffset(lineFound)); - } + + ClassNode finalClassNode = classNode; + ClassNode finalClassNode1 = classNode; + SingleThreadedExecutor.execute( () -> { + int lineFound = selectFileWithSearch(getFullName(finalClassNode1.name), fn); + if (lineFound != -1) { + try { + Main.txtByteEditView.setCaretPosition(Main.txtByteEditView.getLineStartOffset(lineFound)); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + }); + break; } } diff --git a/src/org/fife/ui/rsyntaxtextarea/DefaultTokenFactory.java b/src/org/fife/ui/rsyntaxtextarea/DefaultTokenFactory.java index fe8ed1d..c8fa4e4 100644 --- a/src/org/fife/ui/rsyntaxtextarea/DefaultTokenFactory.java +++ b/src/org/fife/ui/rsyntaxtextarea/DefaultTokenFactory.java @@ -102,6 +102,7 @@ class DefaultTokenFactory implements TokenFactory { @Override public TokenImpl createToken(final char[] line, final int beg, final int end, final int startOffset, final int type) { + // if(tokenList.length - 1 > currentFreeToken) return null; TokenImpl token = tokenList[currentFreeToken]; token.set(line, beg, end, startOffset, type); currentFreeToken++; diff --git a/src/org/fife/ui/rsyntaxtextarea/TokenMakerBase.java b/src/org/fife/ui/rsyntaxtextarea/TokenMakerBase.java index 454ad2d..398d798 100644 --- a/src/org/fife/ui/rsyntaxtextarea/TokenMakerBase.java +++ b/src/org/fife/ui/rsyntaxtextarea/TokenMakerBase.java @@ -108,6 +108,7 @@ public abstract class TokenMakerBase implements TokenMaker { currentToken = firstToken; // previous token is still null. } else { TokenImpl next = tokenFactory.createToken(array, start, end, startOffset, tokenType); + if(currentToken == null || next == null ) return; currentToken.setNextToken(next); previousToken = currentToken; currentToken = next; diff --git a/src/org/fife/ui/rsyntaxtextarea/modes/JavaTokenMaker.java b/src/org/fife/ui/rsyntaxtextarea/modes/JavaTokenMaker.java index 8158e3d..35e0fdb 100644 --- a/src/org/fife/ui/rsyntaxtextarea/modes/JavaTokenMaker.java +++ b/src/org/fife/ui/rsyntaxtextarea/modes/JavaTokenMaker.java @@ -3184,7 +3184,9 @@ public class JavaTokenMaker extends AbstractJFlexCTokenMaker { return null; } } else { - zzScanError(ZZ_NO_MATCH); + //zzScanError(ZZ_NO_MATCH); + return null; + } } }