Changed: Prepared files have _debug/_debugpcode suffix.

Added: Prepare for p-code debugging.
Added: Preparation progress
Fixed: Nullpointer in tag when character not found.
This commit is contained in:
Jindra Petřík
2026-02-16 18:19:23 +01:00
parent 729e4a1685
commit 203ac6dcc9
4 changed files with 136 additions and 38 deletions

View File

@@ -917,6 +917,9 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable {
if (swf.getCharacters(true).containsKey(characterId) && !swf.getCyclicCharacters().contains(characterId)) {
Set<Integer> needed4 = new LinkedHashSet<>();
CharacterTag character = swf.getCharacter(characterId);
if (character == null) {
continue;
}
if (character.isImported()) {
continue;
}

View File

@@ -1111,8 +1111,12 @@ work.generating.swc = Generating Swc...
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.injectDebug.pcode = Prepare file for P-code 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.
contextmenu.prepareDebug.generateSwd.pcode = Prepare file for P-code debugging (+ generate SWD)
prepareDebug.title = Save prepared version
work.prepareDebug = Preparing file for debugging...
prepareDebug.finishedin = Prepared in %time%
work.halted.with = Debugging of %file% started, execution halted. Add breakpoints and click Continue (F5) to resume running.
debug.session = Session %id%
debug.session.running = (Running)

View File

@@ -1110,8 +1110,12 @@ work.generating.swc = Generov\u00e1n\u00ed Swc...
menu.file.start.debuglisten = Naslouchat lad\u011bn\u00ed...
work.debugging.listening = Naslouch\u00e1m pro p\u0159\u00edchoz\u00ed spojen\u00ed...
contextmenu.prepareDebug.injectDebug = P\u0159ipravit soubor pro lad\u011bn\u00ed (+ injektovat lad\u00edc\u00ed informace)
contextmenu.prepareDebug.injectDebug.pcode = P\u0159ipravit soubor pro lad\u011bn\u00ed P-k\u00f3du (+ injektovat lad\u00edc\u00ed informace)
contextmenu.prepareDebug.generateSwd = P\u0159ipravit soubor pro lad\u011bn\u00ed (+ vytvo\u0159it SWD)
prepareDebug.title = Stiskni Ulo\u017eit pro p\u0159eps\u00e1n\u00ed nyn\u011bj\u0161\u00edhio souboru nebo vyber soubor jin\u00fd.
contextmenu.prepareDebug.generateSwd.pcode = P\u0159ipravit soubor pro lad\u011bn\u00ed P-k\u00f3du (+ vytvo\u0159it SWD)
prepareDebug.title = Ulo\u017een\u00ed p\u0159ipraven\u00e9 verze
work.prepareDebug = P\u0159ipravuji soubor pro lad\u011bn\u00ed...
prepareDebug.finishedin = P\u0159ipraveno za %time%
work.halted.with = Lad\u011bn\u00ed %file% za\u010dalo, b\u011bh je nyn\u00ed pozastaven. P\u0159idejte breakpointy a klikn\u011bte na Pokra\u010dovat (F5) pro obnoven\u00ed b\u011bhu.
debug.session = Sezen\u00ed %id%
debug.session.running = (B\u011b\u017e\u00ed)

View File

@@ -150,6 +150,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFVersion;
import com.jpexs.decompiler.graph.CompilationException;
import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.LinkedIdentityHashSet;
import com.jpexs.helpers.Reference;
@@ -251,7 +252,7 @@ public class TagTreeContextMenu extends JPopupMenu {
private JMenuItem exportSwfXmlMenuItem;
private JMenuItem saveSwcMenuItem;
private JMenuItem saveExeMenuItem;
private JMenuItem importSwfXmlMenuItem;
@@ -379,13 +380,17 @@ public class TagTreeContextMenu extends JPopupMenu {
private JMenuItem convertShapeTypeMenuItem;
private JMenuItem convertPlaceObjectTypeMenuItem;
private JMenuItem normalizeFontsMenuItem;
private JMenuItem prepareDebugInject;
private JMenuItem prepareDebugSwd;
private JMenuItem prepareDebugInjectPCode;
private JMenuItem prepareDebugSwdPCode;
private List<TreeItem> items = new ArrayList<>();
private static final int KIND_TAG_MOVETO = 0;
@@ -576,17 +581,16 @@ public class TagTreeContextMenu extends JPopupMenu {
exportSwfXmlMenuItem.setIcon(View.getIcon("exportxml16"));
add(exportSwfXmlMenuItem);
saveSwcMenuItem = new JMenuItem(mainPanel.translate("contextmenu.saveSwc"));
saveSwcMenuItem.addActionListener(this::saveSwcActionPerformed);
saveSwcMenuItem.setIcon(View.getIcon("bundleswc16"));
add(saveSwcMenuItem);
saveExeMenuItem = new JMenuItem(mainPanel.translate("menu.file.saveasexe"));
saveExeMenuItem.addActionListener(this::saveExeActionPerformed);
saveExeMenuItem.setIcon(View.getIcon("saveasexe16"));
add(saveExeMenuItem);
addSeparator();
rawEditMenuItem = new JMenuItem(mainPanel.translate("contextmenu.rawEdit"));
@@ -649,25 +653,33 @@ public class TagTreeContextMenu extends JPopupMenu {
convertPlaceObjectTypeMenuItem.setIcon(View.getIcon("placeobject16"));
add(convertPlaceObjectTypeMenuItem);
normalizeFontsMenuItem = new JMenuItem(mainPanel.translate("contextmenu.normalizeFonts"));
normalizeFontsMenuItem.addActionListener(this::normalizeFontsActionPerformed);
normalizeFontsMenuItem.setIcon(View.getIcon("font16"));
add(normalizeFontsMenuItem);
addSeparator();
prepareDebugInject = new JMenuItem(mainPanel.translate("contextmenu.prepareDebug.injectDebug"));
prepareDebugInject.addActionListener(this::prepareDebugActionPerformed);
prepareDebugInject.setIcon(View.getIcon("debug16"));
add(prepareDebugInject);
prepareDebugInjectPCode = new JMenuItem(mainPanel.translate("contextmenu.prepareDebug.injectDebug.pcode"));
prepareDebugInjectPCode.addActionListener(this::prepareDebugPCodeActionPerformed);
prepareDebugInjectPCode.setIcon(View.getIcon("debug16"));
add(prepareDebugInjectPCode);
prepareDebugSwd = new JMenuItem(mainPanel.translate("contextmenu.prepareDebug.generateSwd"));
prepareDebugSwd.addActionListener(this::prepareDebugActionPerformed);
prepareDebugSwd.setIcon(View.getIcon("debug16"));
add(prepareDebugSwd);
prepareDebugSwdPCode = new JMenuItem(mainPanel.translate("contextmenu.prepareDebug.generateSwd.pcode"));
prepareDebugSwdPCode.addActionListener(this::prepareDebugPCodeActionPerformed);
prepareDebugSwdPCode.setIcon(View.getIcon("debug16"));
add(prepareDebugSwdPCode);
gotoDocumentClassMenuItem = new JMenuItem(mainPanel.translate("menu.tools.gotoDocumentClass"));
gotoDocumentClassMenuItem.addActionListener(this::gotoDocumentClassActionPerformed);
gotoDocumentClassMenuItem.setIcon(View.getIcon("gotomainclass16"));
@@ -1370,7 +1382,9 @@ public class TagTreeContextMenu extends JPopupMenu {
boolean hasExportableNodes = tree.hasExportableNodes();
prepareDebugInject.setVisible(false);
prepareDebugInjectPCode.setVisible(false);
prepareDebugSwd.setVisible(false);
prepareDebugSwdPCode.setVisible(false);
gotoDocumentClassMenuItem.setVisible(false);
setAsLinkageMenuItem.setVisible(false);
setAs3ClassLinkageMenuItem.setVisible(false);
@@ -1566,7 +1580,7 @@ public class TagTreeContextMenu extends JPopupMenu {
if ("__Packages".equals(firstPkg)) {
addAs12ClassMenuItem.setVisible(true);
}
}
}
if (firstItem instanceof ClassesListTreeModel) {
addAs3ClassMenuItem.setVisible(true);
if (firstItem.getOpenable() instanceof SWF) {
@@ -1604,8 +1618,10 @@ public class TagTreeContextMenu extends JPopupMenu {
if (((SWF) firstItem).isAS3()) {
gotoDocumentClassMenuItem.setVisible(true);
prepareDebugInject.setVisible(true);
prepareDebugInjectPCode.setVisible(true);
} else {
prepareDebugSwd.setVisible(true);
prepareDebugSwdPCode.setVisible(true);
}
}
@@ -2918,17 +2934,15 @@ public class TagTreeContextMenu extends JPopupMenu {
mainPanel.setTagTreeSelectedNode(mainPanel.getCurrentTree(), lastConverted);
}
}
private void normalizeFontsActionPerformed(ActionEvent evt) {
for (TreeItem item : getSelectedItems()) {
SWF swf = (SWF) item;
FontNormalizer normalizer = new FontNormalizer();
normalizer.normalizeFonts(swf);
normalizer.normalizeFonts(swf);
}
mainPanel.getCurrentTree().repaint();
}
private void replaceRefsWithTagActionPerformed(ActionEvent evt) {
TreeItem itemr = getCurrentItem();
@@ -3051,9 +3065,9 @@ public class TagTreeContextMenu extends JPopupMenu {
}
Main.getMainFrame().getPanel().refreshTree();
}
private void saveSwcActionPerformed(ActionEvent evt) {
SWF swf = (SWF) getCurrentItem();
SWF swf = (SWF) getCurrentItem();
Main.saveSwc(swf);
}
@@ -3077,12 +3091,20 @@ public class TagTreeContextMenu extends JPopupMenu {
private void prepareDebugActionPerformed(ActionEvent evt) {
TreeItem item = getCurrentItem();
SWF swf = (SWF) item.getOpenable();
JFileChooser chooser = View.getFileChooserWithIcon("debug");
SWF swf = (SWF) item.getOpenable();
JFileChooser chooser = View.getFileChooserWithIcon("debug");
if (swf.getFile() != null) {
File dir = new File(swf.getFile()).getParentFile();
chooser.setCurrentDirectory(dir);
chooser.setSelectedFile(new File(swf.getFile()));
File file = new File(swf.getFile());
//add _debug extension
String name = file.getName();
if (name.contains(".")) {
name = name.substring(0, name.lastIndexOf(".")) + "_debug" + name.substring(name.lastIndexOf("."));
} else {
name = name + "_debug";
}
chooser.setSelectedFile(new File(dir, name));
chooser.setDialogTitle(AppStrings.translate("prepareDebug.title"));
}
chooser.setFileFilter(new FileFilter() {
@@ -3100,17 +3122,82 @@ public class TagTreeContextMenu extends JPopupMenu {
return;
}
File selFile = Helper.fixDialogFile(chooser.getSelectedFile());
boolean doPCode = false;
List<File> tempFiles = new ArrayList<>();
try {
Main.prepareForDebug(swf, selFile, selFile.getParentFile(), tempFiles, doPCode);
} catch (IOException | InterruptedException ex) {
ViewMessages.showMessageDialog(mainPanel, ex.toString(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE);
}
Main.stopWork();
prepareDebugGeneral(false, swf, selFile);
}
private void prepareDebugPCodeActionPerformed(ActionEvent evt) {
TreeItem item = getCurrentItem();
SWF swf = (SWF) item.getOpenable();
JFileChooser chooser = View.getFileChooserWithIcon("debug");
if (swf.getFile() != null) {
File dir = new File(swf.getFile()).getParentFile();
chooser.setCurrentDirectory(dir);
File file = new File(swf.getFile());
//add _debugpcode extension
String name = file.getName();
if (name.contains(".")) {
name = name.substring(0, name.lastIndexOf(".")) + "_debugpcode" + name.substring(name.lastIndexOf("."));
} else {
name = name + "_debugpcode";
}
chooser.setSelectedFile(new File(dir, name));
chooser.setDialogTitle(AppStrings.translate("prepareDebug.title"));
}
chooser.setFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
return f.getAbsolutePath().toLowerCase().endsWith(".swf") || f.isDirectory();
}
@Override
public String getDescription() {
return AppStrings.translate("filter.swf");
}
});
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
return;
}
File selFile = Helper.fixDialogFile(chooser.getSelectedFile());
prepareDebugGeneral(true, swf, selFile);
}
private void prepareDebugGeneral(boolean pCode, SWF swf, File selFile) {
long timeBefore = System.currentTimeMillis();
CancellableWorker prepareDebugWorker = new CancellableWorker("prepareDebugWorker") {
@Override
protected Object doInBackground() throws Exception {
List<File> tempFiles = new ArrayList<>();
try {
Main.prepareForDebug(swf, selFile, selFile.getParentFile(), tempFiles, pCode);
} catch (IOException | InterruptedException ex) {
ViewMessages.showMessageDialog(mainPanel, ex.toString(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE);
}
return null;
}
@Override
protected void done() {
Main.stopWork();
long timeAfter = System.currentTimeMillis();
final long timeMs = timeAfter - timeBefore;
View.execInEventDispatch(() -> {
mainPanel.setStatus(AppStrings.translate("prepareDebug.finishedin").replace("%time%", Helper.formatTimeSec(timeMs)));
});
}
@Override
public void workerCancelled() {
Main.stopWork();
}
};
Main.startWork(AppStrings.translate("work.prepareDebug"), prepareDebugWorker);
prepareDebugWorker.execute();
}
private void gotoDocumentClassActionPerformed(ActionEvent evt) {
TreeItem item = getCurrentItem();
item.getOpenable();
@@ -4025,9 +4112,9 @@ public class TagTreeContextMenu extends JPopupMenu {
}
private void createAs2Class(String className, SWF swf) {
className = DottedChain.parsePrintable(swf, className).toRawString();
ReadOnlyTagList tags = swf.getTags();
List<Integer> exportedIds = new ArrayList<>();
for (int i = 0; i < tags.size(); i++) {
@@ -4082,7 +4169,7 @@ public class TagTreeContextMenu extends JPopupMenu {
String[] parts = className.contains(".") ? className.split("\\.") : new String[]{className};
DottedChain dc = new DottedChain(parts);
try {
try {
Set<String> used = new LinkedHashSet<>();
String sourceCode = "class " + dc.toPrintableString(used, swf, false) + "{}";
StringBuilder sb = new StringBuilder();
@@ -5135,7 +5222,7 @@ public class TagTreeContextMenu extends JPopupMenu {
neededClasses.add(cls);
}
}
Set<CharacterTag> neededChars = Collections.newSetFromMap(new IdentityHashMap<>());
for (Integer characterId : needed) {
CharacterTag neededTag = sourceSwf.getCharacter(characterId);