From 2fb840baacc8536d372b031f919d3e5dbb79c6a2 Mon Sep 17 00:00:00 2001 From: Exund Date: Sun, 23 Oct 2022 19:36:10 +0200 Subject: [PATCH] Clone tags and frames --- .../src/com/jpexs/decompiler/flash/SWF.java | 14 +- .../flash/tags/DefineSpriteTag.java | 5 + .../decompiler/flash/tags/base/ButtonTag.java | 5 + .../decompiler/flash/timeline/Timelined.java | 5 +- .../jpexs/decompiler/flash/gui/MainPanel.java | 5 + .../flash/gui/locales/MainFrame.properties | 4 +- .../flash/gui/tagtree/TagTreeContextMenu.java | 149 ++++++++++++++++-- .../flash/gui/tagtree/TagTreeModel.java | 2 +- 8 files changed, 167 insertions(+), 22 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index c19f8e370..c18b092ca 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -3190,13 +3190,19 @@ public final class SWF implements SWFContainerItem, Timelined { updateCharacters(); } } + + public int indexOfTag(Tag tag) { + return tags.indexOf(tag); + } /** - * Adds a tag to the SWF If targetTreeItem is: - Frame: adds the tag to the + * Adds a tag to the SWF If targetTreeItem is: + * - Frame: adds the tag to the * Frame. Frame can be a frame of the main timeline or a DefineSprite frame - * - DefineSprite: adds the tag to the end of the DefineSprite's tag list - - * Any other tag in the SWF: adds the new tag exactly before the specified - * tag - Other: adds the tag to the end of the SWF's tag list + * - DefineSprite: adds the tag to the end of the DefineSprite's tag list + * - Any other tag in the SWF: adds the new tag exactly before the specified + * tag + * - Other: adds the tag to the end of the SWF's tag list * * @param tag * @param targetTreeItem diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java index 1e5592807..2846e2f2e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java @@ -327,6 +327,11 @@ public class DefineSpriteTag extends DrawableTag implements Timelined { subTags.add(index, tag); } + @Override + public int indexOfTag(Tag tag) { + return subTags.indexOf(tag); + } + @Override public void createOriginalData() { super.createOriginalData(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index bd6d8f72e..b16840bcd 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -172,6 +172,11 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { @Override public void addTag(int index, Tag tag) { } + + @Override + public int indexOfTag(Tag tag) { + return -1; + } @Override public void replaceTag(int index, Tag newTag) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timelined.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timelined.java index d16e80cdd..1d59e5897 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timelined.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timelined.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.timeline; import com.jpexs.decompiler.flash.ReadOnlyTagList; @@ -42,4 +43,6 @@ public interface Timelined extends BoundedTag { public void addTag(int index, Tag tag); public void replaceTag(int index, Tag newTag); + + public int indexOfTag(Tag tag); } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 4ec854f07..999714ba0 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -4175,6 +4175,11 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public void replaceTag(int index, Tag newTag) { } + @Override + public int indexOfTag(Tag tag) { + return -1; + } + @Override public RECT getRectWithStrokes() { return getRect(); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index bc9e9758b..e81123a0a 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -857,4 +857,6 @@ work.deobfuscating_pcode = Deobfuscating pcode work.injecting_debuginfo = Injecting debug info work.generating_swd = Generating SWD file -button.replaceRefs = Replace references with other character ID \ No newline at end of file +button.replaceRefs = Replace references with other character ID + +contextmenu.cloneTag = Clone tag \ 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 f362322e0..ca71ff6b7 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -125,7 +125,7 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem replaceNoFillMenuItem; private JMenuItem replaceWithTagMenuItem; - + private JMenuItem replaceRefsWithTagMenuItem; private JMenuItem rawEditMenuItem; @@ -142,6 +142,8 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenu addTagMenu; + private JMenuItem cloneTagMenuItem; + private JMenu moveTagMenu; private JMenu copyTagMenu; @@ -176,6 +178,10 @@ public class TagTreeContextMenu extends JPopupMenu { }); add(removeWithDependenciesMenuItem); + cloneTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.cloneTag")); + cloneTagMenuItem.addActionListener(this::cloneTagActionPerformed); + add(cloneTagMenuItem); + undoTagMenuItem = new JMenuItem(mainPanel.translate("contextmenu.undo")); undoTagMenuItem.addActionListener(this::undoTagActionPerformed); add(undoTagMenuItem); @@ -195,7 +201,7 @@ public class TagTreeContextMenu extends JPopupMenu { replaceWithTagMenuItem = new JMenuItem(mainPanel.translate("button.replaceWithTag")); replaceWithTagMenuItem.addActionListener(this::replaceWithTagActionPerformed); add(replaceWithTagMenuItem); - + replaceRefsWithTagMenuItem = new JMenuItem(mainPanel.translate("button.replaceRefs")); replaceRefsWithTagMenuItem.addActionListener(this::replaceRefsWithTagActionPerformed); add(replaceRefsWithTagMenuItem); @@ -324,7 +330,6 @@ public class TagTreeContextMenu extends JPopupMenu { continue; } - canRemove = false; break; } else { @@ -386,6 +391,23 @@ public class TagTreeContextMenu extends JPopupMenu { break; } } + + boolean allSelectedIsTagOrFrame = true; + for (TreeItem item : items) { + if (!(item instanceof Tag)) { + if (item instanceof TagScript) { + Tag tag = ((TagScript) item).getTag(); + if (tag instanceof DoActionTag || tag instanceof DoInitActionTag) { + continue; + } + } else if(item instanceof Frame) { + continue; + } + + allSelectedIsTagOrFrame = false; + break; + } + } boolean allSelectedIsBinaryData = true; for (TreeItem item : items) { @@ -424,9 +446,33 @@ public class TagTreeContextMenu extends JPopupMenu { } } + boolean noSelectParentChild = true; + Set selected = new HashSet<>(); + Set parents = new HashSet<>(); + for (TreeItem item : items) { + Timelined t = item.getSwf().getTimelined(item); + Timelined parent = null; + if (t != item.getSwf()) { + selected.add(t); + } else if (item instanceof Tag) { + Tag tag = (Tag) item; + Timelined temp = tag.getTimelined(); + if (!(temp instanceof SWF)) { + parents.add(temp); + parent = temp; + } + } + + if (selected.contains(parent) || parents.contains(t)) { + noSelectParentChild = false; + break; + } + } + expandRecursiveMenuItem.setVisible(false); removeMenuItem.setVisible(canRemove); removeWithDependenciesMenuItem.setVisible(canRemove && !allDoNotHaveDependencies); + cloneTagMenuItem.setVisible(allSelectedIsTagOrFrame && noSelectParentChild); undoTagMenuItem.setVisible(allSelectedIsTag); exportSelectionMenuItem.setEnabled(tagTree.hasExportableNodes()); replaceMenuItem.setVisible(false); @@ -817,7 +863,7 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.refreshTree(swf); } } - + private void replaceRefsWithTagActionPerformed(ActionEvent evt) { TreeItem itemr = tagTree.getCurrentTreeItem(); if (itemr == null) { @@ -830,11 +876,11 @@ public class TagTreeContextMenu extends JPopupMenu { ReplaceCharacterDialog replaceCharacterDialog = new ReplaceCharacterDialog(Main.getDefaultDialogsOwner()); if (replaceCharacterDialog.showDialog(swf, characterId) == AppDialog.OK_OPTION) { int newCharacterId = replaceCharacterDialog.getCharacterId(); - + for (Tag tag : swf.getTags()) { replaceRef(tag, characterId, newCharacterId); } - + swf.assignExportNamesToSymbols(); swf.assignClassesToSymbols(); swf.clearImageCache(); @@ -844,22 +890,22 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.refreshTree(swf); } } - + private void replaceRef(Tag tag, int characterId, int newCharacterId) { - if(tag instanceof DefineSpriteTag) { - DefineSpriteTag sprite = (DefineSpriteTag)tag; + if (tag instanceof DefineSpriteTag) { + DefineSpriteTag sprite = (DefineSpriteTag) tag; for (Tag subTag : sprite.getTags()) { replaceRef(subTag, characterId, newCharacterId); } sprite.clearReadOnlyListCache(); } - if(tag instanceof PlaceObjectTypeTag) { - PlaceObjectTypeTag placeTag = (PlaceObjectTypeTag)tag; - if(placeTag.getCharacterId() == characterId) { + if (tag instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag placeTag = (PlaceObjectTypeTag) tag; + if (placeTag.getCharacterId() == characterId) { placeTag.setCharacterId(newCharacterId); placeTag.setModified(true); Timelined tim = placeTag.getTimelined(); - if(tim != null) { + if (tim != null) { tim.resetTimeline(); } } @@ -1412,8 +1458,7 @@ public class TagTreeContextMenu extends JPopupMenu { TreePath tPath = path.pathByAddingChild(t); if ((t instanceof TagScript) && (((TagScript) t).getTag() instanceof ASMSource)) { out.add(tPath); - } - else if (t instanceof ASMSource) { + } else if (t instanceof ASMSource) { out.add(tPath); } else { populateScriptSubs(tPath, t, out); @@ -1704,4 +1749,78 @@ public class TagTreeContextMenu extends JPopupMenu { } } } + + private void cloneTagActionPerformed(ActionEvent e) { + List items = tagTree.getSelected(); + Set swfs = new HashSet<>(); + + try { + for (TreeItem item : items) { + SWF swf = item.getSwf(); + swfs.add(swf); + + if (item instanceof Tag) { + Tag tag = (Tag) item; + + Tag copyTag = tag.cloneTag(); + copyTag.setSwf(swf, true); + Timelined timelined = tag.getTimelined(); + int idx = timelined.indexOfTag(tag); + timelined.addTag(idx + 1, copyTag); + copyTag.setTimelined(timelined); + + chechUniqueCharacterId(copyTag); + copyTag.setModified(true); + + timelined.resetTimeline(); + } else if (item instanceof Frame) { + Frame f = (Frame) item; + Timelined timelined = f.timeline.timelined; + + int i; + boolean isLast = f.showFrameTag == null; + if(isLast) { + f.showFrameTag = new ShowFrameTag(swf); + Tag last = f.innerTags.get(f.innerTags.size() - 1); + int idx = timelined.indexOfTag(last) + 1; + timelined.addTag(idx, f.showFrameTag); + f.showFrameTag.setTimelined(timelined); + i = idx; + } else { + i = timelined.indexOfTag(f.showFrameTag); + } + + for (Tag tag : f.innerTags) { + Tag copyTag = tag.cloneTag(); + copyTag.setSwf(swf, true); + + timelined.addTag(++i, copyTag); + copyTag.setTimelined(timelined); + + chechUniqueCharacterId(copyTag); + copyTag.setModified(true); + } + + if(!isLast) { + ShowFrameTag next = new ShowFrameTag(swf); + timelined.addTag(++i, next); + next.setTimelined(timelined); + } + + timelined.resetTimeline(); + } + } + + for (SWF swf : swfs) { + swf.assignExportNamesToSymbols(); + swf.assignClassesToSymbols(); + swf.clearImageCache(); + swf.updateCharacters(); + mainPanel.refreshTree(swf); + } + + } catch (IOException | InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java index 2c4eaa7ca..16c08b467 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeModel.java @@ -264,7 +264,7 @@ public class TagTreeModel implements TreeModel { TreeItem sound = sounds.get(i); if (sound instanceof SoundStreamHeadTypeTag) { List blocks = ((SoundStreamHeadTypeTag) sound).getBlocks(); - if (blocks.isEmpty()) { + if (blocks == null || blocks.isEmpty()) { sounds.remove(i); } }