diff --git a/CHANGELOG.md b/CHANGELOG.md index d087e4e99..8d37b0a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. - Configurable tab size (formatting must be set to use tabs) - default matches indent size of 3 - [#2100] Copy/paste frames (same SWF only) - Updated portugese-brasil translation +- AS3 Debugging - export/import ByteArray variable data ### Fixed - [#2021], [#2000] Caret position in editors when using tabs and / or unicode @@ -17,6 +18,7 @@ All notable changes to this project will be documented in this file. - [#2122] `-header` command did not support negative integers for displayrect - AS3 direct editation - namespaces were initialized in class initializers - Debugging - do not invoke getter when there is none - avoid freezing +- Debugging - properly getting variable value through getter ## [20.0.0] - 2023-11-05 diff --git a/lib/flashdebugger.jar b/lib/flashdebugger.jar index 6cf1d97be..05e7145bd 100644 Binary files a/lib/flashdebugger.jar and b/lib/flashdebugger.jar differ diff --git a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java index d75aae478..04e676f63 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java @@ -20,8 +20,11 @@ import com.jpexs.debugger.flash.Variable; import com.jpexs.debugger.flash.messages.in.InBreakAtExt; import com.jpexs.debugger.flash.messages.in.InConstantPool; import com.jpexs.debugger.flash.messages.in.InFrame; +import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.gui.DebuggerHandler.BreakListener; +import static com.jpexs.decompiler.flash.gui.Main.getDefaultMessagesComponent; import com.jpexs.decompiler.flash.gui.abc.ABCPanel; +import com.jpexs.helpers.Helper; import de.hameister.treetable.MyTreeTable; import de.hameister.treetable.MyTreeTableModel; import java.awt.BorderLayout; @@ -31,9 +34,16 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.JButton; +import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; @@ -161,23 +171,60 @@ public class DebugPanel extends JPanel { ABCPanel.VariableNode vn; if (node instanceof ABCPanel.VariableNode) { vn = ((ABCPanel.VariableNode) node); - v = vn.var; + v = vn.varInsideGetter != null ? vn.varInsideGetter : vn.var; } else { return; } JPopupMenu pm = new JPopupMenu(); - //TODO!! - /*if (v.typeName != null && v.typeName.startsWith("flash.utils::ByteArray")) { - JMenu exportMenu = new JMenu("Export %name%".replace("%name%", v.name)); - JMenuItem exportByteArray = new JMenuItem("Export bytearray"); - exportByteArray.addActionListener((ActionEvent e1) -> { - Main.dumpBytes(v); + boolean isByteArray = false; + for (Variable t : vn.traits) { + if ("flash.utils::ByteArray".equals(t.name)) { + isByteArray = true; + break; + } + } + if (isByteArray) { + JMenu exportMenu = new JMenu(AppStrings.translate("debug.export").replace("%name%", v.name)); + JMenuItem exportByteArrayMenuItem = new JMenuItem(AppStrings.translate("debug.export.bytearray")); + exportByteArrayMenuItem.addActionListener((ActionEvent e1) -> { + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new File(Configuration.lastExportDir.get())); + if (fc.showSaveDialog(Main.getDefaultMessagesComponent()) == JFileChooser.APPROVE_OPTION) { + File file = Helper.fixDialogFile(fc.getSelectedFile()); + try (FileOutputStream fos = new FileOutputStream(file)) { + Main.debugExportByteArray(v, fos); + Configuration.lastExportDir.set(file.getParentFile().getAbsolutePath()); + } catch (IOException ex) { + Logger.getLogger(DebugPanel.class.getName()).log(Level.SEVERE, null, ex); + ViewMessages.showMessageDialog(getDefaultMessagesComponent(), AppStrings.translate("error.file.save") + ": " + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } }); - exportMenu.add(exportByteArray); + exportMenu.add(exportByteArrayMenuItem); pm.add(exportMenu); - }*/ + + JMenu importMenu = new JMenu(AppStrings.translate("debug.import").replace("%name%", v.name)); + JMenuItem importByteArrayMenuItem = new JMenuItem(AppStrings.translate("debug.import.bytearray")); + importByteArrayMenuItem.addActionListener((ActionEvent e1) -> { + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); + if (fc.showOpenDialog(Main.getDefaultMessagesComponent()) == JFileChooser.APPROVE_OPTION) { + File file = Helper.fixDialogFile(fc.getSelectedFile()); + Configuration.lastOpenDir.set(file.getParentFile().getAbsolutePath()); + try (FileInputStream fis = new FileInputStream(file)) { + Main.debugImportByteArray(v, fis); + } catch (IOException ex) { + Logger.getLogger(DebugPanel.class.getName()).log(Level.SEVERE, null, ex); + ViewMessages.showMessageDialog(getDefaultMessagesComponent(), AppStrings.translate("error.file.save") + ": " + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + } + } + }); + + importMenu.add(importByteArrayMenuItem); + pm.add(importMenu); + } long watchParentId = vn.parentObjectId; JMenu addWatchMenu = new JMenu(AppStrings.translate("debug.watch.add").replace("%name%", v.name)); diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 0a5da4050..9b80c90a8 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -291,25 +291,50 @@ public class Main { return runProcess != null && !runProcessDebug; } - /* - * FIXME! - */ - public static synchronized void dumpBytes(Variable v) { - InCallFunction icf; + + public static synchronized void debugExportByteArray(Variable v, OutputStream os) throws IOException { try { long objectId = 0L; if ((v.vType == VariableType.OBJECT || v.vType == VariableType.MOVIECLIP)) { objectId = (Long) v.value; } - Object oldPos = getDebugHandler().getVariable(objectId, "position", true, true).parent.value; + Object oldPos = getDebugHandler().getVariable(objectId, "position", false, true).parent.value; getDebugHandler().setVariable(objectId, "position", VariableType.NUMBER, 0); - icf = getDebugHandler().callFunction(false, "readUTF", v, new ArrayList<>()); - System.out.println("Result=" + icf.variables.get(0).value); + int length = (int)(double)(Double)getDebugHandler().getVariable(objectId, "length", false, true).parent.value; + + for (int i = 0; i < length; i++) { + int b = (int)(double)(Double)getDebugHandler().callFunction(false, "readByte", v, new ArrayList<>()).variables.get(0).value; + os.write(b); + } + getDebugHandler().setVariable(objectId, "position", VariableType.NUMBER, oldPos); + } catch (DebuggerHandler.ActionScriptException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public static synchronized void debugImportByteArray(Variable v, InputStream is) throws IOException { + try { + long objectId = 0L; + if ((v.vType == VariableType.OBJECT || v.vType == VariableType.MOVIECLIP)) { + objectId = (Long) v.value; + } + Double oldPos = (Double) getDebugHandler().getVariable(objectId, "position", false, true).parent.value; + getDebugHandler().setVariable(objectId, "length", VariableType.NUMBER, 0); + getDebugHandler().setVariable(objectId, "position", VariableType.NUMBER, 0); + + int length = 0; + int b; + while ((b = is.read()) > -1) { + getDebugHandler().callFunction(false, "writeByte", v, Arrays.asList((Double)(double) b)); + length++; + } + if (oldPos > length) { + oldPos = (Double) (double) length; + } getDebugHandler().setVariable(objectId, "position", VariableType.NUMBER, oldPos); } catch (DebuggerHandler.ActionScriptException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } - } public static synchronized boolean addWatch(Variable v, long v_id, boolean watchRead, boolean watchWrite) { diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index b62f76aa5..189327e05 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -289,6 +289,8 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener childs; + + public List traits = new ArrayList<>(); @Override public int hashCode() { @@ -347,17 +349,18 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener(); + traits = new ArrayList<>(); if ("".equals(var.name)) { return; } InGetVariable igv; - Long objectId = varToObjectId(varInsideGetter); + Long objectId = varToObjectId(var); - boolean useGetter = (var.flags & VariableFlags.HAS_GETTER) > 0; - - if (parentObjectId == 0 && objectId != 0) { + boolean useGetter = (var.flags & VariableFlags.IS_CONST) == 0; + + if (objectId != 0) { igv = Main.getDebugHandler().getVariable(objectId, "", true, useGetter); } else { igv = Main.getDebugHandler().getVariable(parentObjectId, var.name, true, useGetter); @@ -371,10 +374,11 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener