diff --git a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java index d1632a514..ac0222178 100644 --- a/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/action/ActionPanel.java @@ -1,900 +1,900 @@ -/* - * Copyright (C) 2010-2015 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.action; - -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionGraph; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.CachedScript; -import com.jpexs.decompiler.flash.action.parser.ActionParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; -import com.jpexs.decompiler.flash.action.parser.script.ActionScriptLexer; -import com.jpexs.decompiler.flash.action.parser.script.ActionScriptParser; -import com.jpexs.decompiler.flash.action.parser.script.ParsedSymbol; -import com.jpexs.decompiler.flash.action.parser.script.SymbolType; -import com.jpexs.decompiler.flash.action.swf4.ActionPush; -import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; -import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.gui.AppStrings; -import com.jpexs.decompiler.flash.gui.GraphDialog; -import com.jpexs.decompiler.flash.gui.HeaderLabel; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.gui.MainPanel; -import com.jpexs.decompiler.flash.gui.SearchListener; -import com.jpexs.decompiler.flash.gui.SearchPanel; -import com.jpexs.decompiler.flash.gui.SearchResultsDialog; -import com.jpexs.decompiler.flash.gui.TagEditorPanel; -import com.jpexs.decompiler.flash.gui.View; -import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; -import com.jpexs.decompiler.flash.gui.controls.NoneSelectedButtonGroup; -import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane; -import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; -import com.jpexs.decompiler.flash.helpers.HighlightedText; -import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; -import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.helpers.CancellableWorker; -import com.jpexs.helpers.Helper; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CancellationException; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JToggleButton; -import javax.swing.SwingConstants; -import javax.swing.event.CaretEvent; -import javax.swing.event.CaretListener; -import javax.swing.tree.TreePath; -import jsyntaxpane.SyntaxDocument; -import jsyntaxpane.Token; -import jsyntaxpane.TokenType; -import jsyntaxpane.actions.ActionUtils; - -public class ActionPanel extends JPanel implements SearchListener, TagEditorPanel { - - private MainPanel mainPanel; - - public LineMarkedEditorPane editor; - - public LineMarkedEditorPane decompiledEditor; - - public JPersistentSplitPane splitPane; - - public JButton saveButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); - - public JButton editButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); - - public JButton cancelButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); - - public JLabel experimentalLabel = new JLabel(AppStrings.translate("action.edit.experimental")); - - public JButton editDecompiledButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); - - public JButton saveDecompiledButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); - - public JButton cancelDecompiledButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); - - public JToggleButton hexButton; - - public JToggleButton hexOnlyButton; - - public JToggleButton constantsViewButton; - - public JToggleButton resolveConstantsButton; - - public JLabel asmLabel = new HeaderLabel(AppStrings.translate("panel.disassembled")); - - public JLabel decLabel = new HeaderLabel(AppStrings.translate("panel.decompiled")); - - public List decompiledHilights = new ArrayList<>(); - - public List disassembledHilights = new ArrayList<>(); - - private boolean ignoreCarret = false; - - private boolean editMode = false; - - private boolean editDecompiledMode = false; - - private ActionList lastCode; - - private ASMSource src; - - public JPanel topButtonsPan; - - private HighlightedText srcWithHex; - - private HighlightedText srcNoHex; - - private HighlightedText srcHexOnly; - - private HighlightedText srcConstants; - - private String lastDecompiled = ""; - - private ASMSource lastASM; - - public SearchPanel searchPanel; - - private CancellableWorker setSourceWorker; - - public void clearSource() { - lastCode = null; - lastASM = null; - lastDecompiled = null; - searchPanel.clear(); - src = null; - srcWithHex = null; - srcNoHex = null; - srcHexOnly = null; - srcConstants = null; - } - - public String getStringUnderCursor() { - int pos = decompiledEditor.getCaretPosition(); - - SyntaxDocument sDoc = ActionUtils.getSyntaxDocument(decompiledEditor); - if (sDoc != null) { - Token t = sDoc.getTokenAt(pos + 1); - String ident = null; - //It should be identifier or obfuscated identifier - if (t != null && (t.type == TokenType.IDENTIFIER || t.type == TokenType.REGEX)) { - CharSequence tData = t.getText(sDoc); - ident = tData.toString(); - //We need to get unescaped identifier, so we use our Lexer - ActionScriptLexer lex = new ActionScriptLexer(new StringReader(ident)); - try { - ParsedSymbol symb = lex.lex(); - if (symb.type == SymbolType.IDENTIFIER) { - ident = (String) symb.value; - } else { - ident = null; - } - } catch (IOException | ActionParseException ex) { - ident = null; - } - } - if (ident == null) { - Highlighting h = Highlighting.searchPos(decompiledHilights, pos); - if (h != null) { - List list = lastCode; - Action lastIns = null; - int inspos = 0; - Action selIns = null; - for (Action ins : list) { - if (h.getProperties().offset == ins.getOffset()) { - selIns = ins; - break; - } - if (ins.getOffset() > h.getProperties().offset) { - inspos = (int) (h.getProperties().offset - lastIns.getAddress()); - selIns = lastIns; - break; - } - lastIns = ins; - } - if (selIns != null) { - if (selIns instanceof ActionPush) { - ActionPush ap = (ActionPush) selIns; - Object var = ap.values.get(inspos - 1); - String identifier = null; - if (var instanceof String) { - identifier = (String) var; - } - if (var instanceof ConstantIndex) { - identifier = ap.constantPool.get(((ConstantIndex) var).index); - } - return identifier; - } - } - - } - } else { - return ident; - } - } - return null; - } - - public boolean search(final String txt, boolean ignoreCase, boolean regexp) { - if ((txt != null) && (!txt.isEmpty())) { - searchPanel.setOptions(ignoreCase, regexp); - SWF swf = mainPanel.getCurrentSwf(); - Map asms = swf.getASMs(false); - final List found = new ArrayList<>(); - Pattern pat; - if (regexp) { - pat = Pattern.compile(txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); - } else { - pat = Pattern.compile(Pattern.quote(txt), ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); - } - int pos = 0; - for (Entry item : asms.entrySet()) { - pos++; - String workText = AppStrings.translate("work.searching"); - String decAdd = ""; - ASMSource asm = item.getValue(); - if (!SWF.isCached(asm)) { - decAdd = ", " + AppStrings.translate("work.decompiling"); - } - Main.startWork(workText + " \"" + txt + "\"" + decAdd + " - (" + pos + "/" + asms.size() + ") " + item.getKey() + "... "); - try { - if (pat.matcher(SWF.getCached(asm, null).text).find()) { - found.add(new ActionSearchResult(asm, item.getKey())); - } - } catch (InterruptedException ex) { - break; - } - } - - Main.stopWork(); - searchPanel.setSearchText(txt); - View.execInEventDispatch(() -> { - SearchResultsDialog sr = new SearchResultsDialog<>(ActionPanel.this.mainPanel.getMainFrame().getWindow(), txt, ActionPanel.this); - sr.setResults(found); - sr.setVisible(true); - }); - return true; - //return searchPanel.setResults(found); - } - return false; - } - - private void setDecompiledText(final String text) { - View.execInEventDispatch(() -> { - ignoreCarret = true; - decompiledEditor.setText(text); - ignoreCarret = false; - }); - } - - private void setEditorText(final String text, final String contentType) { - View.execInEventDispatch(() -> { - ignoreCarret = true; - editor.changeContentType(contentType); - editor.setText(text); - ignoreCarret = false; - }); - } - - private void setText(final HighlightedText text, final String contentType) { - View.execInEventDispatch(() -> { - int pos = editor.getCaretPosition(); - Highlighting lastH = null; - for (Highlighting h : disassembledHilights) { - if (pos < h.startPos) { - break; - } - lastH = h; - } - Long offset = lastH == null ? 0 : lastH.getProperties().offset; - disassembledHilights = text.instructionHilights; - String stripped = text.text; - setEditorText(stripped, contentType); - Highlighting h = Highlighting.searchOffset(disassembledHilights, offset); - if (h != null) { - if (h.startPos <= editor.getDocument().getLength()) { - editor.setCaretPosition(h.startPos); - } - } - }); - } - - private HighlightedText getHighlightedText(ScriptExportMode exportMode) { - ASMSource asm = (ASMSource) src; - DisassemblyListener listener = getDisassemblyListener(); - asm.addDisassemblyListener(listener); - HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); - try { - asm.getASMSource(exportMode, writer, lastCode); - } catch (InterruptedException ex) { - Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, null, ex); - } - asm.removeDisassemblyListener(listener); - return new HighlightedText(writer); - } - - public void setHex(ScriptExportMode exportMode) { - switch (exportMode) { - case PCODE: - if (srcNoHex == null) { - srcNoHex = getHighlightedText(exportMode); - } - - setText(srcNoHex, "text/flasm"); - break; - case PCODE_HEX: - if (srcWithHex == null) { - srcWithHex = getHighlightedText(exportMode); - } - - setText(srcWithHex, "text/flasm"); - break; - case HEX: - if (srcHexOnly == null) { - HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); - Helper.byteArrayToHexWithHeader(writer, src.getActionBytes()); - srcHexOnly = new HighlightedText(writer); - } - - setText(srcHexOnly, "text/plain"); - break; - case CONSTANTS: - if (srcConstants == null) { - srcConstants = getHighlightedText(exportMode); - } - - setText(srcConstants, "text/plain"); - break; - default: - throw new Error("Export mode not supported: " + exportMode); - } - } - - private DisassemblyListener getDisassemblyListener() { - DisassemblyListener listener = new DisassemblyListener() { - int percent = 0; - - String phase = ""; - - private void progress(String phase, long pos, long total) { - if (total < 1) { - return; - } - int newpercent = (int) (pos * 100 / total); - if (((newpercent > percent) || (!this.phase.equals(phase))) && newpercent <= 100) { - percent = newpercent; - this.phase = phase; - // todo: honfika: it is very slow to show every percent - setEditorText("; " + AppStrings.translate("work.disassembling") + " - " + phase + " " + percent + "%...", "text/flasm"); - } - } - - @Override - public void progressReading(long pos, long total) { - progress(AppStrings.translate("disassemblingProgress.reading"), pos, total); - } - - @Override - public void progressToString(long pos, long total) { - progress(AppStrings.translate("disassemblingProgress.toString"), pos, total); - } - - @Override - public void progressDeobfuscating(long pos, long total) { - progress(AppStrings.translate("disassemblingProgress.deobfuscating"), pos, total); - } - }; - return listener; - } - - public void setSource(final ASMSource src, final boolean useCache) { - if (setSourceWorker != null) { - setSourceWorker.cancel(true); - setSourceWorker = null; - } - - this.src = src; - final ASMSource asm = (ASMSource) src; - - CancellableWorker worker = new CancellableWorker() { - - @Override - protected Void doInBackground() throws Exception { - setEditorText("; " + AppStrings.translate("work.disassembling") + "...", "text/flasm"); - if (Configuration.decompile.get()) { - setDecompiledText("// " + AppStrings.translate("work.waitingfordissasembly") + "..."); - } else { - lastDecompiled = Helper.getDecompilationSkippedComment(); - setDecompiledText(lastDecompiled); - } - DisassemblyListener listener = getDisassemblyListener(); - asm.addDisassemblyListener(listener); - ActionList actions = asm.getActions(); - lastCode = actions; - asm.removeDisassemblyListener(listener); - srcWithHex = null; - srcNoHex = null; - srcHexOnly = null; - srcConstants = null; - setHex(getExportMode()); - if (Configuration.decompile.get()) { - setDecompiledText("// " + AppStrings.translate("work.decompiling") + "..."); - if (!useCache) { - SWF.uncache(asm); - } - - CachedScript sc = SWF.getCached(asm, actions); - decompiledHilights = sc.hilights; - lastDecompiled = sc.text; - lastASM = asm; - setDecompiledText(lastDecompiled); - } - setEditMode(false); - setDecompiledEditMode(false); - return null; - } - - @Override - protected void done() { - View.execInEventDispatch(() -> { - setSourceWorker = null; - Main.stopWork(); - - try { - get(); - } catch (CancellationException ex) { - setEditorText("; " + AppStrings.translate("work.canceled"), "text/flasm"); - } catch (Exception ex) { - setDecompiledText("// " + AppStrings.translate("decompilationError") + ": " + ex); - } - }); - } - }; - worker.execute(); - setSourceWorker = worker; - Main.startWork(AppStrings.translate("work.decompiling") + "...", worker); - } - - public void hilightOffset(long offset) { - } - - public ActionPanel(MainPanel mainPanel) { - this.mainPanel = mainPanel; - editor = new LineMarkedEditorPane(); - editor.setEditable(false); - decompiledEditor = new LineMarkedEditorPane(); - decompiledEditor.setEditable(false); - - searchPanel = new SearchPanel<>(new FlowLayout(), this); - - JButton graphButton = new JButton(View.getIcon("graph16")); - graphButton.addActionListener(this::graphButtonActionPerformed); - graphButton.setToolTipText(AppStrings.translate("button.viewgraph")); - graphButton.setMargin(new Insets(3, 3, 3, 3)); - - hexButton = new JToggleButton(View.getIcon("hexas16")); - hexButton.addActionListener(this::hexButtonActionPerformed); - hexButton.setToolTipText(AppStrings.translate("button.viewhex")); - hexButton.setMargin(new Insets(3, 3, 3, 3)); - - hexOnlyButton = new JToggleButton(View.getIcon("hex16")); - hexOnlyButton.addActionListener(this::hexOnlyButtonActionPerformed); - hexOnlyButton.setToolTipText(AppStrings.translate("button.viewhex")); - hexOnlyButton.setMargin(new Insets(3, 3, 3, 3)); - - // todo: change icon - constantsViewButton = new JToggleButton(View.getIcon("constantpool16")); - constantsViewButton.addActionListener(this::constantsViewButtonActionPerformed); - constantsViewButton.setToolTipText(AppStrings.translate("button.viewConstants")); - constantsViewButton.setMargin(new Insets(3, 3, 3, 3)); - - NoneSelectedButtonGroup exportModeButtonGroup = new NoneSelectedButtonGroup(); - exportModeButtonGroup.add(hexButton); - exportModeButtonGroup.add(hexOnlyButton); - exportModeButtonGroup.add(constantsViewButton); - - resolveConstantsButton = new JToggleButton(View.getIcon("constantpool16")); - resolveConstantsButton.addActionListener(this::resolveConstantsButtonActionPerformed); - resolveConstantsButton.setToolTipText(AppStrings.translate("button.resolveConstants")); - resolveConstantsButton.setMargin(new Insets(3, 3, 3, 3)); - resolveConstantsButton.setSelected(Configuration.resolveConstants.get()); - - topButtonsPan = new JPanel(); - topButtonsPan.setLayout(new BoxLayout(topButtonsPan, BoxLayout.X_AXIS)); - topButtonsPan.add(graphButton); - topButtonsPan.add(hexButton); - topButtonsPan.add(hexOnlyButton); - topButtonsPan.add(constantsViewButton); - topButtonsPan.add(Box.createRigidArea(new Dimension(10, 0))); - topButtonsPan.add(resolveConstantsButton); - - JPanel panCode = new JPanel(new BorderLayout()); - panCode.add(new JScrollPane(editor), BorderLayout.CENTER); - panCode.add(topButtonsPan, BorderLayout.NORTH); - - JPanel panB = new JPanel(); - panB.setLayout(new BorderLayout()); - asmLabel.setHorizontalAlignment(SwingConstants.CENTER); - //asmLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - panB.add(asmLabel, BorderLayout.NORTH); - panB.add(panCode, BorderLayout.CENTER); - - JPanel buttonsPan = new JPanel(); - buttonsPan.setLayout(new FlowLayout()); - buttonsPan.add(editButton); - buttonsPan.add(saveButton); - buttonsPan.add(cancelButton); - - editButton.setMargin(new Insets(3, 3, 3, 10)); - saveButton.setMargin(new Insets(3, 3, 3, 10)); - cancelButton.setMargin(new Insets(3, 3, 3, 10)); - - JPanel decButtonsPan = new JPanel(new FlowLayout()); - decButtonsPan.add(editDecompiledButton); - decButtonsPan.add(experimentalLabel); - decButtonsPan.add(saveDecompiledButton); - decButtonsPan.add(cancelDecompiledButton); - - editDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - saveDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - cancelDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); - - //buttonsPan.add(saveHexButton); - //buttonsPan.add(loadHexButton); - panB.add(buttonsPan, BorderLayout.SOUTH); - - saveButton.addActionListener(this::saveActionButtonActionPerformed); - editButton.addActionListener(this::editActionButtonActionPerformed); - cancelButton.addActionListener(this::cancelActionButtonActionPerformed); - saveButton.setVisible(false); - cancelButton.setVisible(false); - - saveDecompiledButton.addActionListener(this::saveDecompiledButtonActionPerformed); - editDecompiledButton.addActionListener(this::editDecompiledButtonActionPerformed); - cancelDecompiledButton.addActionListener(this::cancelDecompiledButtonActionPerformed); - saveDecompiledButton.setVisible(false); - cancelDecompiledButton.setVisible(false); - - JPanel decPanel = new JPanel(new BorderLayout()); - decPanel.add(new JScrollPane(decompiledEditor), BorderLayout.CENTER); - //decPanel.add(searchPanel, BorderLayout.NORTH); - - JPanel panA = new JPanel(); - panA.setLayout(new BorderLayout()); - panA.add(decPanel, BorderLayout.CENTER); - panA.add(decLabel, BorderLayout.NORTH); - panA.add(decButtonsPan, BorderLayout.SOUTH); - decLabel.setHorizontalAlignment(SwingConstants.CENTER); - //decLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - - setLayout(new BorderLayout()); - add(splitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, panA, panB, Configuration.guiActionSplitPaneDividerLocationPercent), BorderLayout.CENTER); - - editor.setFont(new Font("Monospaced", Font.PLAIN, editor.getFont().getSize())); - decompiledEditor.setFont(new Font("Monospaced", Font.PLAIN, decompiledEditor.getFont().getSize())); - decompiledEditor.changeContentType("text/actionscript"); - - editor.addCaretListener(new CaretListener() { - @Override - public void caretUpdate(CaretEvent e) { - if (ignoreCarret) { - return; - } - if (editMode || editDecompiledMode) { - return; - } - editor.getCaret().setVisible(true); - int pos = editor.getCaretPosition(); - Highlighting lastH = null; - for (Highlighting h : disassembledHilights) { - if (pos < h.startPos) { - break; - } - lastH = h; - } - Long ofs = lastH == null ? 0 : lastH.getProperties().offset; - Highlighting h2 = Highlighting.searchOffset(decompiledHilights, ofs); - if (h2 != null) { - ignoreCarret = true; - if (h2.startPos <= decompiledEditor.getDocument().getLength()) { - decompiledEditor.setCaretPosition(h2.startPos); - } - decompiledEditor.getCaret().setVisible(true); - ignoreCarret = false; - - } - } - }); - - decompiledEditor.addCaretListener(new CaretListener() { - @Override - public void caretUpdate(CaretEvent e) { - if (ignoreCarret) { - return; - } - if (editMode || editDecompiledMode) { - return; - } - decompiledEditor.getCaret().setVisible(true); - int pos = decompiledEditor.getCaretPosition(); - Highlighting h = Highlighting.searchPos(decompiledHilights, pos); - if (h != null) { - Highlighting h2 = Highlighting.searchOffset(disassembledHilights, h.getProperties().offset); - if (h2 != null) { - ignoreCarret = true; - if (h2.startPos > 0 && h2.startPos < editor.getText().length()) { - editor.setCaretPosition(h2.startPos); - } - editor.getCaret().setVisible(true); - ignoreCarret = false; - } - } - } - }); - - editor.addTextChangedListener(this::editorTextChanged); - decompiledEditor.addTextChangedListener(this::decompiledEditorTextChanged); - } - - private void editorTextChanged() { - setModified(true); - } - - private void decompiledEditorTextChanged() { - setDecompiledModified(true); - } - - private boolean isModified() { - return saveButton.isVisible() && saveButton.isEnabled(); - } - - private void setModified(boolean value) { - saveButton.setEnabled(value); - cancelButton.setEnabled(value); - } - - private boolean isDecompiledModified() { - return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); - } - - private void setDecompiledModified(boolean value) { - saveDecompiledButton.setEnabled(value); - cancelDecompiledButton.setEnabled(value); - } - - public void setEditMode(boolean val) { - if (val) { - if (hexOnlyButton.isSelected()) { - setHex(ScriptExportMode.HEX); - } else if (constantsViewButton.isSelected()) { - setHex(ScriptExportMode.CONSTANTS); - } else { - setHex(ScriptExportMode.PCODE); - } - } - - editor.setEditable(val); - saveButton.setVisible(val); - saveButton.setEnabled(false); - editButton.setVisible(!val); - cancelButton.setVisible(val); - - editor.getCaret().setVisible(true); - asmLabel.setIcon(val ? View.getIcon("editing16") : null); // this line is not working - topButtonsPan.setVisible(!val); - editMode = val; - editor.requestFocusInWindow(); - } - - public void setDecompiledEditMode(boolean val) { - if (lastASM == null) { - return; - } - - int lastLine = decompiledEditor.getLine(); - int prefLines = lastASM.getPrefixLineCount(); - if (val) { - String newText = lastASM.removePrefixAndSuffix(lastDecompiled); - setDecompiledText(newText); - if (lastLine > -1) { - if (lastLine - prefLines >= 0) { - decompiledEditor.gotoLine(lastLine - prefLines + 1); - } - } - } else { - String newText = lastDecompiled; - setDecompiledText(newText); - if (lastLine > -1) { - decompiledEditor.gotoLine(lastLine + prefLines + 1); - } - } - - decompiledEditor.setEditable(val); - saveDecompiledButton.setVisible(val); - saveDecompiledButton.setEnabled(false); - editDecompiledButton.setVisible(!val); - experimentalLabel.setVisible(!val); - cancelDecompiledButton.setVisible(val); - - decompiledEditor.getCaret().setVisible(true); - decLabel.setIcon(val ? View.getIcon("editing16") : null); - editDecompiledMode = val; - decompiledEditor.requestFocusInWindow(); - } - - private void graphButtonActionPerformed(ActionEvent evt) { - if (lastCode != null) { - try { - GraphDialog gf = new GraphDialog(mainPanel.getMainFrame().getWindow(), new ActionGraph(lastCode, new HashMap<>(), new HashMap<>(), new HashMap<>(), SWF.DEFAULT_VERSION), ""); - gf.setVisible(true); - } catch (InterruptedException ex) { - Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - private void editActionButtonActionPerformed(ActionEvent evt) { - setEditMode(true); - } - - private void hexButtonActionPerformed(ActionEvent evt) { - setHex(getExportMode()); - } - - private void hexOnlyButtonActionPerformed(ActionEvent evt) { - setHex(getExportMode()); - } - - private void constantsViewButtonActionPerformed(ActionEvent evt) { - setHex(getExportMode()); - } - - private void resolveConstantsButtonActionPerformed(ActionEvent evt) { - boolean resolve = resolveConstantsButton.isSelected(); - Configuration.resolveConstants.set(resolve); - - srcWithHex = null; - srcNoHex = null; - // srcHexOnly = null; is not needed since it does not contains the resolved constant names - setHex(getExportMode()); - } - - private void cancelActionButtonActionPerformed(ActionEvent evt) { - setEditMode(false); - setHex(getExportMode()); - } - - private void saveActionButtonActionPerformed(ActionEvent evt) { - try { - String text = editor.getText(); - String trimmed = text.trim(); - if (trimmed.startsWith(Helper.hexData)) { - src.setActionBytes(Helper.getBytesFromHexaText(text)); - } else if (trimmed.startsWith(Helper.constants)) { - setConstantPools(Helper.getConstantPoolsFromText(text)); - } else { - src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); - } - - SWF.uncache(src); - src.setModified(); - setSource(this.src, false); - View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); - saveButton.setVisible(false); - cancelButton.setVisible(false); - editButton.setVisible(true); - editor.setEditable(false); - editMode = false; - mainPanel.refreshTree(src.getSwf()); - } catch (IOException ex) { - } catch (ActionParseException ex) { - editor.gotoLine((int) ex.line); - editor.markError(); - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - } - - private void setConstantPools(List> constantPools) { - try { - ActionList actions = src.getActions(); - int poolIdx = 0; - for (Action action : actions) { - if (action instanceof ActionConstantPool) { - ActionConstantPool cPool = (ActionConstantPool) action; - List constantPool = constantPools.get(poolIdx); - cPool.constantPool = constantPool; - - poolIdx++; - if (constantPools.size() <= poolIdx) { - break; - } - } - } - - src.setActions(actions); - } catch (InterruptedException ex) { - Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - - private void editDecompiledButtonActionPerformed(ActionEvent evt) { - if (View.showConfirmDialog(null, AppStrings.translate("message.confirm.experimental.function"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, Configuration.warningExperimentalAS12Edit, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { - setDecompiledEditMode(true); - } - } - - private void cancelDecompiledButtonActionPerformed(ActionEvent evt) { - setDecompiledEditMode(false); - } - - private void saveDecompiledButtonActionPerformed(ActionEvent evt) { - try { - ActionScriptParser par = new ActionScriptParser(mainPanel.getCurrentSwf().version); - src.setActions(par.actionsFromString(decompiledEditor.getText())); - src.setModified(); - setSource(src, false); - - View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); - setDecompiledEditMode(false); - } catch (IOException ex) { - Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, "IOException during action compiling", ex); - } catch (ActionParseException ex) { - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } catch (CompilationException ex) { - View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); - } - } - - private ScriptExportMode getExportMode() { - ScriptExportMode exportMode = hexOnlyButton.isSelected() ? ScriptExportMode.HEX - : hexButton.isSelected() ? ScriptExportMode.PCODE_HEX - : constantsViewButton.isSelected() ? ScriptExportMode.CONSTANTS - : ScriptExportMode.PCODE; - return exportMode; - } - - @Override - public void updateSearchPos(ActionSearchResult item) { - TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); - TreePath tp = ttm.getTreePath(item.src); - mainPanel.tagTree.setSelectionPath(tp); - mainPanel.tagTree.scrollPathToVisible(tp); - decompiledEditor.setCaretPosition(0); - - View.execInEventDispatchLater(() -> { - searchPanel.showQuickFindDialog(decompiledEditor); - }); - } - - @Override - public boolean tryAutoSave() { - // todo: implement - return false; - } - - @Override - public boolean isEditing() { - return (saveButton.isVisible() && saveButton.isEnabled()) - || (saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled()); - } -} +/* + * Copyright (C) 2010-2015 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.action; + +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionGraph; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.CachedScript; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; +import com.jpexs.decompiler.flash.action.parser.script.ActionScriptLexer; +import com.jpexs.decompiler.flash.action.parser.script.ActionScriptParser; +import com.jpexs.decompiler.flash.action.parser.script.ParsedSymbol; +import com.jpexs.decompiler.flash.action.parser.script.SymbolType; +import com.jpexs.decompiler.flash.action.swf4.ActionPush; +import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; +import com.jpexs.decompiler.flash.action.swf5.ActionConstantPool; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.GraphDialog; +import com.jpexs.decompiler.flash.gui.HeaderLabel; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.gui.MainPanel; +import com.jpexs.decompiler.flash.gui.SearchListener; +import com.jpexs.decompiler.flash.gui.SearchPanel; +import com.jpexs.decompiler.flash.gui.SearchResultsDialog; +import com.jpexs.decompiler.flash.gui.TagEditorPanel; +import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; +import com.jpexs.decompiler.flash.gui.controls.NoneSelectedButtonGroup; +import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane; +import com.jpexs.decompiler.flash.gui.tagtree.TagTreeModel; +import com.jpexs.decompiler.flash.helpers.HighlightedText; +import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.hilight.Highlighting; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.helpers.CancellableWorker; +import com.jpexs.helpers.Helper; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JToggleButton; +import javax.swing.SwingConstants; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.tree.TreePath; +import jsyntaxpane.SyntaxDocument; +import jsyntaxpane.Token; +import jsyntaxpane.TokenType; +import jsyntaxpane.actions.ActionUtils; + +public class ActionPanel extends JPanel implements SearchListener, TagEditorPanel { + + private MainPanel mainPanel; + + public LineMarkedEditorPane editor; + + public LineMarkedEditorPane decompiledEditor; + + public JPersistentSplitPane splitPane; + + public JButton saveButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); + + public JButton editButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); + + public JButton cancelButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); + + public JLabel experimentalLabel = new JLabel(AppStrings.translate("action.edit.experimental")); + + public JButton editDecompiledButton = new JButton(AppStrings.translate("button.edit"), View.getIcon("edit16")); + + public JButton saveDecompiledButton = new JButton(AppStrings.translate("button.save"), View.getIcon("save16")); + + public JButton cancelDecompiledButton = new JButton(AppStrings.translate("button.cancel"), View.getIcon("cancel16")); + + public JToggleButton hexButton; + + public JToggleButton hexOnlyButton; + + public JToggleButton constantsViewButton; + + public JToggleButton resolveConstantsButton; + + public JLabel asmLabel = new HeaderLabel(AppStrings.translate("panel.disassembled")); + + public JLabel decLabel = new HeaderLabel(AppStrings.translate("panel.decompiled")); + + public List decompiledHilights = new ArrayList<>(); + + public List disassembledHilights = new ArrayList<>(); + + private boolean ignoreCarret = false; + + private boolean editMode = false; + + private boolean editDecompiledMode = false; + + private ActionList lastCode; + + private ASMSource src; + + public JPanel topButtonsPan; + + private HighlightedText srcWithHex; + + private HighlightedText srcNoHex; + + private HighlightedText srcHexOnly; + + private HighlightedText srcConstants; + + private String lastDecompiled = ""; + + private ASMSource lastASM; + + public SearchPanel searchPanel; + + private CancellableWorker setSourceWorker; + + public void clearSource() { + lastCode = null; + lastASM = null; + lastDecompiled = null; + searchPanel.clear(); + src = null; + srcWithHex = null; + srcNoHex = null; + srcHexOnly = null; + srcConstants = null; + } + + public String getStringUnderCursor() { + int pos = decompiledEditor.getCaretPosition(); + + SyntaxDocument sDoc = ActionUtils.getSyntaxDocument(decompiledEditor); + if (sDoc != null) { + Token t = sDoc.getTokenAt(pos + 1); + String ident = null; + //It should be identifier or obfuscated identifier + if (t != null && (t.type == TokenType.IDENTIFIER || t.type == TokenType.REGEX)) { + CharSequence tData = t.getText(sDoc); + ident = tData.toString(); + //We need to get unescaped identifier, so we use our Lexer + ActionScriptLexer lex = new ActionScriptLexer(new StringReader(ident)); + try { + ParsedSymbol symb = lex.lex(); + if (symb.type == SymbolType.IDENTIFIER) { + ident = (String) symb.value; + } else { + ident = null; + } + } catch (IOException | ActionParseException ex) { + ident = null; + } + } + if (ident == null) { + Highlighting h = Highlighting.searchPos(decompiledHilights, pos); + if (h != null) { + List list = lastCode; + Action lastIns = null; + int inspos = 0; + Action selIns = null; + for (Action ins : list) { + if (h.getProperties().offset == ins.getOffset()) { + selIns = ins; + break; + } + if (ins.getOffset() > h.getProperties().offset) { + inspos = (int) (h.getProperties().offset - lastIns.getAddress()); + selIns = lastIns; + break; + } + lastIns = ins; + } + if (selIns != null) { + if (selIns instanceof ActionPush) { + ActionPush ap = (ActionPush) selIns; + Object var = ap.values.get(inspos - 1); + String identifier = null; + if (var instanceof String) { + identifier = (String) var; + } + if (var instanceof ConstantIndex) { + identifier = ap.constantPool.get(((ConstantIndex) var).index); + } + return identifier; + } + } + + } + } else { + return ident; + } + } + return null; + } + + public boolean search(final String txt, boolean ignoreCase, boolean regexp) { + if ((txt != null) && (!txt.isEmpty())) { + searchPanel.setOptions(ignoreCase, regexp); + SWF swf = mainPanel.getCurrentSwf(); + Map asms = swf.getASMs(false); + final List found = new ArrayList<>(); + Pattern pat; + if (regexp) { + pat = Pattern.compile(txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); + } else { + pat = Pattern.compile(Pattern.quote(txt), ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0); + } + int pos = 0; + for (Entry item : asms.entrySet()) { + pos++; + String workText = AppStrings.translate("work.searching"); + String decAdd = ""; + ASMSource asm = item.getValue(); + if (!SWF.isCached(asm)) { + decAdd = ", " + AppStrings.translate("work.decompiling"); + } + Main.startWork(workText + " \"" + txt + "\"" + decAdd + " - (" + pos + "/" + asms.size() + ") " + item.getKey() + "... "); + try { + if (pat.matcher(SWF.getCached(asm, null).text).find()) { + found.add(new ActionSearchResult(asm, item.getKey())); + } + } catch (InterruptedException ex) { + break; + } + } + + Main.stopWork(); + searchPanel.setSearchText(txt); + View.execInEventDispatch(() -> { + SearchResultsDialog sr = new SearchResultsDialog<>(ActionPanel.this.mainPanel.getMainFrame().getWindow(), txt, ActionPanel.this); + sr.setResults(found); + sr.setVisible(true); + }); + return true; + //return searchPanel.setResults(found); + } + return false; + } + + private void setDecompiledText(final String text) { + View.execInEventDispatch(() -> { + ignoreCarret = true; + decompiledEditor.setText(text); + ignoreCarret = false; + }); + } + + private void setEditorText(final String text, final String contentType) { + View.execInEventDispatch(() -> { + ignoreCarret = true; + editor.changeContentType(contentType); + editor.setText(text); + ignoreCarret = false; + }); + } + + private void setText(final HighlightedText text, final String contentType) { + View.execInEventDispatch(() -> { + int pos = editor.getCaretPosition(); + Highlighting lastH = null; + for (Highlighting h : disassembledHilights) { + if (pos < h.startPos) { + break; + } + lastH = h; + } + Long offset = lastH == null ? 0 : lastH.getProperties().offset; + disassembledHilights = text.instructionHilights; + String stripped = text.text; + setEditorText(stripped, contentType); + Highlighting h = Highlighting.searchOffset(disassembledHilights, offset); + if (h != null) { + if (h.startPos <= editor.getDocument().getLength()) { + editor.setCaretPosition(h.startPos); + } + } + }); + } + + private HighlightedText getHighlightedText(ScriptExportMode exportMode) { + ASMSource asm = (ASMSource) src; + DisassemblyListener listener = getDisassemblyListener(); + asm.addDisassemblyListener(listener); + HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); + try { + asm.getASMSource(exportMode, writer, lastCode); + } catch (InterruptedException ex) { + Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, null, ex); + } + asm.removeDisassemblyListener(listener); + return new HighlightedText(writer); + } + + public void setHex(ScriptExportMode exportMode) { + switch (exportMode) { + case PCODE: + if (srcNoHex == null) { + srcNoHex = getHighlightedText(exportMode); + } + + setText(srcNoHex, "text/flasm"); + break; + case PCODE_HEX: + if (srcWithHex == null) { + srcWithHex = getHighlightedText(exportMode); + } + + setText(srcWithHex, "text/flasm"); + break; + case HEX: + if (srcHexOnly == null) { + HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); + Helper.byteArrayToHexWithHeader(writer, src.getActionBytes()); + srcHexOnly = new HighlightedText(writer); + } + + setText(srcHexOnly, "text/plain"); + break; + case CONSTANTS: + if (srcConstants == null) { + srcConstants = getHighlightedText(exportMode); + } + + setText(srcConstants, "text/plain"); + break; + default: + throw new Error("Export mode not supported: " + exportMode); + } + } + + private DisassemblyListener getDisassemblyListener() { + DisassemblyListener listener = new DisassemblyListener() { + int percent = 0; + + String phase = ""; + + private void progress(String phase, long pos, long total) { + if (total < 1) { + return; + } + int newpercent = (int) (pos * 100 / total); + if (((newpercent > percent) || (!this.phase.equals(phase))) && newpercent <= 100) { + percent = newpercent; + this.phase = phase; + // todo: honfika: it is very slow to show every percent + setEditorText("; " + AppStrings.translate("work.disassembling") + " - " + phase + " " + percent + "%...", "text/flasm"); + } + } + + @Override + public void progressReading(long pos, long total) { + progress(AppStrings.translate("disassemblingProgress.reading"), pos, total); + } + + @Override + public void progressToString(long pos, long total) { + progress(AppStrings.translate("disassemblingProgress.toString"), pos, total); + } + + @Override + public void progressDeobfuscating(long pos, long total) { + progress(AppStrings.translate("disassemblingProgress.deobfuscating"), pos, total); + } + }; + return listener; + } + + public void setSource(final ASMSource src, final boolean useCache) { + if (setSourceWorker != null) { + setSourceWorker.cancel(true); + setSourceWorker = null; + } + + this.src = src; + final ASMSource asm = (ASMSource) src; + + CancellableWorker worker = new CancellableWorker() { + + @Override + protected Void doInBackground() throws Exception { + setEditorText("; " + AppStrings.translate("work.disassembling") + "...", "text/flasm"); + if (Configuration.decompile.get()) { + setDecompiledText("// " + AppStrings.translate("work.waitingfordissasembly") + "..."); + } else { + lastDecompiled = Helper.getDecompilationSkippedComment(); + setDecompiledText(lastDecompiled); + } + DisassemblyListener listener = getDisassemblyListener(); + asm.addDisassemblyListener(listener); + ActionList actions = asm.getActions(); + lastCode = actions; + asm.removeDisassemblyListener(listener); + srcWithHex = null; + srcNoHex = null; + srcHexOnly = null; + srcConstants = null; + setHex(getExportMode()); + if (Configuration.decompile.get()) { + setDecompiledText("// " + AppStrings.translate("work.decompiling") + "..."); + if (!useCache) { + SWF.uncache(asm); + } + + CachedScript sc = SWF.getCached(asm, actions); + decompiledHilights = sc.hilights; + lastDecompiled = sc.text; + lastASM = asm; + setDecompiledText(lastDecompiled); + } + setEditMode(false); + setDecompiledEditMode(false); + return null; + } + + @Override + protected void done() { + View.execInEventDispatch(() -> { + setSourceWorker = null; + Main.stopWork(); + + try { + get(); + } catch (CancellationException ex) { + setEditorText("; " + AppStrings.translate("work.canceled"), "text/flasm"); + } catch (Exception ex) { + setDecompiledText("// " + AppStrings.translate("decompilationError") + ": " + ex); + } + }); + } + }; + worker.execute(); + setSourceWorker = worker; + Main.startWork(AppStrings.translate("work.decompiling") + "...", worker); + } + + public void hilightOffset(long offset) { + } + + public ActionPanel(MainPanel mainPanel) { + this.mainPanel = mainPanel; + editor = new LineMarkedEditorPane(); + editor.setEditable(false); + decompiledEditor = new LineMarkedEditorPane(); + decompiledEditor.setEditable(false); + + searchPanel = new SearchPanel<>(new FlowLayout(), this); + + JButton graphButton = new JButton(View.getIcon("graph16")); + graphButton.addActionListener(this::graphButtonActionPerformed); + graphButton.setToolTipText(AppStrings.translate("button.viewgraph")); + graphButton.setMargin(new Insets(3, 3, 3, 3)); + + hexButton = new JToggleButton(View.getIcon("hexas16")); + hexButton.addActionListener(this::hexButtonActionPerformed); + hexButton.setToolTipText(AppStrings.translate("button.viewhex")); + hexButton.setMargin(new Insets(3, 3, 3, 3)); + + hexOnlyButton = new JToggleButton(View.getIcon("hex16")); + hexOnlyButton.addActionListener(this::hexOnlyButtonActionPerformed); + hexOnlyButton.setToolTipText(AppStrings.translate("button.viewhex")); + hexOnlyButton.setMargin(new Insets(3, 3, 3, 3)); + + // todo: change icon + constantsViewButton = new JToggleButton(View.getIcon("constantpool16")); + constantsViewButton.addActionListener(this::constantsViewButtonActionPerformed); + constantsViewButton.setToolTipText(AppStrings.translate("button.viewConstants")); + constantsViewButton.setMargin(new Insets(3, 3, 3, 3)); + + NoneSelectedButtonGroup exportModeButtonGroup = new NoneSelectedButtonGroup(); + exportModeButtonGroup.add(hexButton); + exportModeButtonGroup.add(hexOnlyButton); + exportModeButtonGroup.add(constantsViewButton); + + resolveConstantsButton = new JToggleButton(View.getIcon("resolveconst16")); + resolveConstantsButton.addActionListener(this::resolveConstantsButtonActionPerformed); + resolveConstantsButton.setToolTipText(AppStrings.translate("button.resolveConstants")); + resolveConstantsButton.setMargin(new Insets(3, 3, 3, 3)); + resolveConstantsButton.setSelected(Configuration.resolveConstants.get()); + + topButtonsPan = new JPanel(); + topButtonsPan.setLayout(new BoxLayout(topButtonsPan, BoxLayout.X_AXIS)); + topButtonsPan.add(graphButton); + topButtonsPan.add(hexButton); + topButtonsPan.add(hexOnlyButton); + topButtonsPan.add(constantsViewButton); + topButtonsPan.add(Box.createRigidArea(new Dimension(10, 0))); + topButtonsPan.add(resolveConstantsButton); + + JPanel panCode = new JPanel(new BorderLayout()); + panCode.add(new JScrollPane(editor), BorderLayout.CENTER); + panCode.add(topButtonsPan, BorderLayout.NORTH); + + JPanel panB = new JPanel(); + panB.setLayout(new BorderLayout()); + asmLabel.setHorizontalAlignment(SwingConstants.CENTER); + //asmLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + panB.add(asmLabel, BorderLayout.NORTH); + panB.add(panCode, BorderLayout.CENTER); + + JPanel buttonsPan = new JPanel(); + buttonsPan.setLayout(new FlowLayout()); + buttonsPan.add(editButton); + buttonsPan.add(saveButton); + buttonsPan.add(cancelButton); + + editButton.setMargin(new Insets(3, 3, 3, 10)); + saveButton.setMargin(new Insets(3, 3, 3, 10)); + cancelButton.setMargin(new Insets(3, 3, 3, 10)); + + JPanel decButtonsPan = new JPanel(new FlowLayout()); + decButtonsPan.add(editDecompiledButton); + decButtonsPan.add(experimentalLabel); + decButtonsPan.add(saveDecompiledButton); + decButtonsPan.add(cancelDecompiledButton); + + editDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + saveDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + cancelDecompiledButton.setMargin(new Insets(3, 3, 3, 10)); + + //buttonsPan.add(saveHexButton); + //buttonsPan.add(loadHexButton); + panB.add(buttonsPan, BorderLayout.SOUTH); + + saveButton.addActionListener(this::saveActionButtonActionPerformed); + editButton.addActionListener(this::editActionButtonActionPerformed); + cancelButton.addActionListener(this::cancelActionButtonActionPerformed); + saveButton.setVisible(false); + cancelButton.setVisible(false); + + saveDecompiledButton.addActionListener(this::saveDecompiledButtonActionPerformed); + editDecompiledButton.addActionListener(this::editDecompiledButtonActionPerformed); + cancelDecompiledButton.addActionListener(this::cancelDecompiledButtonActionPerformed); + saveDecompiledButton.setVisible(false); + cancelDecompiledButton.setVisible(false); + + JPanel decPanel = new JPanel(new BorderLayout()); + decPanel.add(new JScrollPane(decompiledEditor), BorderLayout.CENTER); + //decPanel.add(searchPanel, BorderLayout.NORTH); + + JPanel panA = new JPanel(); + panA.setLayout(new BorderLayout()); + panA.add(decPanel, BorderLayout.CENTER); + panA.add(decLabel, BorderLayout.NORTH); + panA.add(decButtonsPan, BorderLayout.SOUTH); + decLabel.setHorizontalAlignment(SwingConstants.CENTER); + //decLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + + setLayout(new BorderLayout()); + add(splitPane = new JPersistentSplitPane(JSplitPane.HORIZONTAL_SPLIT, panA, panB, Configuration.guiActionSplitPaneDividerLocationPercent), BorderLayout.CENTER); + + editor.setFont(new Font("Monospaced", Font.PLAIN, editor.getFont().getSize())); + decompiledEditor.setFont(new Font("Monospaced", Font.PLAIN, decompiledEditor.getFont().getSize())); + decompiledEditor.changeContentType("text/actionscript"); + + editor.addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent e) { + if (ignoreCarret) { + return; + } + if (editMode || editDecompiledMode) { + return; + } + editor.getCaret().setVisible(true); + int pos = editor.getCaretPosition(); + Highlighting lastH = null; + for (Highlighting h : disassembledHilights) { + if (pos < h.startPos) { + break; + } + lastH = h; + } + Long ofs = lastH == null ? 0 : lastH.getProperties().offset; + Highlighting h2 = Highlighting.searchOffset(decompiledHilights, ofs); + if (h2 != null) { + ignoreCarret = true; + if (h2.startPos <= decompiledEditor.getDocument().getLength()) { + decompiledEditor.setCaretPosition(h2.startPos); + } + decompiledEditor.getCaret().setVisible(true); + ignoreCarret = false; + + } + } + }); + + decompiledEditor.addCaretListener(new CaretListener() { + @Override + public void caretUpdate(CaretEvent e) { + if (ignoreCarret) { + return; + } + if (editMode || editDecompiledMode) { + return; + } + decompiledEditor.getCaret().setVisible(true); + int pos = decompiledEditor.getCaretPosition(); + Highlighting h = Highlighting.searchPos(decompiledHilights, pos); + if (h != null) { + Highlighting h2 = Highlighting.searchOffset(disassembledHilights, h.getProperties().offset); + if (h2 != null) { + ignoreCarret = true; + if (h2.startPos > 0 && h2.startPos < editor.getText().length()) { + editor.setCaretPosition(h2.startPos); + } + editor.getCaret().setVisible(true); + ignoreCarret = false; + } + } + } + }); + + editor.addTextChangedListener(this::editorTextChanged); + decompiledEditor.addTextChangedListener(this::decompiledEditorTextChanged); + } + + private void editorTextChanged() { + setModified(true); + } + + private void decompiledEditorTextChanged() { + setDecompiledModified(true); + } + + private boolean isModified() { + return saveButton.isVisible() && saveButton.isEnabled(); + } + + private void setModified(boolean value) { + saveButton.setEnabled(value); + cancelButton.setEnabled(value); + } + + private boolean isDecompiledModified() { + return saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled(); + } + + private void setDecompiledModified(boolean value) { + saveDecompiledButton.setEnabled(value); + cancelDecompiledButton.setEnabled(value); + } + + public void setEditMode(boolean val) { + if (val) { + if (hexOnlyButton.isSelected()) { + setHex(ScriptExportMode.HEX); + } else if (constantsViewButton.isSelected()) { + setHex(ScriptExportMode.CONSTANTS); + } else { + setHex(ScriptExportMode.PCODE); + } + } + + editor.setEditable(val); + saveButton.setVisible(val); + saveButton.setEnabled(false); + editButton.setVisible(!val); + cancelButton.setVisible(val); + + editor.getCaret().setVisible(true); + asmLabel.setIcon(val ? View.getIcon("editing16") : null); // this line is not working + topButtonsPan.setVisible(!val); + editMode = val; + editor.requestFocusInWindow(); + } + + public void setDecompiledEditMode(boolean val) { + if (lastASM == null) { + return; + } + + int lastLine = decompiledEditor.getLine(); + int prefLines = lastASM.getPrefixLineCount(); + if (val) { + String newText = lastASM.removePrefixAndSuffix(lastDecompiled); + setDecompiledText(newText); + if (lastLine > -1) { + if (lastLine - prefLines >= 0) { + decompiledEditor.gotoLine(lastLine - prefLines + 1); + } + } + } else { + String newText = lastDecompiled; + setDecompiledText(newText); + if (lastLine > -1) { + decompiledEditor.gotoLine(lastLine + prefLines + 1); + } + } + + decompiledEditor.setEditable(val); + saveDecompiledButton.setVisible(val); + saveDecompiledButton.setEnabled(false); + editDecompiledButton.setVisible(!val); + experimentalLabel.setVisible(!val); + cancelDecompiledButton.setVisible(val); + + decompiledEditor.getCaret().setVisible(true); + decLabel.setIcon(val ? View.getIcon("editing16") : null); + editDecompiledMode = val; + decompiledEditor.requestFocusInWindow(); + } + + private void graphButtonActionPerformed(ActionEvent evt) { + if (lastCode != null) { + try { + GraphDialog gf = new GraphDialog(mainPanel.getMainFrame().getWindow(), new ActionGraph(lastCode, new HashMap<>(), new HashMap<>(), new HashMap<>(), SWF.DEFAULT_VERSION), ""); + gf.setVisible(true); + } catch (InterruptedException ex) { + Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + private void editActionButtonActionPerformed(ActionEvent evt) { + setEditMode(true); + } + + private void hexButtonActionPerformed(ActionEvent evt) { + setHex(getExportMode()); + } + + private void hexOnlyButtonActionPerformed(ActionEvent evt) { + setHex(getExportMode()); + } + + private void constantsViewButtonActionPerformed(ActionEvent evt) { + setHex(getExportMode()); + } + + private void resolveConstantsButtonActionPerformed(ActionEvent evt) { + boolean resolve = resolveConstantsButton.isSelected(); + Configuration.resolveConstants.set(resolve); + + srcWithHex = null; + srcNoHex = null; + // srcHexOnly = null; is not needed since it does not contains the resolved constant names + setHex(getExportMode()); + } + + private void cancelActionButtonActionPerformed(ActionEvent evt) { + setEditMode(false); + setHex(getExportMode()); + } + + private void saveActionButtonActionPerformed(ActionEvent evt) { + try { + String text = editor.getText(); + String trimmed = text.trim(); + if (trimmed.startsWith(Helper.hexData)) { + src.setActionBytes(Helper.getBytesFromHexaText(text)); + } else if (trimmed.startsWith(Helper.constants)) { + setConstantPools(Helper.getConstantPoolsFromText(text)); + } else { + src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); + } + + SWF.uncache(src); + src.setModified(); + setSource(this.src, false); + View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); + saveButton.setVisible(false); + cancelButton.setVisible(false); + editButton.setVisible(true); + editor.setEditable(false); + editMode = false; + mainPanel.refreshTree(src.getSwf()); + } catch (IOException ex) { + } catch (ActionParseException ex) { + editor.gotoLine((int) ex.line); + editor.markError(); + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + + private void setConstantPools(List> constantPools) { + try { + ActionList actions = src.getActions(); + int poolIdx = 0; + for (Action action : actions) { + if (action instanceof ActionConstantPool) { + ActionConstantPool cPool = (ActionConstantPool) action; + List constantPool = constantPools.get(poolIdx); + cPool.constantPool = constantPool; + + poolIdx++; + if (constantPools.size() <= poolIdx) { + break; + } + } + } + + src.setActions(actions); + } catch (InterruptedException ex) { + Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private void editDecompiledButtonActionPerformed(ActionEvent evt) { + if (View.showConfirmDialog(null, AppStrings.translate("message.confirm.experimental.function"), AppStrings.translate("message.warning"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, Configuration.warningExperimentalAS12Edit, JOptionPane.OK_OPTION) == JOptionPane.OK_OPTION) { + setDecompiledEditMode(true); + } + } + + private void cancelDecompiledButtonActionPerformed(ActionEvent evt) { + setDecompiledEditMode(false); + } + + private void saveDecompiledButtonActionPerformed(ActionEvent evt) { + try { + ActionScriptParser par = new ActionScriptParser(mainPanel.getCurrentSwf().version); + src.setActions(par.actionsFromString(decompiledEditor.getText())); + src.setModified(); + setSource(src, false); + + View.showMessageDialog(this, AppStrings.translate("message.action.saved"), AppStrings.translate("dialog.message.title"), JOptionPane.INFORMATION_MESSAGE, Configuration.showCodeSavedMessage); + setDecompiledEditMode(false); + } catch (IOException ex) { + Logger.getLogger(ActionPanel.class.getName()).log(Level.SEVERE, "IOException during action compiling", ex); + } catch (ActionParseException ex) { + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } catch (CompilationException ex) { + View.showMessageDialog(this, AppStrings.translate("error.action.save").replace("%error%", ex.text).replace("%line%", Long.toString(ex.line)), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + + private ScriptExportMode getExportMode() { + ScriptExportMode exportMode = hexOnlyButton.isSelected() ? ScriptExportMode.HEX + : hexButton.isSelected() ? ScriptExportMode.PCODE_HEX + : constantsViewButton.isSelected() ? ScriptExportMode.CONSTANTS + : ScriptExportMode.PCODE; + return exportMode; + } + + @Override + public void updateSearchPos(ActionSearchResult item) { + TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel(); + TreePath tp = ttm.getTreePath(item.src); + mainPanel.tagTree.setSelectionPath(tp); + mainPanel.tagTree.scrollPathToVisible(tp); + decompiledEditor.setCaretPosition(0); + + View.execInEventDispatchLater(() -> { + searchPanel.showQuickFindDialog(decompiledEditor); + }); + } + + @Override + public boolean tryAutoSave() { + // todo: implement + return false; + } + + @Override + public boolean isEditing() { + return (saveButton.isVisible() && saveButton.isEnabled()) + || (saveDecompiledButton.isVisible() && saveDecompiledButton.isEnabled()); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/constantpool16.png b/src/com/jpexs/decompiler/flash/gui/graphics/constantpool16.png index 5c8a1ecb7..8016404d1 100644 Binary files a/src/com/jpexs/decompiler/flash/gui/graphics/constantpool16.png and b/src/com/jpexs/decompiler/flash/gui/graphics/constantpool16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/resolveconst16.png b/src/com/jpexs/decompiler/flash/gui/graphics/resolveconst16.png new file mode 100644 index 000000000..5deb5c2bf Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/resolveconst16.png differ