diff --git a/CHANGELOG.md b/CHANGELOG.md index 7605a292c..2dde4aee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,14 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added - Debugger - Debugged SWF file name in the session title +- Debugger - Remove watch +- Debugger - List of watches +- Debugger - Show flag of watch type (read/write) ### Fixed - [#2639] Export to FLA - missing sound streams - Debugger - Threading issues with multiple SWFs +- Debugger - Reading variables on 64bit flash players (like in browsers) ## [25.1.1] - 2026-02-19 ### Fixed diff --git a/lib/flashdebugger.jar b/lib/flashdebugger.jar index 9e5d8b116..b0477c274 100644 Binary files a/lib/flashdebugger.jar and b/lib/flashdebugger.jar differ diff --git a/libsrc/ffdec_lib/lib/flashdebugger.jar b/libsrc/ffdec_lib/lib/flashdebugger.jar index 9e5d8b116..b0477c274 100644 Binary files a/libsrc/ffdec_lib/lib/flashdebugger.jar and b/libsrc/ffdec_lib/lib/flashdebugger.jar differ diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swd b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swd new file mode 100644 index 000000000..87c1bc7e0 Binary files /dev/null and b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swd differ diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swf b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swf index da5f1d2ae..5f3f0af37 100644 Binary files a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swf and b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring.swf differ diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/DOMDocument.xml b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/DOMDocument.xml index 9c14de71b..4fa7a414d 100644 --- a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/DOMDocument.xml +++ b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/DOMDocument.xml @@ -6,6 +6,8 @@ + + @@ -43,6 +45,7 @@ stop();]]> var score = parseInt(_root.inpScore.text); score = score + 5; _root.inpScore.text = score; + trace("plus 5"); }]]> @@ -58,6 +61,7 @@ stop();]]> var score = parseInt(_root.inpScore.text); score = score * 2; _root.inpScore.text = score; + trace("multiply 2"); }]]> @@ -100,7 +104,7 @@ stop();]]> - + @@ -110,6 +114,48 @@ stop();]]> + + + + + + + + + Speed + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -122,6 +168,10 @@ stop();]]> + + + + @@ -138,6 +188,5 @@ stop();]]> - \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/LIBRARY/ButtonDown.xml b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/LIBRARY/ButtonDown.xml new file mode 100644 index 000000000..3d0048d06 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/LIBRARY/ButtonDown.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/LIBRARY/ButtonUp.xml b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/LIBRARY/ButtonUp.xml new file mode 100644 index 000000000..d2e081df9 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/LIBRARY/ButtonUp.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/META-INF/metadata.xml b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/META-INF/metadata.xml index 0ac4224b3..e4344c771 100644 --- a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/META-INF/metadata.xml +++ b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/META-INF/metadata.xml @@ -5,8 +5,8 @@ xmlns:xmp="http://ns.adobe.com/xap/1.0/"> Adobe Flash Professional CS6 - build 481 2026-02-16T10:31:42-08:00 - 2026-02-16T11:18:35-08:00 - 2026-02-16T11:18:35-08:00 + 2026-02-21T09:35:10-08:00 + 2026-02-21T09:35:10-08:00 @@ -17,7 +17,7 @@ xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"> xmp.did:7FE581FA660BF11197008EF33C376745 - xmp.iid:9BDF39F36B0BF11197008EF33C376745 + xmp.iid:377DE8AB4B0FF111A0D3B09FD2F43FD4 xmp.did:7FE581FA660BF11197008EF33C376745 @@ -39,6 +39,12 @@ 2026-02-16T10:31:42-08:00 Adobe Flash Professional CS6 - build 481 + + created + xmp.iid:377DE8AB4B0FF111A0D3B09FD2F43FD4 + 2026-02-16T10:31:42-08:00 + Adobe Flash Professional CS6 - build 481 + diff --git a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/bin/SymDepend.cache b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/bin/SymDepend.cache index ce963c485..62634e4c3 100644 Binary files a/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/bin/SymDepend.cache and b/libsrc/ffdec_lib/testdata/debug_game/as2_scoring/bin/SymDepend.cache differ diff --git a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring.swf b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring.swf index 4280165d6..d5bb3532b 100644 Binary files a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring.swf and b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring.swf differ diff --git a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/DOMDocument.xml b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/DOMDocument.xml index 2397a0329..c16c0879e 100644 --- a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/DOMDocument.xml +++ b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/DOMDocument.xml @@ -6,6 +6,8 @@ + + @@ -42,7 +44,8 @@ setTimeout(decreaseScore, delay); function onPlus5(e:MouseEvent):void { var score:int = getScore(); score = score + 5; - setScore(score); + setScore(score); + trace("plus 5"); } butPlus5.addEventListener(MouseEvent.CLICK, onPlus5); @@ -51,15 +54,32 @@ function onMultiply2(e:MouseEvent):void { var score:int = getScore(); score = score * 2; setScore(score); + trace("multiply 2"); } butMultiply2.addEventListener(MouseEvent.CLICK, onMultiply2); function onReset(e:MouseEvent):void { setScore(0); + trace("reset"); } -butReset.addEventListener(MouseEvent.CLICK, onReset);]]> +butReset.addEventListener(MouseEvent.CLICK, onReset); + +function onSpeedUp(e:MouseEvent): void { + delay = Math.round(delay / 1.1); + trace("speed up"); +} + +btnSpeedUp.addEventListener(MouseEvent.CLICK, onSpeedUp); + +function onSpeedDown(e:MouseEvent): void { + delay = Math.round(delay * 1.1); + trace("speed down"); +} + +btnSpeedDown.addEventListener(MouseEvent.CLICK, onSpeedDown); +]]> @@ -125,6 +145,35 @@ butReset.addEventListener(MouseEvent.CLICK, onReset);]]> + + + + + + + Speed + + + + + + + + + + + + + + + + + + + + + + @@ -134,6 +183,8 @@ butReset.addEventListener(MouseEvent.CLICK, onReset);]]> + + @@ -152,7 +203,5 @@ butReset.addEventListener(MouseEvent.CLICK, onReset);]]> - - \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/LIBRARY/ButtonDown.xml b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/LIBRARY/ButtonDown.xml new file mode 100644 index 000000000..3d0048d06 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/LIBRARY/ButtonDown.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/LIBRARY/ButtonUp.xml b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/LIBRARY/ButtonUp.xml new file mode 100644 index 000000000..d2e081df9 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/LIBRARY/ButtonUp.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/META-INF/metadata.xml b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/META-INF/metadata.xml index 445a6caa0..fe30b6721 100644 --- a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/META-INF/metadata.xml +++ b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/META-INF/metadata.xml @@ -5,8 +5,8 @@ xmlns:xmp="http://ns.adobe.com/xap/1.0/"> Adobe Flash Professional CS6 - build 481 2026-02-16T10:31:42-08:00 - 2026-02-16T11:18:44-08:00 - 2026-02-16T11:18:44-08:00 + 2026-02-21T09:45:46-08:00 + 2026-02-21T09:45:46-08:00 @@ -22,7 +22,7 @@ xmp.did:7FE581FA660BF11197008EF33C376745 xmp.did:EDF1CDD4690BF11197008EF33C376745 - xmp.iid:9DDF39F36B0BF11197008EF33C376745 + xmp.iid:3B7DE8AB4B0FF111A0D3B09FD2F43FD4 xmp.did:7FE581FA660BF11197008EF33C376745 @@ -45,6 +45,12 @@ 2026-02-16T10:31:42-08:00 Adobe Flash Professional CS6 - build 481 + + created + xmp.iid:3B7DE8AB4B0FF111A0D3B09FD2F43FD4 + 2026-02-16T10:31:42-08:00 + Adobe Flash Professional CS6 - build 481 + diff --git a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/bin/SymDepend.cache b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/bin/SymDepend.cache index dbecb1715..2debd5f6d 100644 Binary files a/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/bin/SymDepend.cache and b/libsrc/ffdec_lib/testdata/debug_game/as3_scoring/bin/SymDepend.cache differ diff --git a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java index 7c5b7703c..7a5e9b9c9 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebugPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/DebugPanel.java @@ -16,6 +16,8 @@ */ package com.jpexs.decompiler.flash.gui; +import com.jpexs.debugger.flash.Debugger; +import com.jpexs.debugger.flash.DebuggerCommands; import com.jpexs.debugger.flash.Variable; import com.jpexs.debugger.flash.messages.in.InConstantPool; import com.jpexs.debugger.flash.messages.in.InFrame; @@ -70,7 +72,9 @@ public class DebugPanel extends JPanel { private MyTreeTable debugRegistersTable; - private MyTreeTable debugLocalsTable; //JTable debugLocalsTable; + private MyTreeTable debugLocalsTable; + + private MyTreeTable debugWatchesTable; private MyTreeTable debugScopeTable; @@ -91,14 +95,14 @@ public class DebugPanel extends JPanel { private boolean loading = false; public ABCPanel.VariablesTableModel localsTable; - + private WeakReference currentSessionRef = null; private boolean as3; - + public static enum SelectedTab { - LOG, STACK, SCOPECHAIN, LOCALS, REGISTERS, CALLSTACK, CONSTANTPOOL + LOG, STACK, SCOPECHAIN, LOCALS, WATCHES, REGISTERS, CALLSTACK, CONSTANTPOOL } public synchronized boolean isLoading() { @@ -107,6 +111,7 @@ public class DebugPanel extends JPanel { public synchronized void setLoading(boolean loading) { this.loading = loading; + //varTabs.setVisible(!loading); } private SelectedTab selectedTab = null; @@ -157,15 +162,16 @@ public class DebugPanel extends JPanel { //ignore } if (wasEmpty) { - refresh(session); + refresh(); } } public DebugPanel(boolean as3) { super(new BorderLayout()); this.as3 = as3; - debugRegistersTable = new MyTreeTable(new ABCPanel.VariablesTableModel(as3, debugRegistersTable, new ArrayList<>()), false); - debugLocalsTable = new MyTreeTable(new ABCPanel.VariablesTableModel(as3, debugLocalsTable, new ArrayList<>()), false); + debugRegistersTable = new MyTreeTable(new ABCPanel.VariablesTableModel(null, as3, debugRegistersTable, new ArrayList<>(), new ArrayList<>()), false); + debugLocalsTable = new MyTreeTable(new ABCPanel.VariablesTableModel(null, as3, debugLocalsTable, new ArrayList<>(), new ArrayList<>()), false); + debugWatchesTable = new MyTreeTable(new ABCPanel.VariablesTableModel(null, as3, debugWatchesTable, new ArrayList<>(), new ArrayList<>()), false); MouseAdapter watchHandler = new MouseAdapter() { @@ -184,7 +190,7 @@ public class DebugPanel extends JPanel { } private void dopop(MouseEvent e) { - + if (currentSessionRef == null) { return; } @@ -192,11 +198,13 @@ public class DebugPanel extends JPanel { if (session == null) { return; } - - if (debugLocalsTable.getSelectedRow() == -1) { + + MyTreeTable src = (MyTreeTable) e.getSource(); + + if (src.getSelectedRow() == -1) { return; } - Object node = debugLocalsTable.getTree().getPathForRow(debugLocalsTable.getSelectedRow()).getLastPathComponent(); + Object node = src.getTree().getPathForRow(src.getSelectedRow()).getLastPathComponent(); Variable v; ABCPanel.VariableNode vn; if (node instanceof ABCPanel.VariableNode) { @@ -261,7 +269,7 @@ public class DebugPanel extends JPanel { if (igv == null) { return; } - Variable debugConnectionClass = igv.parent; + Variable debugConnectionClass = igv.parent; String dataStr = (String) session.callMethod(debugConnectionClass, "readCommaSeparatedFromByteArray", Arrays.asList(v)).variables.get(0).value; String[] parts = dataStr.split(","); byte[] data = new byte[parts.length]; @@ -356,35 +364,63 @@ public class DebugPanel extends JPanel { watchReadMenuItem.addActionListener((ActionEvent e1) -> { if (!Main.addWatch(session, v, watchParentId, true, false)) { ViewMessages.showMessageDialog(DebugPanel.this, AppStrings.translate("error.debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; } + refresh(); }); JMenuItem watchWriteMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.write")); watchWriteMenuItem.addActionListener((ActionEvent e1) -> { if (!Main.addWatch(session, v, watchParentId, false, true)) { ViewMessages.showMessageDialog(DebugPanel.this, AppStrings.translate("error.debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; } + refresh(); }); JMenuItem watchReadWriteMenuItem = new JMenuItem(AppStrings.translate("debug.watch.add.readwrite")); watchReadWriteMenuItem.addActionListener((ActionEvent e1) -> { if (!Main.addWatch(session, v, watchParentId, true, true)) { ViewMessages.showMessageDialog(DebugPanel.this, AppStrings.translate("error.debug.watch.add"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; } + refresh(); }); addWatchMenu.add(watchReadMenuItem); addWatchMenu.add(watchWriteMenuItem); addWatchMenu.add(watchReadWriteMenuItem); pm.add(addWatchMenu); + + JMenuItem removeWatcheMenuItem = new JMenuItem(AppStrings.translate("debug.watch.remove")); + removeWatcheMenuItem.addActionListener((ActionEvent e1) -> { + if (!Main.removeWatch(session, v, watchParentId)) { + ViewMessages.showMessageDialog(DebugPanel.this, AppStrings.translate("error.debug.watch.remove"), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); + return; + } + refresh(); + }); + if (session.getWatch(v.name, watchParentId) != null) { + pm.add(removeWatcheMenuItem); + } pm.show(e.getComponent(), e.getX(), e.getY()); } }; debugLocalsTable.addMouseListener(watchHandler); + debugWatchesTable.addMouseListener(watchHandler); //debugScopeTable.addMouseListener(watchHandler); - debugScopeTable = new MyTreeTable(new ABCPanel.VariablesTableModel(as3, debugScopeTable, new ArrayList<>()), false); + debugScopeTable = new MyTreeTable(new ABCPanel.VariablesTableModel(null, as3, debugScopeTable, new ArrayList<>(), new ArrayList<>()), false); - constantPoolTable = new JTable(); + constantPoolTable = new JTable(new DefaultTableModel(new Object[2][0], new Object[]{ + AppStrings.translate("constantpool.header.id"), + AppStrings.translate("constantpool.header.value") + }) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + }); traceLogTextarea = new JTextArea(); traceLogTextarea.setEditable(false); traceLogTextarea.setOpaque(false); @@ -408,7 +444,7 @@ public class DebugPanel extends JPanel { public void errorException(DebuggerSession session, String message, Variable thrownVar) { logAdd(session, "unhandled exception: " + message); selectedTab = tabTypes.get(tabTypes.size() - 1); - refresh(session); + refresh(); } }); @@ -420,19 +456,26 @@ public class DebugPanel extends JPanel { @Override public void disconnected(DebuggerSession session) { - refresh(session); + refresh(); + } + }); + + Main.getDebugHandler().addSelectionListener(new DebuggerHandler.SessionSelectionListener() { + @Override + public void sessionSelected(DebuggerSession newSession, int oldSessionId) { + refresh(); } }); Main.getDebugHandler().addFrameChangeListener(frameChangeListener = new DebuggerHandler.FrameChangeListener() { @Override public void frameChanged(DebuggerSession session) { - View.execInEventDispatchLater(new Runnable() { - @Override - public void run() { - refresh(session); - } - }); + /*DebuggerSession currentSession = Main.getCurrentDebugSession(); + if (session != currentSession) { + return; + } */ + + refresh(); } }); @@ -440,22 +483,18 @@ public class DebugPanel extends JPanel { @Override public void doContinue(DebuggerSession session) { - View.execInEventDispatch(new Runnable() { - @Override - public void run() { - refresh(Main.getCurrentDebugSession()); - } - }); + refresh(); } @Override public void breakAt(DebuggerSession session, String scriptName, int line, int classIndex, int traitIndex, int methodIndex) { - View.execInEventDispatch(new Runnable() { + /*View.execInEventDispatch(new Runnable() { @Override public void run() { refresh(Main.getCurrentDebugSession()); } - }); + });*/ + //reacts to frameChanged instead } }); @@ -479,7 +518,7 @@ public class DebugPanel extends JPanel { }); add(new HeaderLabel(AppStrings.translate("debugpanel.header")), BorderLayout.NORTH); - add(varTabs, BorderLayout.CENTER); + add(varTabs, BorderLayout.CENTER); } /* private void getVariableList() { @@ -509,190 +548,269 @@ public class DebugPanel extends JPanel { } return v.getMembers(this); }*/ - public void refresh(DebuggerSession session) { - - if (session != null && !session.isPaused()) { - return; + private final Object refreshLock = new Object(); + + public void refresh() { + DebuggerSession session = Main.getCurrentDebugSession(); + + if (session != null) { + SWF swf = session.getCurrentSwf(); + if (swf != null) { + if (swf.isAS3() != as3) { + return; + } + } } - + currentSessionRef = session == null ? null : new WeakReference<>(session); - View.execInEventDispatch(new Runnable() { + Runnable r = new Runnable() { @Override public void run() { - if (session != null && !session.isPaused()) { - return; - } - setLoading(true); - synchronized (DebugPanel.this) { + synchronized (refreshLock) { + try { + setLoading(true); - SelectedTab oldSel = selectedTab; - localsTable = null; - InFrame f = session == null ? null : session.getFrame(); - if (f != null) { - - - List locals = new ArrayList<>(); - - - Map placedObjects = session.getPlacedObjects(); - for (String poName : placedObjects.keySet()) { - String realName = poName; - if ("/".equals(realName)) { - realName = "_root"; - } else if (realName.startsWith("/")) { - continue; - } - InGetVariable igv = session.getVariable(0, realName, false, false); - if (igv != null) { - Variable placedVar = igv.parent; - if (placedVar != null) { - locals.add(placedVar); + int sessionId = -1; + + if (session != null) { + sessionId = session.getId(); + ABCPanel.VariableNode.stopLoading(sessionId); //stop any previous loading + } + + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Refreshing debug panel", new Object[]{sessionId}); + + localsTable = null; + InFrame f = session == null ? null : session.getFrame(); + + ABCPanel.VariablesTableModel registersTableModel = new ABCPanel.VariablesTableModel(session, as3, debugRegistersTable, new ArrayList<>(), new ArrayList<>()); + ABCPanel.VariablesTableModel localsTableModel = new ABCPanel.VariablesTableModel(session, as3, debugLocalsTable, new ArrayList<>(), new ArrayList<>()); + ABCPanel.VariablesTableModel scopeTableModel = new ABCPanel.VariablesTableModel(session, as3, debugScopeTable, new ArrayList<>(), new ArrayList<>()); + ABCPanel.VariablesTableModel watchesTableModel = new ABCPanel.VariablesTableModel(session, as3, debugWatchesTable, new ArrayList<>(), new ArrayList<>()); + ABCPanel.VariablesTableModel fRegistersTableModel; + ABCPanel.VariablesTableModel fLocalsTableModel; + ABCPanel.VariablesTableModel fScopeTableModel; + ABCPanel.VariablesTableModel fWatchesTableModel; + Object[][] fCpoolData; + + if (f != null) { + + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Debug panel - has frame", new Object[]{sessionId}); + + List locals = new ArrayList<>(); + + Map placedObjects = session.getPlacedObjects(); + for (String poName : placedObjects.keySet()) { + String realName = poName; + if ("/".equals(realName)) { + realName = "_root"; + } else if (realName.startsWith("/")) { + continue; + } + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Getting placedObject {1}", new Object[]{sessionId, realName}); + InGetVariable igv = session.getVariable(0, realName, false, false); + if (igv != null) { + Variable placedVar = igv.parent; + if (placedVar != null) { + locals.add(placedVar); + } + } else { + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Cannot get placedObject {1}", new Object[]{sessionId, realName}); } } + + locals.addAll(f.arguments); + locals.addAll(f.variables); + + localsTable = new ABCPanel.VariablesTableModel(session, as3, debugLocalsTable, locals, null); + + List watchedVars = new ArrayList<>(); + List watchedParentIds = new ArrayList<>(); + for (DebuggerCommands.Watch w : session.getWatches().values()) { + InGetVariable igv = session.getVariable(w.varId, w.varName, false, false); + if (igv != null) { + Variable wVar = igv.parent; + if (wVar != null) { + watchedVars.add(wVar); + watchedParentIds.add(w.varId); + } + } + } + + TreeModelListener refreshListener = new TreeModelListener() { + @Override + public void treeNodesChanged(TreeModelEvent e) { + synchronized (DebugPanel.this) { + if (loading) { + return; + } + } + session.refreshFrame(); + } + + @Override + public void treeNodesInserted(TreeModelEvent e) { + } + + @Override + public void treeNodesRemoved(TreeModelEvent e) { + + } + + @Override + public void treeStructureChanged(TreeModelEvent e) { + + } + }; + + registersTableModel = new ABCPanel.VariablesTableModel(session, as3, debugRegistersTable, f.registers, null); + localsTableModel = localsTable; + scopeTableModel = new ABCPanel.VariablesTableModel(session, as3, debugScopeTable, f.scopeChain, null); + watchesTableModel = new ABCPanel.VariablesTableModel(session, as3, debugWatchesTable, watchedVars, watchedParentIds); + + localsTableModel.addTreeModelListener(refreshListener); + scopeTableModel.addTreeModelListener(refreshListener); + watchesTableModel.addTreeModelListener(refreshListener); } - - safeSetTreeModel(debugRegistersTable, new ABCPanel.VariablesTableModel(as3, debugRegistersTable, f.registers)); - - locals.addAll(f.arguments); - locals.addAll(f.variables); - - localsTable = new ABCPanel.VariablesTableModel(as3, debugLocalsTable, locals); - safeSetTreeModel(debugLocalsTable, localsTable); - safeSetTreeModel(debugScopeTable, new ABCPanel.VariablesTableModel(as3, debugScopeTable, f.scopeChain)); - - /*TableModelListener refreshListener = new TableModelListener() { - @Override - public void tableChanged(TableModelEvent e) { - Main.getDebugHandler().refreshFrame(); - refresh(); - } - };*/ - TreeModelListener refreshListener = new TreeModelListener() { - @Override - public void treeNodesChanged(TreeModelEvent e) { - session.refreshFrame(); - refresh(session); + InConstantPool cpool = session == null ? null : session.getConstantPool(); + Object[][] cpoolData = new Object[0][2]; + if (cpool != null) { + cpoolData = new Object[cpool.vars.size()][2]; + for (int i = 0; i < cpool.vars.size(); i++) { + cpoolData[i][0] = cpool.ids.get(i); + cpoolData[i][1] = cpool.vars.get(i).value; } - - @Override - public void treeNodesInserted(TreeModelEvent e) { - session.refreshFrame(); - refresh(session); - } - - @Override - public void treeNodesRemoved(TreeModelEvent e) { - session.refreshFrame(); - refresh(session); - } - - @Override - public void treeStructureChanged(TreeModelEvent e) { - session.refreshFrame(); - refresh(session); - } - }; - debugLocalsTable.getTreeTableModel().addTreeModelListener(refreshListener); - debugScopeTable.getTreeTableModel().addTreeModelListener(refreshListener); - } else { - debugRegistersTable.setTreeModel(new ABCPanel.VariablesTableModel(as3, debugRegistersTable, new ArrayList<>())); - debugLocalsTable.setTreeModel(new ABCPanel.VariablesTableModel(as3, debugLocalsTable, new ArrayList<>())); - debugScopeTable.setTreeModel(new ABCPanel.VariablesTableModel(as3, debugScopeTable, new ArrayList<>())); - } - InConstantPool cpool = session == null ? null : session.getConstantPool(); - if (cpool != null) { - Object[][] data2 = new Object[cpool.vars.size()][2]; - for (int i = 0; i < cpool.vars.size(); i++) { - data2[i][0] = cpool.ids.get(i); - data2[i][1] = cpool.vars.get(i).value; } - constantPoolTable.setModel(new DefaultTableModel(data2, new Object[]{ - AppStrings.translate("constantpool.header.id"), - AppStrings.translate("constantpool.header.value") - }) { + + fRegistersTableModel = registersTableModel; + fLocalsTableModel = localsTableModel; + fScopeTableModel = scopeTableModel; + fWatchesTableModel = watchesTableModel; + fCpoolData = cpoolData; + + View.execInEventDispatch(new Runnable() { @Override - public boolean isCellEditable(int row, int column) { - return false; - } + public void run() { + try { + if (f != null) { + safeSetTreeModel(debugRegistersTable, fRegistersTableModel); + safeSetTreeModel(debugLocalsTable, fLocalsTableModel); + safeSetTreeModel(debugScopeTable, fScopeTableModel); + safeSetTreeModel(debugWatchesTable, fWatchesTableModel); + } else { + debugRegistersTable.setTreeModel(fRegistersTableModel); + debugLocalsTable.setTreeModel(fLocalsTableModel); + debugScopeTable.setTreeModel(fScopeTableModel); + debugWatchesTable.setTreeModel(fWatchesTableModel); + } - }); - } else { - constantPoolTable.setModel(new DefaultTableModel()); - } + constantPoolTable.setModel(new DefaultTableModel(fCpoolData, new Object[]{ + AppStrings.translate("constantpool.header.id"), + AppStrings.translate("constantpool.header.value") + }) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } - varTabs.removeAll(); - tabTypes.clear(); - JPanel pa; - if (debugRegistersTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.REGISTERS); - pa = new JPanel(new BorderLayout()); - pa.add(new FasterScrollPane(debugRegistersTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("variables.header.registers"), pa); - } - if (debugLocalsTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.LOCALS); + }); - pa = new JPanel(new BorderLayout()); - pa.add(new FasterScrollPane(debugLocalsTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("variables.header.locals"), pa); - } + varTabs.removeAll(); + tabTypes.clear(); + JPanel pa; + if (debugRegistersTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.REGISTERS); + pa = new JPanel(new BorderLayout()); + pa.add(new FasterScrollPane(debugRegistersTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.registers"), pa); + } + if (debugLocalsTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.LOCALS); - if (debugScopeTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.SCOPECHAIN); + pa = new JPanel(new BorderLayout()); + pa.add(new FasterScrollPane(debugLocalsTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.locals"), pa); + } - pa = new JPanel(new BorderLayout()); - pa.add(new FasterScrollPane(debugScopeTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("variables.header.scopeChain"), pa); - } + if (debugWatchesTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.WATCHES); - if (constantPoolTable.getRowCount() > 0) { - tabTypes.add(SelectedTab.CONSTANTPOOL); + pa = new JPanel(new BorderLayout()); + pa.add(new FasterScrollPane(debugWatchesTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.watches"), pa); + } - pa = new JPanel(new BorderLayout()); - pa.add(new FasterScrollPane(constantPoolTable), BorderLayout.CENTER); - varTabs.addTab(AppStrings.translate("constantpool.header"), pa); - } + if (debugScopeTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.SCOPECHAIN); - if (logLength > 0) { - tabTypes.add(SelectedTab.LOG); + pa = new JPanel(new BorderLayout()); + pa.add(new FasterScrollPane(debugScopeTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("variables.header.scopeChain"), pa); + } - pa = new JPanel(new BorderLayout()); - pa.add(new FasterScrollPane(traceLogTextarea), BorderLayout.CENTER); - JButton clearButton = new JButton(AppStrings.translate("debuglog.button.clear")); - clearButton.addActionListener(new ActionListener() { + if (constantPoolTable.getRowCount() > 0) { + tabTypes.add(SelectedTab.CONSTANTPOOL); + + pa = new JPanel(new BorderLayout()); + pa.add(new FasterScrollPane(constantPoolTable), BorderLayout.CENTER); + varTabs.addTab(AppStrings.translate("constantpool.header"), pa); + } + + if (logLength > 0) { + tabTypes.add(SelectedTab.LOG); + + pa = new JPanel(new BorderLayout()); + pa.add(new FasterScrollPane(traceLogTextarea), BorderLayout.CENTER); + JButton clearButton = new JButton(AppStrings.translate("debuglog.button.clear")); + clearButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + traceLogTextarea.setText(""); + logLength = 0; + refresh(); + } + }); + JPanel butPanel = new JPanel(new FlowLayout()); + butPanel.add(clearButton); + pa.add(butPanel, BorderLayout.SOUTH); + varTabs.addTab(AppStrings.translate("debuglog.header"), pa); + } + boolean newVisible = !tabTypes.isEmpty(); + if (newVisible != isVisible()) { + setVisible(newVisible); + } + + SelectedTab oldSel = selectedTab; + + if (!tabTypes.isEmpty()) { + if (oldSel != null && !tabTypes.contains(oldSel)) { + oldSel = null; + } + } + if (oldSel != null) { + selectedTab = oldSel; + varTabs.setSelectedIndex(tabTypes.indexOf(selectedTab)); + } + } catch (Throwable t) { + int sessionId = session == null ? -1 : session.getId(); + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session" + sessionId + ": Error refeshing debug panel in UI thread", t); + } + + setLoading(false); + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Refreshed debug panel", new Object[]{session == null ? -1 : session.getId()}); - @Override - public void actionPerformed(ActionEvent e) { - traceLogTextarea.setText(""); - logLength = 0; - refresh(session); } }); - JPanel butPanel = new JPanel(new FlowLayout()); - butPanel.add(clearButton); - pa.add(butPanel, BorderLayout.SOUTH); - varTabs.addTab(AppStrings.translate("debuglog.header"), pa); + } catch (Throwable t) { + int sessionId = session == null ? -1 : session.getId(); + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session" + sessionId + ": Error refeshing debug panel", t); } - boolean newVisible = !tabTypes.isEmpty(); - if (newVisible != isVisible()) { - setVisible(newVisible); - } - if (!tabTypes.isEmpty()) { - if (oldSel != null && !tabTypes.contains(oldSel)) { - oldSel = null; - } - } - if (oldSel != null) { - selectedTab = oldSel; - varTabs.setSelectedIndex(tabTypes.indexOf(selectedTab)); - } - setLoading(false); } - } - }); + }; + DebuggerHandler.getPool().submit(r); } public void dispose() { diff --git a/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java b/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java index 294e5662a..8b3508a6a 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java @@ -45,6 +45,7 @@ import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableModel; /** * @author JPEXS @@ -67,8 +68,23 @@ public class DebugStackPanel extends JPanel { private int[] traitIndices = new int[0]; private WeakReference currentSessionRef = null; + private DefaultTableModel getStackTableModel(Object[][] data) { + return new DefaultTableModel(data, new Object[]{ + AppStrings.translate("callStack.header.swf"), + AppStrings.translate("callStack.header.file"), + AppStrings.translate("callStack.header.line"), + AppStrings.translate("stack.header.item") + }) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + }; + } + public DebugStackPanel() { - stackTable = new JTable(); + stackTable = new JTable(getStackTableModel(new Object[0][4])); Main.getDebugHandler().addFrameChangeListener(new DebuggerHandler.FrameChangeListener() { @Override public void frameChanged(DebuggerSession session) { @@ -160,8 +176,10 @@ public class DebugStackPanel extends JPanel { sessionComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - if (sessionComboBoxCreating) { - return; + synchronized (DebugStackPanel.this) { + if (sessionComboBoxCreating) { + return; + } } SessionItem selection = (SessionItem) sessionComboBox.getSelectedItem(); if (selection != null) { @@ -174,12 +192,13 @@ public class DebugStackPanel extends JPanel { lastSessionComboBoxIndex = sessionComboBox.getSelectedIndex(); Main.getDebugHandler().setSelectedSessionId(session.getId()); + //session.refreshFrame(); View.execInEventDispatch(new Runnable() { @Override public void run() { refresh(session); } - }); + }); View.execInEventDispatchLater(new Runnable() { @Override public void run() { @@ -210,7 +229,7 @@ public class DebugStackPanel extends JPanel { SessionItem item = (SessionItem) value; if (item != null) { DebuggerSession session = Main.getDebugHandler().getSessionById(item.id); - if (!session.isPaused()) { + if (session == null || !session.isPaused()) { c.setForeground(Color.GRAY); c.setBackground(list.getBackground()); } else { @@ -279,8 +298,8 @@ public class DebugStackPanel extends JPanel { } } - public void clear() { - stackTable.setModel(new DefaultTableModel()); + public void clear() { + stackTable.setModel(getStackTableModel(new Object[0][4])); if (Main.getDebugHandler().getActiveSessions().isEmpty()) { active = false; } @@ -304,7 +323,10 @@ public class DebugStackPanel extends JPanel { model.addElement(new SessionItem(id)); j++; } - sessionComboBoxCreating = true; + synchronized (this) { + sessionComboBoxCreating = true; + } + if (itemIndex > -1) { final int fItemIndex = itemIndex; View.execInEventDispatchLater(new Runnable() { @@ -315,7 +337,9 @@ public class DebugStackPanel extends JPanel { sessionComboBox.setSelectedIndex(fItemIndex); } lastSessionComboBoxIndex = fItemIndex; - sessionComboBoxCreating = false; + synchronized (DebugStackPanel.this) { + sessionComboBoxCreating = false; + } } }); } @@ -378,19 +402,7 @@ public class DebugStackPanel extends JPanel { newTraitIndices[i] = newTraitIndex == null ? -1 : newTraitIndex; } - DefaultTableModel tm = new DefaultTableModel(data, new Object[]{ - AppStrings.translate("callStack.header.swf"), - AppStrings.translate("callStack.header.file"), - AppStrings.translate("callStack.header.line"), - AppStrings.translate("stack.header.item") - }) { - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - }; - stackTable.setModel(tm); + stackTable.setModel(getStackTableModel(data)); this.swfHashes = newSwfHashes; this.classIndices = newClassIndices; this.methodIndices = newMethodIndices; @@ -410,11 +422,12 @@ public class DebugStackPanel extends JPanel { } }; - if (stackTable.getColumnModel().getColumnCount() >= 3) { + stackTable.setDefaultRenderer(String.class, renderer); + /*if (stackTable.getColumnModel().getColumnCount() >= 3) { stackTable.getColumnModel().getColumn(0).setCellRenderer(renderer); stackTable.getColumnModel().getColumn(1).setCellRenderer(renderer); stackTable.getColumnModel().getColumn(2).setCellRenderer(renderer); - } + }*/ repaint(); } }); diff --git a/src/com/jpexs/decompiler/flash/gui/DebuggerHandler.java b/src/com/jpexs/decompiler/flash/gui/DebuggerHandler.java index 019ebfc81..9c2560f5c 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebuggerHandler.java +++ b/src/com/jpexs/decompiler/flash/gui/DebuggerHandler.java @@ -31,6 +31,10 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.WeakHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; /** * @author JPEXS @@ -46,6 +50,8 @@ public class DebuggerHandler implements DebugConnectionListener { private boolean terminating = false; private Map>> allBreakPoints = new WeakHashMap<>(); + + private static ExecutorService pool = null; public static class ActionScriptException extends Exception { @@ -263,6 +269,11 @@ public class DebuggerHandler implements DebugConnectionListener { synchronized (this) { terminating = false; } + + if (pool != null) { + pool.shutdownNow(); + pool = null; + } } public synchronized Map> getAllSessionsBreakPoints(SWF swf) { @@ -423,4 +434,20 @@ public class DebuggerHandler implements DebugConnectionListener { } return true; } + + + + public static synchronized ExecutorService getPool() { + if (pool == null) { + AtomicInteger counter = new AtomicInteger(1); + + ThreadFactory namedThreadFactory = runnable -> { + Thread t = new Thread(runnable); + t.setName("debugger-handler-thread-" + counter.getAndIncrement()); + return t; + }; + pool = Executors.newCachedThreadPool(namedThreadFactory); + } + return pool; + } } diff --git a/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java b/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java index 4df57d67c..5647bc689 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java +++ b/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui; import com.jpexs.debugger.flash.DebugMessageListener; +import com.jpexs.debugger.flash.Debugger; import com.jpexs.debugger.flash.DebuggerCommands; import com.jpexs.debugger.flash.DebuggerConnection; import com.jpexs.debugger.flash.Variable; @@ -34,6 +35,7 @@ import com.jpexs.debugger.flash.messages.in.InFrame; import com.jpexs.debugger.flash.messages.in.InGetSwf; import com.jpexs.debugger.flash.messages.in.InGetVariable; import com.jpexs.debugger.flash.messages.in.InNumScript; +import com.jpexs.debugger.flash.messages.in.InOption; import com.jpexs.debugger.flash.messages.in.InPlaceObject; import com.jpexs.debugger.flash.messages.in.InProcessTag; import com.jpexs.debugger.flash.messages.in.InScript; @@ -47,6 +49,7 @@ import com.jpexs.debugger.flash.messages.out.OutGetSwf; import com.jpexs.debugger.flash.messages.out.OutPlay; import com.jpexs.debugger.flash.messages.out.OutProcessedTag; import com.jpexs.debugger.flash.messages.out.OutRewind; +import com.jpexs.debugger.flash.messages.out.OutSetOption; import com.jpexs.debugger.flash.messages.out.OutSwfInfo; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; @@ -56,6 +59,7 @@ import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -136,14 +140,17 @@ public class DebuggerSession { private InSetBreakpoint inSetBreakpoint; private int id; - + private String title = ""; - + private static final int DEFAULT_TIMEOUT = 1000; + private List getSwfThreadList = Collections.synchronizedList(new ArrayList<>()); + private Map watches = new LinkedHashMap<>(); + public DebuggerSession(DebuggerHandler handler, DebuggerConnection con, Map>> breakpoints) { - id = con.getId(); + id = con.getId(); toAddBPointMap = breakpoints; this.handler = handler; @@ -157,10 +164,8 @@ public class DebuggerSession { @Override public void run() { Main.getMainFrame().getPanel().updateMenu(); - } + } }); - - //enlog(DebuggerConnection.class); //enlog(DebuggerCommands.class); @@ -291,6 +296,10 @@ public class DebuggerSession { commands.setGetterTimeout(1500); commands.setSetterTimeout(5000); + if (commands.playerConcurrency()) { + commands.debuggerSetConcurrency(); + } + commands.squelch(true); con.addMessageListener(new DebugMessageListener() { @@ -340,16 +349,16 @@ public class DebuggerSession { if (inGetSwf == null) { Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: Cannot read SWF", id); continue; - } + } String sha256 = Helper.byteArrayToHex(MessageDigest.getInstance("SHA-256").digest(inGetSwf.swfData)); Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: Received SWF hash = {1}", new Object[]{id, sha256}); - + String originalHash = Main.getHashFromMetadataFromSwfBytes(inGetSwf.swfData); if (originalHash != null) { Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: Received SWF original hash = {1}", new Object[]{id, originalHash}); sha256 = originalHash; } - + SWF debuggedSwf = Main.findOpenedSwfByHash(sha256); if (debuggedSwf == null) { con.disconnect(); @@ -379,9 +388,9 @@ public class DebuggerSession { } try { - + Set hashes = new LinkedHashSet<>(); - + for (int file : modulePaths.keySet()) { String path = modulePaths.get(file); if (!path.contains(":")) { @@ -391,22 +400,22 @@ public class DebuggerSession { String hash = path.substring(0, path.indexOf(":")); hashes.add(hash); } - for (String hash : hashes) { + for (String hash : hashes) { if (Main.findOpenedSwfByHash(hash) == null) { //This is probably SWF file instrumented by another software throw new IOException("SWF with hash " + hash + " not found"); } - } - + } + if (!debuggedSwfs.isEmpty() && modulesEmptyBefore) { if (title.isEmpty()) { title = debuggedSwfs.values().iterator().next().toString(); } if (con.isAS3) { - //Widelines - only AS3, it hangs in AS1/2 and SWD does not support UI32 lines - con.wideLines = commands.getOption("wide_line_player", "false").equals("true"); + //Widelines - only AS3, it hangs in AS1/2 and SWD does not support UI32 lines + con.wideLines = commands.playerIsWideLine(); if (con.wideLines) { - commands.setOption("wide_line_debugger", "on"); + commands.debuggerSetWideLine();; } } else { Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINER, "session{0}: End of connect - sending continue", id); @@ -461,7 +470,19 @@ public class DebuggerSession { con.addMessageListener(new DebugMessageListener() { @Override public void message(InPlaceObject t) { - placedObjects.put(t.path, t.objId); + //Sometimes the player sends _global variable with 4 byte PTR instead of 8 byte + byte[] globalVar = new byte[]{(byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x5f, (byte) 0x67, (byte) 0x6c, (byte) 0x6f, (byte) 0x62, (byte) 0x61, (byte) 0x6c, (byte) 0x00}; + String placeName = t.path; + long placeId = t.objId; + if (Arrays.equals(t.data, globalVar)) { + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINER, "session{0}: Received mangled placeobject _global", new Object[]{id}); + placeName = "_global"; + placeId = -2; + } else { + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINER, "session{0}: Received placeobject {1}", new Object[]{id, t.path}); + } + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINEST, "session{0}: Placeobject hex data: {1}", new Object[]{id, Helper.byteArrayToHex(t.data)}); + placedObjects.put(placeName, placeId); con.dropMessage(t); } }); @@ -474,10 +495,10 @@ public class DebuggerSession { synchronized (DebuggerSession.this) { paused = false; Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: continued", id); - } + } for (DebuggerHandler.BreakListener bl : handler.getBreakListeners()) { bl.doContinue(DebuggerSession.this); - } + } } }); @@ -491,14 +512,13 @@ public class DebuggerSession { @Override public void run() { Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: In break at start", id); - + synchronized (DebuggerSession.this) { if (!connected) { Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: Received break while not connected", id); return; } - - + paused = true; Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: paused", id); } @@ -543,7 +563,7 @@ public class DebuggerSession { sendBreakPoints(); } catch (IOException ex) { //ignore - Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: send breakpoints exception: {1}", new Object[] {id, ex.getMessage()}); + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "session{0}: send breakpoints exception: {1}", new Object[]{id, ex.getMessage()}); } synchronized (DebuggerSession.this) { @@ -574,14 +594,15 @@ public class DebuggerSession { } else { Main.startWork(AppStrings.translate("debug.session").replace("%id%", "" + id) + " - " + AppStrings.translate("work.breakat") + userBreakScriptName + ":" + message.line + " " + AppStrings.translate("debug.break.reason." + reason), null, true); } - depth = 0; - refreshFrame(); - + //If there is single one left paused, switch to it if (Main.getDebugHandler().getNumberOfPausedSessions() == 1) { Main.getDebugHandler().setSelectedSessionId(getId()); } - + + depth = 0; + refreshFrame(); + for (DebuggerHandler.BreakListener l : handler.getBreakListeners()) { l.breakAt(DebuggerSession.this, newBreakScriptName, message.line, moduleToClassIndex.containsKey(message.file) ? moduleToClassIndex.get(message.file) : -1, @@ -631,7 +652,7 @@ public class DebuggerSession { } catch (IOException ex) { Logger.getLogger(DebuggerSession.class.getName()).log(Level.SEVERE, null, ex); } - }*/ + }*/ con.addMessageListener(new DebugMessageListener() { @Override @@ -657,8 +678,6 @@ public class DebuggerSession { public int getId() { return id; } - - public boolean containsSwf(SWF swf) { return debuggedSwfs.containsValue(swf); @@ -705,9 +724,9 @@ public class DebuggerSession { return stackLines; } - public InGetVariable getVariable(long parentId, String varName, boolean children, boolean useGetter) { + public synchronized InGetVariable getVariable(long parentId, String varName, boolean children, boolean useGetter) { try { - return commands.getVariableWithTimeout(parentId, varName, useGetter, children, 100); + return commands.getVariableWithTimeout(parentId, varName, useGetter, children, DEFAULT_TIMEOUT); } catch (IOException ex) { return null; } @@ -740,7 +759,7 @@ public class DebuggerSession { } public void removeBreakPoint(SWF swf, String scriptName, int line) { - synchronized (this) { + synchronized (this) { if (isBreakpointInvalid(swf, scriptName, line)) { invalidBreakPointMap.get(swf).get(scriptName).remove(line); if (invalidBreakPointMap.get(swf).get(scriptName).isEmpty()) { @@ -772,10 +791,19 @@ public class DebuggerSession { private int watchTag = 1; - public synchronized com.jpexs.debugger.flash.DebuggerCommands.Watch addWatch(Variable v, long v_id, boolean watchRead, boolean watchWrite) { + public synchronized DebuggerCommands.Watch addWatch(String varName, long varId, boolean watchRead, boolean watchWrite) { + if (getWatch(varName, varId) != null) { + if (!removeWatch(varName, varId)) { + return null; + } + } int tag = watchTag++; try { - return commands.addWatch(v_id, v.name, (watchRead ? OutAddWatch2.FLAG_READ : 0) | (watchWrite ? OutAddWatch2.FLAG_WRITE : 0), tag); + com.jpexs.debugger.flash.DebuggerCommands.Watch w = commands.addWatch(varId, varName, (watchRead ? OutAddWatch2.FLAG_READ : 0) | (watchWrite ? OutAddWatch2.FLAG_WRITE : 0), tag); + if (w != null) { + watches.put("" + w.varId + ":" + w.varName, w); + } + return w; } catch (IOException ex) { return null; } @@ -953,8 +981,11 @@ public class DebuggerSession { } public void setDepth(int depth) { + boolean depthChanged = depth != this.depth; this.depth = depth; - refreshFrame(); + if (depthChanged) { + refreshFrame(); + } } public String moduleToString(int file) { @@ -991,20 +1022,42 @@ public class DebuggerSession { } public void refreshFrame() { - synchronized (this) { + synchronized (DebuggerSession.this) { if (!paused) { return; } - try { - frame = commands.getFrameWithTimeout(depth, 100); - pool = commands.getConstantPoolWithTimeout(0, 100); - } catch (IOException ex) { - //ignore + } + + DebuggerHandler.getPool().submit(new Runnable() { + @Override + public void run() { + synchronized (DebuggerSession.this) { + if (!paused) { + return; + } + } + try { + frame = commands.getFrameWithTimeout(depth, DEFAULT_TIMEOUT); + pool = commands.getConstantPoolWithTimeout(0, DEFAULT_TIMEOUT); + } catch (IOException ex) { + //ignore + } + + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Frame loaded", new Object[]{getId()}); + + if (frame == null) { + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: Frame is null!", new Object[]{getId()}); + } + + if (pool == null) { + Logger.getLogger(DebugPanel.class.getName()).log(Level.FINE, "session{0}: CPool is null!", new Object[]{getId()}); + } + + for (DebuggerHandler.FrameChangeListener l : handler.getFrameChangeListeners()) { + l.frameChanged(DebuggerSession.this); + } } - } - for (DebuggerHandler.FrameChangeListener l : handler.getFrameChangeListeners()) { - l.frameChanged(DebuggerSession.this); - } + }); } public synchronized InFrame getFrame() { @@ -1066,17 +1119,17 @@ public class DebuggerSession { } } } - + //If there is single one left paused, switch to it if (Main.getDebugHandler().getNumberOfPausedSessions() == 1) { - for (DebuggerSession session : Main.getDebugHandler().getActiveSessions().values()) { + for (DebuggerSession session : Main.getDebugHandler().getActiveSessions().values()) { if (session.isPaused()) { Main.getDebugHandler().setSelectedSessionId(session.getId()); break; } } } - + for (DebuggerHandler.ConnectionListener l : handler.getConnectionListeners()) { l.disconnected(DebuggerSession.this); } @@ -1282,5 +1335,45 @@ public class DebuggerSession { public String getTitle() { return title; - } + } + + public Map getWatches() { + return new LinkedHashMap<>(watches); + } + + public DebuggerCommands.Watch getWatch(String varName, long varId) { + return watches.get("" + varId + ":" + varName); + } + + public boolean removeWatch(String varName, long variId) { + String key = "" + variId + ":" + varName; + if (!watches.containsKey(key)) { + return false; + } + try { + if (commands.removeWatch(variId, varName)) { + watches.remove(key); + return true; + } + } catch (IOException ex) { + //false + } + return false; + } + + public SWF getCurrentSwf() { + InBreakAtExt info = getBreakInfo(); + if (info == null) { + return null; + } + //Object[][] data = new Object[info.files.size()][4]; + String moduleName = moduleToString(info.file); + if (moduleName.contains(":")) { + String swfHash = moduleName.substring(0, moduleName.indexOf(":")); + return Main.findOpenedSwfByHash(swfHash); + } else { + List debuggedSwfs = new ArrayList<>(getDebuggedSwfs().values()); + return debuggedSwfs.get(debuggedSwfs.size() - 1); + } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 964054641..09fb9fd9e 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -441,9 +441,13 @@ public class Main { } public static synchronized boolean addWatch(DebuggerSession session, Variable v, long v_id, boolean watchRead, boolean watchWrite) { - DebuggerCommands.Watch w = session.addWatch(v, v_id, watchRead, watchWrite); + DebuggerCommands.Watch w = session.addWatch(v.name, v_id, watchRead, watchWrite); return w != null; } + + public static synchronized boolean removeWatch(DebuggerSession session, Variable v, long v_id) { + return session.removeWatch(v.name, v_id); + } public static void runPlayer(String title, final String exePath, String file, String flashVars) { if (!new File(file).exists()) { @@ -1137,7 +1141,7 @@ public class Main { } public static void updateSession() { - DebuggerSession session = getCurrentDebugSession(); + /*DebuggerSession session = getCurrentDebugSession(); for (DebuggerHandler.FrameChangeListener l : getDebugHandler().getFrameChangeListeners()) { l.frameChanged(session); } @@ -1145,6 +1149,7 @@ public class Main { if (getDebugHandler().getNumberOfPausedSessions() > 0) { mainFrame.getPanel().showDebugStackFrame(); } + */ } public static void ensureMainFrame() { @@ -3197,7 +3202,7 @@ public class Main { @Override public void disconnected(DebuggerSession session) { - Main.updateSession(); + //Main.updateSession(); if (Main.mainFrame != null) { Main.mainFrame.getMenu().updateComponents(); } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 3ede7ad82..6c0973547 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -1636,7 +1636,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private void updateUi(final Openable openable) { View.checkAccess(); - Main.updateSession(); + //Main.updateSession(); SWF swf = null; if (openable instanceof SWF) { swf = (SWF) openable; @@ -5785,7 +5785,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se return; } - Main.updateSession(); + //Main.updateSession(); if (!(treeItem instanceof OpenableList)) { Openable openable = treeItem.getOpenable(); diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index 4201d1175..6e6bcc54b 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -16,10 +16,13 @@ */ package com.jpexs.decompiler.flash.gui.abc; +import com.jpexs.debugger.flash.DebuggerCommands; +import com.jpexs.debugger.flash.DebuggerMessage; import com.jpexs.debugger.flash.Variable; import com.jpexs.debugger.flash.VariableFlags; import com.jpexs.debugger.flash.VariableType; import com.jpexs.debugger.flash.messages.in.InGetVariable; +import com.jpexs.debugger.flash.messages.out.OutAddWatch2; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.ClassPath; @@ -266,7 +269,7 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener loaderList = Collections.synchronizedList(new ArrayList<>()); - private static final Object loaderLock = new Object(); - + private static Map sessionIdToVariableLoaderThread = new HashMap<>(); + private static Map> sessionIdToLoaderList = new HashMap<>(); + private static Map> sessionIdToLoadedVariableNode = new HashMap<>(); + private static Map sessionIdToLoaderLock = new HashMap<>(); + public List path = new ArrayList<>(); public Variable var; @@ -322,8 +326,25 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener childs; public List traits = new ArrayList<>(); + private DebuggerSession session; + private MyTreeTable treeTable; private boolean as3; - + + public static void stopLoading(int sessionId) { + if (!sessionIdToLoadedVariableNode.containsKey(sessionId)) { + return; + } + sessionIdToLoaderList.get(sessionId).clear(); + for (VariableNode node : sessionIdToLoadedVariableNode.get(sessionId)) { + synchronized (node) { + node.childs = new ArrayList<>(); + node.loading = false; + node.loaded = true; + } + } + sessionIdToLoadedVariableNode.get(sessionId).clear(); + } + @Override public int hashCode() { int hash = 3; @@ -361,6 +382,8 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener 0) { varInsideGetter = igv.parent; @@ -416,10 +444,10 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener 0) { continue; } - + if (!isTraits(igv.childs.get(i))) { Long parentObjectId = varToObjectId(varInsideGetter); - childs.add(new VariableNode(as3, path, level + 1, igv.childs.get(i), parentObjectId, curTrait)); + childs.add(new VariableNode(currentSession, treeTable, as3, path, level + 1, igv.childs.get(i), parentObjectId, curTrait)); } else { curTrait = igv.childs.get(i); traits.add(curTrait); @@ -427,62 +455,130 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener()); + sessionIdToLoaderLock.put(sessionId, new Object()); + Thread t = new Thread(r, "Variable loader for session " + sessionId); + sessionIdToVariableLoaderThread.put(sessionId, t); + t.setDaemon(true); + t.start(); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + //ignore + } } + + sessionIdToLoaderList.get(sessionId).add(new Runnable() { + @Override + public void run() { + synchronized (VariableNode.this) { + if (!loading) { + Logger.getLogger(VariableNode.class.getName()).log(Level.FINE, "session{0}: Already loaded {1}, skipping", new Object[]{session.getId(), var.name}); + return; + } + } + Logger.getLogger(VariableNode.class.getName()).log(Level.FINE, "session{0}: Loading children of {1}", new Object[]{session.getId(), var.name}); + + reloadChildren(); + + synchronized (VariableNode.this) { + if (!loading) { + Logger.getLogger(VariableNode.class.getName()).log(Level.FINE, "session{0}: Already loaded {1}, not firing", new Object[]{session.getId(), var.name}); + return; + } + loaded = true; + loading = false; + Logger.getLogger(VariableNode.class.getName()).log(Level.FINE, "session{0}: Children of {1} loaded", new Object[]{session.getId(), var.name}); + } + + DebuggerSession currentSession = Main.getCurrentDebugSession(); + if (currentSession == null || currentSession.getId() != sessionId) { + return; + } + + VariablesTableModel variableModel = (VariablesTableModel) treeTable.getTreeTableModel(); + Object[] changedPath = new Object[path.size()]; + for (int i = 0; i < path.size(); i++) { + changedPath[i] = path.get(i); + } + View.execInEventDispatch(new Runnable() { + @Override + public void run() { + Logger.getLogger(VariableNode.class.getName()).log(Level.FINE, "session{0}: Firing change of {1} - {2} childs", new Object[]{session.getId(), var.name, childs.size()}); + int[] childIds = new int[childs.size()]; + Object[] childObjs = new Object[childs.size()]; + + for (int i = 0; i < childIds.length; i++) { + childIds[i] = i; + childObjs[i] = childs.get(i); + } + + //variableModel.fireTreeStructureChanged(treeTable.getTree(), changedPath, new int[0], null); + variableModel.fireTreeNodesInserted(treeTable.getTree(), changedPath, childIds, childObjs); + treeTable.repaint(); + } + }); + } + }); + if (!sessionIdToLoadedVariableNode.containsKey(sessionId)) { + sessionIdToLoadedVariableNode.put(sessionId, new ArrayList<>()); + } + sessionIdToLoadedVariableNode.get(sessionId).add(this); + Object lock = sessionIdToLoaderLock.get(sessionId); + Logger.getLogger(VariableNode.class.getName()).log(Level.FINE, "session{0}: Scheduling loading children of {1}", new Object[]{session.getId(), var.name}); + synchronized (lock) { + lock.notify(); + } + /*reloadChildren(); + loaded = true;*/ } public VariableNode getChildAt(int index) { ensureLoaded(); + if (!loaded) { + return null; + } return childs.get(index); } public int getChildCount() { ensureLoaded(); + if (!loaded) { + return 0; + } return childs.size(); } - public VariableNode(boolean as3, List parentPath, int level, Variable var, Long parentObjectId, Variable trait) { + public VariableNode(DebuggerSession session, MyTreeTable treeTable, boolean as3, List parentPath, int level, Variable var, Long parentObjectId, Variable trait) { this.var = var; this.varInsideGetter = var; this.parentObjectId = parentObjectId; @@ -491,10 +587,12 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener parentPath, int level, Variable var, Long parentObjectId, Variable trait, List subvars) { + public VariableNode(DebuggerSession session, MyTreeTable treeTable, boolean as3, List parentPath, int level, Variable var, Long parentObjectId, Variable trait, List subvars) { this.var = var; this.varInsideGetter = var; this.parentObjectId = parentObjectId; @@ -510,6 +608,8 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener vars) { - this.ttable = ttable; + public VariablesTableModel(DebuggerSession session, boolean as3, MyTreeTable treeTable, List vars, List parentIds) { + this.treeTable = treeTable; List childs = new ArrayList<>(); for (int i = 0; i < vars.size(); i++) { - childs.add(new VariableNode(as3, new ArrayList<>(), 1, vars.get(i), 0L, null)); + childs.add(new VariableNode(session, treeTable, as3, new ArrayList<>(), 1, vars.get(i), parentIds == null ? 0L : parentIds.get(i), null)); } - root = new VariableNode(as3, new ArrayList<>(), 0, null, 0L, null, childs); + root = new VariableNode(session, treeTable, as3, new ArrayList<>(), 0, null, 0L, null, childs); + root.loaded = true; } @Override @@ -767,7 +868,26 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener 0) { + if (!flags.isEmpty()) { + flags += ", "; + } + + flags += "watch:read"; + } + if ((w.flags & OutAddWatch2.FLAG_WRITE) > 0) { + if (!flags.isEmpty()) { + flags += ", "; + } + + flags += "watch:write"; + } + } + return flags; case COLUMN_TYPE: String typeStr = val.getTypeAsStr(); if ("Object".equals(typeStr)) { @@ -782,7 +902,7 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener(), ci.abc.getSwf(), true); - - if (swfRef.getVal() == abc.getSwf()) { + + if (swfRef.getVal() == abc.getSwf()) { hilightScript(getOpenable(), scriptNamePrintable); return; } @@ -1131,12 +1251,12 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener foundStatic = new Reference<>(null); @@ -1212,7 +1332,7 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener