From 8645679ce916bdfad18e0fb7baa787bbec80b1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 15 Feb 2026 10:21:58 +0100 Subject: [PATCH] Debugging in the browser, improved 2 --- .../abc/avm2/parser/script/AbcIndexing.java | 6 +- .../decompiler/flash/gui/DebugStackPanel.java | 8 +- .../decompiler/flash/gui/DebuggerHandler.java | 13 +- .../decompiler/flash/gui/DebuggerSession.java | 381 ++++++++++-------- src/com/jpexs/decompiler/flash/gui/Main.java | 137 +++++-- .../decompiler/flash/gui/MainFrameRibbon.java | 10 +- .../decompiler/flash/gui/abc/ABCPanel.java | 26 +- .../gui/editor/DebuggableEditorPane.java | 4 +- .../flash/gui/locales/MainFrame.properties | 2 + .../flash/gui/tagtree/TagTreeContextMenu.java | 15 +- 10 files changed, 363 insertions(+), 239 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java index fdc7bd05e..b636b96e2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AbcIndexing.java @@ -583,7 +583,8 @@ public final class AbcIndexing { */ public void rebuildPkgToObjectsNameMap() { pkgToObjectsName.clear(); - for (ClassDef cd : classes.keySet()) { + Set cs = new LinkedHashSet<>(classes.keySet()); + for (ClassDef cd : cs) { if (!(cd.type instanceof TypeItem)) { continue; } @@ -592,7 +593,8 @@ public final class AbcIndexing { } pkgToObjectsName.get(cd.pkg).add(((TypeItem) cd.type).fullTypeName.getLast()); } - for (PropertyNsDef nsdef : scriptProperties.keySet()) { + Set nss = new LinkedHashSet<>(scriptProperties.keySet()); + for (PropertyNsDef nsdef : nss) { if (!pkgToObjectsName.containsKey(nsdef.ns)) { pkgToObjectsName.put(nsdef.ns, new LinkedHashSet<>()); } diff --git a/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java b/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java index f1024602c..894b4d64b 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/DebugStackPanel.java @@ -194,9 +194,11 @@ public class DebugStackPanel extends JPanel { } }; - stackTable.getColumnModel().getColumn(0).setCellRenderer(renderer); - stackTable.getColumnModel().getColumn(1).setCellRenderer(renderer); - stackTable.getColumnModel().getColumn(2).setCellRenderer(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 fd9ac4972..2911c70f5 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebuggerHandler.java +++ b/src/com/jpexs/decompiler/flash/gui/DebuggerHandler.java @@ -222,14 +222,21 @@ public class DebuggerHandler implements DebugConnectionListener { return count; } - public synchronized void terminateAllSessions() { - terminating = true; + public void terminateAllSessions() { + synchronized (this) { + if (terminating) { + return; + } + terminating = true; + } for (DebuggerSession session : sessions) { if (session.isConnected()) { session.disconnect(); } } - terminating = false; + synchronized (this) { + terminating = false; + } } public synchronized Map> getAllSessionsBreakPoints(SWF swf) { diff --git a/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java b/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java index 58c58d460..9f9fa44b2 100644 --- a/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java +++ b/src/com/jpexs/decompiler/flash/gui/DebuggerSession.java @@ -52,10 +52,12 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.Helper; +import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -63,6 +65,8 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import java.util.TreeSet; import java.util.WeakHashMap; import java.util.logging.Level; @@ -77,8 +81,6 @@ import java.util.regex.Pattern; public class DebuggerSession { private boolean connected = false; - - private boolean inited = false; private DebuggerCommands commands = null; @@ -117,7 +119,7 @@ public class DebuggerSession { private List stackLines = new ArrayList<>(); - private List debuggedSwfs = new ArrayList<>(); + private Map debuggedSwfs = new LinkedHashMap<>(); private Map placedObjects = new LinkedHashMap<>(); @@ -132,17 +134,19 @@ public class DebuggerSession { private InBreakReason breakReason; private DebuggerHandler handler; - + private InSetBreakpoint inSetBreakpoint; - + private int id; - + private static int lastId = 0; + private List getSwfThreadList = Collections.synchronizedList(new ArrayList<>()); + public DebuggerSession(DebuggerHandler handler, DebuggerConnection con, Map>> breakpoints) { lastId++; id = lastId; - + toAddBPointMap = breakpoints; this.handler = handler; Main.startWork(AppStrings.translate("work.debugging"), null); @@ -226,7 +230,7 @@ public class DebuggerSession { Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "Received script added - index {0} name: {1}", new Object[]{file, name}); - String swfHash = "main"; + String swfHash = "unknown"; Matcher m; if ((m = patAS3.matcher(name)).matches()) { String clsNameWithSuffix = m.group(3).replace("{{semicolon}}", ";"); @@ -256,7 +260,11 @@ public class DebuggerSession { modulePaths.put(file, name); scriptToModule.put(name, file); moduleToSwfIndex.put(file, swfIndex); - /*if (swfIndex > debuggedSwfs.size() - 1) { + /*if (!"main".equals(swfHash)) { + SWF swf = Main.getSwfByHash(swfHash); + debuggedSwfs.put(swfIndex, swf); + }*/ + /*if (swfIndex > debuggedSwfs.size() - 1) { SWF swf = Main.getSwfByHash(swfHash); if (swf == null) { Logger.getLogger(DebuggerSession.class.getName()).log(Level.WARNING, "SWF with hash {0} not found", swfHash); @@ -329,101 +337,116 @@ public class DebuggerSession { }); }*/ con.dropMessage(t); - View.execInEventDispatch(new Runnable() { + //View.execInEventDispatch( + Runnable r = new Runnable() { @Override public void run() { - for (InSwfInfo.SwfInfo s : t.swfInfos) { - try { - /*try { + try { + for (InSwfInfo.SwfInfo s : t.swfInfos) { + try { + /*try { con.sendMessageWithTimeout(new OutGetSwf(con, (int) s.index), InGetSwf.class); con.sendMessageWithTimeout(new OutGetSwd(con, (int) s.index), InGetSwd.class); } catch (IOException ex) { //ignore - }*/ - InGetSwf inGetSwf = con.sendMessageWithTimeout(new OutGetSwf(con, (int) s.index), InGetSwf.class); - Logger.getLogger(DebuggerSession.class.getName()).fine("Received SWF"); - Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "Received SWF.URL = {0}", s.url); - - String sha256 = Helper.byteArrayToHex(MessageDigest.getInstance("SHA-256").digest(inGetSwf.swfData)); + }*/ + /*if (s.scriptCount == 0) { + continue; + }*/ + + InGetSwf inGetSwf = con.sendMessage(new OutGetSwf(con, (int) s.index), InGetSwf.class); + Logger.getLogger(DebuggerSession.class.getName()).fine("Received SWF"); + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "Received SWF.URL = {0}", s.url); + if (inGetSwf == null) { + Logger.getLogger(DebuggerSession.class.getName()).fine("Cannot read SWF"); + continue; + } + String sha256 = Helper.byteArrayToHex(MessageDigest.getInstance("SHA-256").digest(inGetSwf.swfData)); + + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "Received SWF hash = {0}", sha256); + SWF debuggedSwf = Main.findOpenedSwfByHash(sha256); + if (debuggedSwf == null) { + con.disconnect(); + synchronized (DebuggerSession.this) { + paused = false; + connected = false; + } + disconnected(); + } else { + debuggedSwfs.put((int) s.index, debuggedSwf); + if (debuggedSwfs.size() == 1) { //it's the first one + con.isAS3 = debuggedSwf.isAS3(); + } + } + } catch (IOException | NoSuchAlgorithmException ex) { + //ignore - SWF debuggedSwf = Main.findOpenedSwfByHash(sha256); - if (debuggedSwf == null) { con.disconnect(); synchronized (DebuggerSession.this) { paused = false; connected = false; } disconnected(); - } else { - debuggedSwfs.add(debuggedSwf); - if (debuggedSwfs.size() == 1) { //it's the first one - con.isAS3 = debuggedSwf.isAS3(); - } - } - } catch (IOException | NoSuchAlgorithmException ex) { - //ignore + return; + } + } + try { + + if (con.isAS3) { + //FIXME + //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"); + if (con.wideLines) { + commands.setOption("wide_line_debugger", "on"); + }*/ + } + if (!con.isAS3) { + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINER, "End of connect - sending continue"); + con.writeMessage(new OutRewind(con)); + con.writeMessage(new OutPlay(con)); + commands.sendContinue(); + } + + synchronized (DebuggerSession.this) { + for (int i = 0; i < inSetBreakpoint.files.size(); i++) { + String sname = moduleNames.get(inSetBreakpoint.files.get(i)); + int swfIndex = moduleToSwfIndex.get(inSetBreakpoint.files.get(i)); + SWF debuggedSwf = debuggedSwfs.get(swfIndex); + if (!confirmedPointMap.containsKey(debuggedSwf)) { + confirmedPointMap.put(debuggedSwf, new HashMap<>()); + } + if (!confirmedPointMap.get(debuggedSwf).containsKey(sname)) { + confirmedPointMap.get(debuggedSwf).put(sname, new TreeSet<>()); + } + if (toAddBPointMap.containsKey(debuggedSwf)) { + if (toAddBPointMap.get(debuggedSwf).containsKey(sname)) { + toAddBPointMap.get(debuggedSwf).get(sname).remove(inSetBreakpoint.lines.get(i)); + if (toAddBPointMap.get(debuggedSwf).get(sname).isEmpty()) { + toAddBPointMap.get(debuggedSwf).remove(sname); + } + } + } + confirmedPointMap.get(debuggedSwf).get(sname).add(inSetBreakpoint.lines.get(i)); + Logger.getLogger(DebuggerSession.class.getName()).log(Level.INFO, "Breakpoint {0}:{1} submitted successfully", new Object[]{sname, inSetBreakpoint.lines.get(i)}); + } + } + } catch (IOException ex) { con.disconnect(); synchronized (DebuggerSession.this) { paused = false; connected = false; } disconnected(); - return; } - } - - try { - - 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"); - if (con.wideLines) { - commands.setOption("wide_line_debugger", "on"); - } - } else { - Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINER, "End of connect - sending continue"); - con.writeMessage(new OutRewind(con)); - con.writeMessage(new OutPlay(con)); - commands.sendContinue(); - } - - synchronized (DebuggerSession.this) { - for (int i = 0; i < inSetBreakpoint.files.size(); i++) { - String sname = moduleNames.get(inSetBreakpoint.files.get(i)); - int swfIndex = moduleToSwfIndex.get(inSetBreakpoint.files.get(i)); - SWF debuggedSwf = debuggedSwfs.get(swfIndex); - if (!confirmedPointMap.containsKey(debuggedSwf)) { - confirmedPointMap.put(debuggedSwf, new HashMap<>()); - } - if (!confirmedPointMap.get(debuggedSwf).containsKey(sname)) { - confirmedPointMap.get(debuggedSwf).put(sname, new TreeSet<>()); - } - if (toAddBPointMap.containsKey(debuggedSwf)) { - if (toAddBPointMap.get(debuggedSwf).containsKey(sname)) { - toAddBPointMap.get(debuggedSwf).get(sname).remove(inSetBreakpoint.lines.get(i)); - if (toAddBPointMap.get(debuggedSwf).get(sname).isEmpty()) { - toAddBPointMap.get(debuggedSwf).remove(sname); - } - } - } - confirmedPointMap.get(debuggedSwf).get(sname).add(inSetBreakpoint.lines.get(i)); - Logger.getLogger(DebuggerSession.class.getName()).log(Level.INFO, "Breakpoint {0}:{1} submitted successfully", new Object[]{sname, inSetBreakpoint.lines.get(i)}); - } - } - - inited = true; - handler.sessionInited(DebuggerSession.this); - } catch (IOException ex) { - con.disconnect(); - synchronized (DebuggerSession.this) { - paused = false; - connected = false; - } - disconnected(); + } finally { + getSwfThreadList.remove(Thread.currentThread()); } } - }); + }; + Thread thread = new Thread(r, "Debugger session " + this.toString() + " - Get SWF"); + thread.start(); + getSwfThreadList.add(thread); } }); @@ -435,7 +458,7 @@ public class DebuggerSession { } }); - inSetBreakpoint = con.getMessage(InSetBreakpoint.class); + inSetBreakpoint = con.getMessage(InSetBreakpoint.class); con.addMessageListener(new DebugMessageListener() { @Override @@ -453,88 +476,123 @@ public class DebuggerSession { con.addMessageListener(new DebugMessageListener() { @Override public void message(InBreakAt message) { - synchronized (DebuggerSession.this) { - paused = true; - Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "paused"); - - } - - try { - //ignore single InSetBreakpoint, otherwise setting breakpoints later won't work - con.getMessage(InSetBreakpoint.class, DebuggerConnection.PREF_RESPONSE_TIMEOUT); - - breakInfo = con.getMessage(InBreakAtExt.class, DebuggerConnection.PREF_RESPONSE_TIMEOUT); - breakReason = con.sendMessageWithTimeout(new OutGetBreakReason(con), InBreakReason.class); - - int reasonInt = breakReason == null ? 0 : breakReason.reason; - - String newBreakScriptName = "unknown"; - String userBreakScriptName = "unknown"; - if (modulePaths.containsKey(message.file)) { - newBreakScriptName = modulePaths.get(message.file); - userBreakScriptName = newBreakScriptName; - if (newBreakScriptName.contains(":")) { - //String swfHash = newBreakScriptName.substring(0, newBreakScriptName.indexOf(":")); - //SWF swf = Main.getSwfByHash(swfHash); - SWF swf = debuggedSwfs.get(moduleToSwfIndex.get(message.file)); - - userBreakScriptName = swf.toString() + ": " + newBreakScriptName.substring(newBreakScriptName.indexOf(":") + 1); + Runnable r = new Runnable() { + @Override + public void run() { + synchronized (DebuggerSession.this) { + paused = true; + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "paused"); } - } else if (reasonInt != InBreakReason.REASON_SCRIPT_LOADED) { - Logger.getLogger(DebuggerCommands.class.getName()).log(Level.SEVERE, "Invalid file: {0}", message.file); - //return; - } - final String[] reasonNames = new String[]{"unknown", "breakpoint", "watch", "fault", "stopRequest", "step", "halt", "scriptLoaded"}; - String reason = reasonInt < reasonNames.length ? reasonNames[reasonInt] : reasonNames[0]; + try { + //ignore single InSetBreakpoint, otherwise setting breakpoints later won't work + con.getMessage(InSetBreakpoint.class, DebuggerConnection.PREF_RESPONSE_TIMEOUT); - Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "break at {0}:{1}, reason: {2}", new Object[]{newBreakScriptName, message.line, reason}); + breakInfo = con.getMessage(InBreakAtExt.class, DebuggerConnection.PREF_RESPONSE_TIMEOUT); + breakReason = con.sendMessageWithTimeout(new OutGetBreakReason(con), InBreakReason.class); - try { - sendBreakPoints(); - } catch (IOException ex) { - //ignore - Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "send breakpoints exception: " + ex.getMessage()); - } + int reasonInt = breakReason == null ? 0 : breakReason.reason; - synchronized (DebuggerSession.this) { - breakScriptName = newBreakScriptName; - breakIp = message.line; - - if (breakInfo != null) { - List files = new ArrayList<>(); - for (int i = 0; i < breakInfo.files.size(); i++) { - files.add(DebuggerSession.this.moduleToString(breakInfo.files.get(i))); + String newBreakScriptName = "unknown"; + String userBreakScriptName = "unknown"; + String userSwfName = "unknown"; + if (moduleToSwfIndex.containsKey(message.file)) { + SWF swf = debuggedSwfs.get(moduleToSwfIndex.get(message.file)); + userSwfName = swf.toString(); + } else if(!debuggedSwfs.isEmpty()) { + userSwfName = debuggedSwfs.get(debuggedSwfs.size() - 1).toString(); } - stackScriptNames = files; - List lines = new ArrayList<>(breakInfo.lines); - stackLines = lines; + + if (modulePaths.containsKey(message.file)) { + newBreakScriptName = modulePaths.get(message.file); + userBreakScriptName = newBreakScriptName; + if (newBreakScriptName.contains(":")) { + userBreakScriptName = userSwfName + ": " + newBreakScriptName.substring(newBreakScriptName.indexOf(":") + 1); + } + } else if (reasonInt != InBreakReason.REASON_SCRIPT_LOADED) { + Logger.getLogger(DebuggerCommands.class.getName()).log(Level.SEVERE, "Invalid file: {0}", message.file); + //return; + } + + final String[] reasonNames = new String[]{"unknown", "breakpoint", "watch", "fault", "stopRequest", "step", "halt", "scriptLoaded"}; + String reason = reasonInt < reasonNames.length ? reasonNames[reasonInt] : reasonNames[0]; + + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "break at {0}:{1}, reason: {2}", new Object[]{newBreakScriptName, message.line, reason}); + + try { + sendBreakPoints(); + } catch (IOException ex) { + //ignore + Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINE, "send breakpoints exception: " + ex.getMessage()); + } + + synchronized (DebuggerSession.this) { + breakScriptName = newBreakScriptName; + breakIp = message.line; + + if (breakInfo != null) { + List files = new ArrayList<>(); + for (int i = 0; i < breakInfo.files.size(); i++) { + files.add(DebuggerSession.this.moduleToString(breakInfo.files.get(i))); + } + stackScriptNames = files; + List lines = new ArrayList<>(breakInfo.lines); + stackLines = lines; + } + } + + if (reasonInt == InBreakReason.REASON_SCRIPT_LOADED) { + if (!Configuration.debugHalt.get()) { + try { + commands.sendContinue(); + } catch (IOException iex) { + //ignore + } + return; + } + Main.startWork(AppStrings.translate("work.halted.with").replace("%file%", userSwfName), null, true); + } else { + Main.startWork(AppStrings.translate("work.breakat") + userBreakScriptName + ":" + message.line + " " + AppStrings.translate("debug.break.reason." + reason), null, true); + } + 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, + moduleToTraitIndex.containsKey(message.file) ? moduleToTraitIndex.get(message.file) : -1, + moduleToMethodIndex.containsKey(message.file) ? moduleToMethodIndex.get(message.file) : -1 + ); + } + } catch (IOException iex) { + //ignore ?? } } + }; - if (reasonInt == InBreakReason.REASON_SCRIPT_LOADED) { - if (!Configuration.debugHalt.get()) { - commands.sendContinue(); - return; + if (getSwfThreadList.isEmpty()) { + r.run(); + } else { + //Wait till all SWFs are loaded + int delay = 500; //check each 500ms + Timer tim = new Timer(); + tim.schedule(new TimerTask() { + @Override + public void run() { + TimerTask that = this; + if (getSwfThreadList.isEmpty()) { + r.run(); + } else { + Timer tim = new Timer(); + tim.schedule(new TimerTask() { + @Override + public void run() { + that.run(); + } + }, delay); + } } - Main.startWork(AppStrings.translate("work.halted"), null, true); - } else { - Main.startWork(AppStrings.translate("work.breakat") + userBreakScriptName + ":" + message.line + " " + AppStrings.translate("debug.break.reason." + reason), null, true); - } - 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, - moduleToTraitIndex.containsKey(message.file) ? moduleToTraitIndex.get(message.file) : -1, - moduleToMethodIndex.containsKey(message.file) ? moduleToMethodIndex.get(message.file) : -1 - ); - } - - } catch (IOException ex) { - //ignore + }, delay); } - } }); @@ -573,9 +631,9 @@ public class DebuggerSession { disconnected(); } } - + public boolean containsSwf(SWF swf) { - return debuggedSwfs.contains(swf); + return debuggedSwfs.containsValue(swf); } public void setMainDebuggedSwf(SWF debuggedSwf) { @@ -583,7 +641,7 @@ public class DebuggerSession { //debuggedSwfs.add(debuggedSwf); } - public List getDebuggedSwfs() { + public Map getDebuggedSwfs() { return debuggedSwfs; } @@ -948,7 +1006,7 @@ public class DebuggerSession { connected = false; commands = null; synchronized (this) { - for (SWF debuggedSwf : debuggedSwfs) { + for (SWF debuggedSwf : debuggedSwfs.values()) { if (confirmedPointMap.containsKey(debuggedSwf)) { for (String scriptName : confirmedPointMap.get(debuggedSwf).keySet()) { if (!toAddBPointMap.containsKey(debuggedSwf)) { @@ -991,6 +1049,11 @@ public class DebuggerSession { if (commands != null) { commands.disconnect(); } + + List threads = new ArrayList<>(getSwfThreadList); + for (Thread t : threads) { + t.interrupt(); + } disconnected(); } @@ -1017,7 +1080,7 @@ public class DebuggerSession { continue; } SWF debuggedSwf = debuggedSwfs.get(swfIndex); - String hash = swfIndex == 0 ? "main" : Main.getSwfHash(debuggedSwf); //?? + String hash = Main.getSwfHash(debuggedSwf); //?? Logger.getLogger(DebuggerSession.class.getName()).log(Level.FINEST, "sending breakpoints of {0}", hash); @@ -1172,5 +1235,5 @@ public class DebuggerSession { @Override public String toString() { return "[s:" + id + "]"; - } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index 7752523bf..d520f30d1 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -97,6 +97,7 @@ import java.awt.geom.AffineTransform; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -131,6 +132,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -140,6 +142,7 @@ import java.util.ResourceBundle; import java.util.Set; import java.util.Timer; import java.util.TimerTask; +import java.util.WeakHashMap; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -235,13 +238,15 @@ public class Main { private static SWF debuggedSWF = null; - private static String preparedHashSha256 = null; + //private static String preparedHashSha256 = null; private static SwfPreparation runningPreparation = null; private static boolean loggingFileLoaded = false; private static boolean debugListening = false; + + private static Map> hash2PreparedHash = new HashMap<>(); public static SWF getDebuggedSWF() { return debuggedSWF; @@ -260,18 +265,40 @@ public class Main { public static SWF findOpenedSwfByHash(String hashSha256) { Logger.getLogger(Main.class.getName()).log(Level.FINE, "Searching for hash {0}...", hashSha256); + for (String origKey : hash2PreparedHash.keySet()) { + if (hash2PreparedHash.get(origKey).contains(hashSha256)) { + hashSha256 = origKey; + break; + } + } + + if (hashSha256.startsWith("loaded_")) { + hashSha256 = hashSha256.substring("loaded_".length()); + } + if (hashSha256.startsWith("imported_")) { + hashSha256 = hashSha256.substring("loaded_".length()); + } + for (SWF swf : mainFrame.getPanel().getAllSwfs()) { Logger.getLogger(Main.class.getName()).log(Level.FINE, "Checking {0} with hash {1}", new Object[]{swf.toString(), swf.getHashSha256()}); if (Objects.equals(hashSha256, swf.getHashSha256())) { Logger.getLogger(Main.class.getName()).log(Level.FINE, "RECEIVED SWF FOUND"); return swf; } + String t = swf.getTitleOrShortFileName(); + if (t == null) { + t = ""; + } + if (t.endsWith(":" + hashSha256)) { + Logger.getLogger(Main.class.getName()).log(Level.FINE, "RECEIVED SUB SWF FOUND"); + return swf; + } } - if (preparedHashSha256 != null && hashSha256.equals(preparedHashSha256)) { + /*if (preparedHashSha256 != null && hashSha256.equals(preparedHashSha256)) { Logger.getLogger(Main.class.getName()).log(Level.FINE, "RECEIVED SWF IS ONE THAT WAS PREPARED FOR RUN..."); return runningSWF; - } + }*/ Logger.getLogger(Main.class.getName()).log(Level.FINE, "RECEIVED SWF IS NOT ANY OF OURS"); return null; @@ -285,7 +312,8 @@ public class Main { if (session != null) { return session.getDebuggedSwfs().get(0); } - return null; + return runningSWF; + //return null; } public static WatchService getWatcher() { @@ -632,8 +660,9 @@ public class Main { private static void prepareSwf(String swfHash, SwfPreparation prep, File toPrepareFile, File origFile, File tempFilesDir, List tempFiles) throws IOException, InterruptedException { SWF instrSWF = null; + String origHash = swfHash; try (FileInputStream fis = new FileInputStream(toPrepareFile)) { - instrSWF = new SWF(fis, toPrepareFile.getAbsolutePath(), origFile == null ? "unknown.swf" : origFile.getName(), false); + instrSWF = new SWF(fis, toPrepareFile.getAbsolutePath(), origFile == null ? "unknown.swf" : origFile.getName(), false); } catch (InterruptedException ex) { logger.log(Level.SEVERE, null, ex); } @@ -648,9 +677,11 @@ public class Main { File newTempFile = createTempFileInDir(tempFilesDir, "~ffdec_run_import_", ".swf"); it.setUrl("./" + newTempFile.getName()); byte[] impData = Helper.readFile(importedFile.getAbsolutePath()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWF.decompress(new ByteArrayInputStream(impData), baos); Helper.writeFile(newTempFile.getAbsolutePath(), impData); tempFiles.add(newTempFile); - prepareSwf("imported_" + md5(impData), prep, newTempFile, importedFile, tempFilesDir, tempFiles); + prepareSwf("imported_" + sha256(baos.toByteArray()), prep, newTempFile, importedFile, tempFilesDir, tempFiles); } } } @@ -663,9 +694,15 @@ public class Main { try (FileOutputStream fos = new FileOutputStream(toPrepareFile)) { instrSWF.saveTo(fos); } - if ("main".equals(swfHash)) { - preparedHashSha256 = instrSWF.getHashSha256(); + if (!hash2PreparedHash.containsKey(origHash)) { + hash2PreparedHash.put(origHash, new LinkedHashSet<>()); } + String preparedHash = instrSWF.getHashSha256(); + hash2PreparedHash.get(origHash).add(preparedHash); + Logger.getLogger(Main.class.getName()).log(Level.FINE, "Prepared {0} as {1}", new Object[]{origHash, preparedHash}); + /*if ("main".equals(swfHash)) { + preparedHashSha256 = instrSWF.getHashSha256(); + }*/ } } @@ -677,7 +714,7 @@ public class Main { try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fTempFile))) { swfToSave.saveTo(fos, false, swf.gfx); } - prepareSwf("main", new SwfDebugPrepare(doPCode), fTempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles); + prepareSwf(swf.getHashSha256(), new SwfDebugPrepare(doPCode), fTempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles); } private static File createTempFileInDir(File tempFilesDir, String prefix, String suffix) throws IOException { @@ -741,7 +778,7 @@ public class Main { swfToSave.saveTo(fos, false, swf.gfx); } - prepareSwf("main", new SwfRunPrepare(), tempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles); + prepareSwf(swf.getHashSha256(), new SwfRunPrepare(), tempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles); } catch (IOException ex) { return; @@ -901,7 +938,7 @@ public class Main { try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fTempFile))) { swfToSave.saveTo(fos, false, swf.gfx); } - prepareSwf("main", new SwfDebugPrepare(doPCode), fTempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles); + prepareSwf(swf.getHashSha256(), new SwfDebugPrepare(doPCode), fTempFile, swf.getFile() == null ? null : new File(swf.getFile()), tempRunDir, tempFiles); return null; } @@ -1892,7 +1929,8 @@ public class Main { List openableLists = new ArrayList<>(); for (int index = 0; index < sourceInfos.length; index++) { OpenableSourceInfo sourceInfo = sourceInfos[index]; - OpenableList openables = null; + //Logger.getLogger(Main.class.getName()).log(Level.FINE, "Loading source info {0}", sourceInfo.getFile()); + OpenableList openables = null; try { Main.startWork(AppStrings.translate("work.reading.swf") + "...", null); try { @@ -1966,15 +2004,15 @@ public class Main { mainFrame.getPanel().getABCPanel().updateLinksLabel(); } } - } - List breakpointsList = conf.getCustomDataAsList(CustomConfigurationKeys.KEY_BREAKPOINTS); - for (String breakpoint : breakpointsList) { - if (breakpoint.matches("^.*:[0-9]+$")) { - int line = Integer.parseInt(breakpoint.substring(breakpoint.lastIndexOf(":") + 1)); - String scriptName = breakpoint.substring(0, breakpoint.lastIndexOf(":")); - getDebugHandler().addBreakPoint(swf, scriptName, line); + List breakpointsList = conf.getCustomDataAsList(CustomConfigurationKeys.KEY_BREAKPOINTS); + for (String breakpoint : breakpointsList) { + if (breakpoint.matches("^.*:[0-9]+$")) { + int line = Integer.parseInt(breakpoint.substring(breakpoint.lastIndexOf(":") + 1)); + String scriptName = breakpoint.substring(0, breakpoint.lastIndexOf(":")); + getDebugHandler().addBreakPoint(swf, scriptName, line); + } } - } + } } } } @@ -2836,9 +2874,9 @@ public class Main { ErrorLogFrame.getInstance().setVisible(true); } - private static String md5(byte[] data) { + private static String sha256(byte[] data) { try { - java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); byte[] array = md.digest(data); StringBuilder sb = new StringBuilder(); for (int i = 0; i < array.length; ++i) { @@ -2995,7 +3033,9 @@ public class Main { modifiedListener.dataModified(inputData); return; } - final String hash = md5(inputData); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWF.decompress(new ByteArrayInputStream(inputData), baos); + final String hash = sha256(baos.toByteArray()); OpenableOpened afterLoad = new OpenableOpened() { @Override public void opened(Openable openable) { @@ -3024,6 +3064,13 @@ public class Main { for (OpenableList sl : Main.getMainFrame().getPanel().getSwfs()) { for (int s = 0; s < sl.size(); s++) { Openable op = sl.get(s); + if (op instanceof SWF) { + SWF swf = (SWF) op; + if (hash.equals(swf.getHashSha256())) { + afterLoad.opened(op); + return; + } + } String t = op.getTitleOrShortFileName(); if (t == null) { t = ""; @@ -3064,13 +3111,13 @@ public class Main { View.execInEventDispatch(new Runnable() { @Override public void run() { - String hash = "main"; + String hash = "unknown"; String scriptNameNoHash = scriptName; if (scriptName.contains(":")) { hash = scriptName.substring(0, scriptName.indexOf(":")); scriptNameNoHash = scriptName.substring(scriptName.indexOf(":") + 1); } - SWF swf = "main".equals(hash) ? session.getDebuggedSwfs().get(0) : Main.getSwfByHash(hash); + SWF swf = Main.getSwfByHash(hash); Logger.getLogger(Main.class.getName()).log(Level.FINE, "Break. Current session: {0}, New break session: {1}", new Object[]{Main.getCurrentDebugSession(), session}); if ( @@ -3081,6 +3128,10 @@ public class Main { mainFrame.getPanel().refreshBreakPoints(); return; } + if (swf == null) { + Logger.getLogger(Main.class.getName()).log(Level.FINE, "Break at unknown SWF."); + return; + } mainFrame.getPanel().gotoScriptLine(swf, scriptNameNoHash, line, classIndex, traitIndex, methodIndex, Main.isDebugPCode()); } }); @@ -3935,40 +3986,56 @@ public class Main { } public static String getSwfHash(SWF swf) { - if (swf == getRunningSWF()) { + /*if (swf == getRunningSWF()) { return "main"; - } + }*/ String tit = swf.getTitleOrShortFileName(); if (tit != null && tit.contains(":")) { return "loaded_" + tit.substring(tit.lastIndexOf(":") + 1); } - return ""; + return swf.getHashSha256(); } - public static SWF getSwfByHash(String hash) { - if ("main".equals(hash)) { + public static SWF getSwfByHash(String hashSha256) { + /*if ("main".equals(hash)) { SWF runningSwf = getRunningSWF(); if (runningSwf == null) { return mainFrame.getPanel().getCurrentSwf(); } return runningSwf; + }*/ + + + for (String origKey : hash2PreparedHash.keySet()) { + if (hash2PreparedHash.get(origKey).contains(hashSha256)) { + hashSha256 = origKey; + break; + } } - if (!hash.startsWith("loaded_")) { - return null; + + if (hashSha256.startsWith("loaded_")) { + hashSha256 = hashSha256.substring("loaded_".length()); } - hash = hash.substring("loaded_".length()); + if (hashSha256.startsWith("imported_")) { + hashSha256 = hashSha256.substring("imported_".length()); + } + for (OpenableList sl : Main.getMainFrame().getPanel().getSwfs()) { for (int s = 0; s < sl.size(); s++) { Openable op = sl.get(s); if (!(op instanceof SWF)) { continue; } + SWF swf = (SWF) op; + if (hashSha256.equals(swf.getHashSha256())) { + return swf; + } String t = op.getTitleOrShortFileName(); if (t == null) { t = ""; } - if (t.endsWith(":" + hash)) { //this one is already opened - return (SWF) op; + if (t.endsWith(":" + hashSha256)) { //this one is already opened + return swf; } } } diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbon.java b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbon.java index 23ac0ddbc..74e60a530 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameRibbon.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameRibbon.java @@ -143,16 +143,16 @@ public final class MainFrameRibbon extends AppRibbonFrame { boolean first = true; for (OpenableList swf : panel.getSwfs()) { - if (!first) { - sb.append(File.pathSeparator); - sbt.append(File.pathSeparator); - } - first = false; String file = swf.sourceInfo.getFile(); if (file != null) { + if (!first) { + sb.append(File.pathSeparator); + sbt.append(File.pathSeparator); + } sb.append(file); String t = swf.sourceInfo.getFileTitle(); sbt.append(t == null ? "" : t); + first = false; } } diff --git a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java index ee0211495..ffba47a5c 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/ABCPanel.java @@ -388,6 +388,9 @@ public class ABCPanel extends JPanel implements ItemListener, SearchListener stackClasses = Main.getStackClasses(); for (int i = 1; i < stackClasses.size(); i++) { String cls = stackClasses.get(i); - String clsHash = "main"; + String clsHash = "unknown"; if (cls.contains(":")) { clsHash = cls.substring(0, cls.indexOf(":")); cls = cls.substring(cls.indexOf(":") + 1); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index e64e5f996..ba3c983e5 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1112,3 +1112,5 @@ menu.file.start.debuglisten = Debug listen... work.debugging.listening = Listening for incoming connections... contextmenu.prepareDebug.injectDebug = Prepare file for debugging (+ inject debug info) contextmenu.prepareDebug.generateSwd = Prepare file for debugging (+ generate SWD) +prepareDebug.title = Hit Save to overwrite current file or select another file. +work.halted.with = Debugging of %file% started, execution halted. Add breakpoints and click Continue (F5) to resume running. \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index ab00fd853..b6e7cdd3b 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -3077,17 +3077,14 @@ public class TagTreeContextMenu extends JPopupMenu { private void prepareDebugActionPerformed(ActionEvent evt) { TreeItem item = getCurrentItem(); - SWF swf = (SWF) item.getOpenable(); - File dir = null; - String newName = "debug.swf"; + SWF swf = (SWF) item.getOpenable(); + JFileChooser chooser = View.getFileChooserWithIcon("debug"); if (swf.getFile() != null) { - dir = new File(swf.getFile()).getParentFile(); - String name = new File(swf.getFile()).getName(); - newName = name.substring(0, name.lastIndexOf(".")) + "_debug" + name.substring(name.lastIndexOf(".")); + File dir = new File(swf.getFile()).getParentFile(); + chooser.setCurrentDirectory(dir); + chooser.setSelectedFile(new File(swf.getFile())); + chooser.setDialogTitle(AppStrings.translate("prepareDebug.title")); } - JFileChooser chooser = View.getFileChooserWithIcon("debug"); - chooser.setCurrentDirectory(dir); - chooser.setSelectedFile(new File(dir, newName)); chooser.setFileFilter(new FileFilter() { @Override public boolean accept(File f) {