diff --git a/CHANGELOG.md b/CHANGELOG.md index a129acde4..e80238bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. - Harman unpacker for binary data - Multilevel binary data unpacking is possible - [#2131] AS1/2 Debugger - show _root variable +- [#2124] Copy tags to other SWFs and replace same classes / export names ### Fixed - [#2021], [#2000] Caret position in editors when using tabs and / or unicode @@ -3309,6 +3310,7 @@ Major version of SWF to XML export changed to 2. [#2119]: https://www.free-decompiler.com/flash/issues/2119 [#2129]: https://www.free-decompiler.com/flash/issues/2129 [#2131]: https://www.free-decompiler.com/flash/issues/2131 +[#2124]: https://www.free-decompiler.com/flash/issues/2124 [#2021]: https://www.free-decompiler.com/flash/issues/2021 [#2000]: https://www.free-decompiler.com/flash/issues/2000 [#2116]: https://www.free-decompiler.com/flash/issues/2116 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 2385fa1f4..e38224be5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -413,6 +413,9 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { private static final DecompilerPool decompilerPool = new DecompilerPool(); + @Internal + private Map exportNameToCharacter = new HashMap<>(); + @Internal private AbcIndexing abcIndex; @@ -822,6 +825,14 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { int charId = classToCharacter.get(className); return getCharacter(charId); } + + public CharacterTag getCharacterByExportName(String exportName) { + if (!exportNameToCharacter.containsKey(exportName)) { + return null; + } + int charId = exportNameToCharacter.get(exportName); + return getCharacter(charId); + } public String getExportName(int characterId) { CharacterTag characterTag = getCharacters().get(characterId); @@ -1821,6 +1832,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } public void assignExportNamesToSymbols() { + exportNameToCharacter.clear(); HashMap exportNames = new HashMap<>(importedTagToExportNameMapping); for (Tag t : getTags()) { if (t instanceof ExportAssetsTag) { @@ -1830,6 +1842,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { String name = eat.names.get(i); if ((!exportNames.containsKey(tagId)) && (!exportNames.containsValue(name))) { exportNames.put(tagId, name); + exportNameToCharacter.put(name, tagId); } } } 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 19585f04e..411d62e5e 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 @@ -377,8 +377,11 @@ public class DefineSpriteTag extends DrawableTag implements Timelined { @Override public void getNeededCharacters(Set needed, SWF swf) { for (Tag t : getTags()) { - if ((t instanceof CharacterIdTag) && !(t instanceof SoundStreamHeadTypeTag) && !(t instanceof DefineExternalStreamSound)) { - needed.add(((CharacterIdTag) t).getCharacterId()); + if (t instanceof PlaceObjectTypeTag) { + int chId = ((PlaceObjectTypeTag) t).getCharacterId(); + if (chId != -1) { + needed.add(chId); + } } } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/LinkedIdentityHashSet.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/LinkedIdentityHashSet.java new file mode 100644 index 000000000..ba997e94a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/LinkedIdentityHashSet.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2010-2023 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.helpers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Identity hash set which maintains insertion order. + * @author JPEXS + * @param + */ +public class LinkedIdentityHashSet implements Set { + + private class MyObj { + private final Object obj; + + public MyObj(Object obj) { + this.obj = obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(obj); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MyObj other = (MyObj) obj; + return this.obj == other.obj; + } + } + + private final Set set = new LinkedHashSet<>(); + + public LinkedIdentityHashSet() { + } + + public LinkedIdentityHashSet(Collection c) { + addAll(c); + } + + @Override + public int size() { + return set.size(); + } + + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return set.contains(new MyObj(o)); + } + + @Override + public Iterator iterator() { + final Iterator setIterator = set.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return setIterator.hasNext(); + } + + @Override + @SuppressWarnings("unchecked") + public E next() { + return (E) setIterator.next().obj; + } + }; + } + + @Override + @SuppressWarnings("unchecked") + public Object[] toArray() { + Object[] objs = set.toArray(); + for (int i = 0; i < objs.length; i++) { + objs[i] = ((MyObj) objs[i]).obj; + } + return objs; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + T[] ret = prepareArray(a); + Object[] objs = set.toArray(); + for (int i = 0; i < objs.length; i++) { + ret[i] = (T) ((MyObj) objs[i]).obj; + } + return ret; + } + + @SuppressWarnings("unchecked") + private T[] prepareArray(T[] a) { + int size = this.set.size(); + if (a.length < size) { + return (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); + } + if (a.length > size) { + a[size] = null; + } + return a; + } + + @Override + public boolean add(E e) { + return set.add(new MyObj(e)); + } + + @Override + public boolean remove(Object o) { + return set.remove(new MyObj(o)); + } + + @Override + public boolean containsAll(Collection c) { + Iterator it = c.iterator(); + while (it.hasNext()) { + Object o = it.next(); + if (!set.contains(new MyObj(o))) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection c) { + Iterator it = c.iterator(); + List items = new ArrayList<>(); + while (it.hasNext()) { + items.add(new MyObj(it.next())); + } + return set.addAll(items); + } + + @Override + public boolean retainAll(Collection c) { + Iterator it = c.iterator(); + List items = new ArrayList<>(); + while (it.hasNext()) { + items.add(new MyObj(it.next())); + } + return set.retainAll(items); + } + + @Override + public boolean removeAll(Collection c) { + Iterator it = c.iterator(); + List items = new ArrayList<>(); + while (it.hasNext()) { + items.add(new MyObj(it.next())); + } + return set.removeAll(items); + } + + @Override + public void clear() { + set.clear(); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 09466a689..a59cb3505 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1246,3 +1246,6 @@ contextmenu.applyUnpacker = Apply unpacker binarydata.dataInside.packer = It looks like this binary data is packed with %packer%. Click here to unpack the binary data. error.wrong.packer = %item%\r\nCannot unpack binary data with %packer%.\r\nThe data is probably not packed with this packer. + +contextmenu.copyTagToReplaceByClass = Copy tag to (replace by class name) +contextmenu.copyTagToReplaceByExportName = Copy tag to (replace by export name) \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index d25123773..300f2c46b 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -843,7 +843,7 @@ importing_as.finishedin = Importov\u00e1no za %time% work.deobfuscating_pcode = Deobfuskov\u00e1n\u00ed p-k\u00f3du work.injecting_debuginfo = Injektuji lad\u00edc\u00ed informace -work.generating_swd = Generaruji SWD soubor +work.generating_swd = Generuji SWD soubor button.replaceRefs = Nahradit reference za jin\u00e9 ID charakteru @@ -1221,3 +1221,6 @@ header.warning.unsupportedGfxEncryption = GFX nepodporuje Harman \u0161ifrov\u00 contextmenu.applyUnpacker = Pou\u017e\u00edt unpacker binarydata.dataInside.packer = Vypad\u00e1 to, \u017ee tato bin\u00e1rn\u00ed data jsou zabalena pomoc\u00ed %packer%. Klikn\u011bte zde pro jejich rozbalen\u00ed. error.wrong.packer = %item%\r\nNelze rozbalit bin\u00e1rn\u00ed data pomoc\u00ed %packer%.\r\nTato data pravd\u011bpodobn\u011b nebyla zabalena t\u00edmto packerem. + +contextmenu.copyTagToReplaceByClass = Kop\u00edrovat tag do (nahradit podle n\u00e1zvu t\u0159\u00eddy) +contextmenu.copyTagToReplaceByExportName = Kop\u00edrovat tag do (nahradit podle exportovan\u00e9ho n\u00e1zvu) \ 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 06f0b0232..5539edc98 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -19,6 +19,8 @@ package com.jpexs.decompiler.flash.gui.tagtree; import com.jpexs.decompiler.flash.IdentifiersDeobfuscation; import com.jpexs.decompiler.flash.ReadOnlyTagList; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.TagRemoveListener; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.ScriptPack; @@ -110,7 +112,9 @@ import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; import com.jpexs.decompiler.flash.types.HasCharacterId; import com.jpexs.decompiler.graph.CompilationException; import com.jpexs.decompiler.graph.DottedChain; +import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.LinkedIdentityHashSet; import com.jpexs.helpers.Reference; import com.jpexs.helpers.utf8.Utf8Helper; import java.awt.event.ActionEvent; @@ -122,9 +126,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; +import java.util.IdentityHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -225,6 +231,8 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenu copyTagToMenu; private JMenu copyTagToWithDependenciesMenu; + + private JMenu copyTagToReplaceByClassMenu; private JMenu copyFrameToMenu; @@ -296,6 +304,7 @@ public class TagTreeContextMenu extends JPopupMenu { private static final int KIND_TAG_COPYTODEPS = 3; private static final int KIND_FRAME_MOVETO = 4; private static final int KIND_FRAME_COPYTO = 5; + private static final int KIND_TAG_COPYTOCLASSOREXPORTNAME = 6; private TreeItem getCurrentItem() { if (items.isEmpty()) { @@ -562,6 +571,10 @@ public class TagTreeContextMenu extends JPopupMenu { copyTagToWithDependenciesMenu = new JMenu(mainPanel.translate("contextmenu.copyTagWithDependencies")); copyTagToWithDependenciesMenu.setIcon(View.getIcon("copy16")); add(copyTagToWithDependenciesMenu); + + copyTagToReplaceByClassMenu = new JMenu(mainPanel.translate("contextmenu.copyTagToReplaceByClass")); + copyTagToReplaceByClassMenu.setIcon(View.getIcon("copy16")); + add(copyTagToReplaceByClassMenu); copyFrameToMenu = new JMenu(mainPanel.translate("contextmenu.copyFrame")); copyFrameToMenu.setIcon(View.getIcon("copy16")); @@ -913,6 +926,8 @@ public class TagTreeContextMenu extends JPopupMenu { boolean allSelectedIsWritable = true; boolean allSelectedIsNotImported = true; boolean allSelectedIsFrameInSameTimeline = true; + boolean allSelectedIsTagWithClassName = true; + boolean allSelectedIsTagWithExportName = true; Timelined tim = null; for (TreeItem item : items) { if (!(item instanceof Frame)) { @@ -935,6 +950,16 @@ public class TagTreeContextMenu extends JPopupMenu { if (tag.isImported()) { allSelectedIsNotImported = false; } + if (tag instanceof CharacterTag) { + CharacterTag chtag = (CharacterTag) tag; + if (chtag.getClassNames().isEmpty()) { + allSelectedIsTagWithClassName = false; + } + if (chtag.getExportName() == null) { + allSelectedIsTagWithExportName = false; + } + + } } else { if (item instanceof TagScript) { Tag tag = ((TagScript) item).getTag(); @@ -950,6 +975,8 @@ public class TagTreeContextMenu extends JPopupMenu { } allSelectedIsTag = false; + allSelectedIsTagWithClassName = false; + allSelectedIsTagWithExportName = false; break; } } @@ -1081,6 +1108,7 @@ public class TagTreeContextMenu extends JPopupMenu { moveDownMenuItem.setVisible(false); copyTagToMenu.setVisible(false); copyTagToWithDependenciesMenu.setVisible(false); + copyTagToReplaceByClassMenu.setVisible(false); copyFrameToMenu.setVisible(false); copyFrameToClipboardMenuItem.setVisible(false); cutTagToClipboardMenuItem.setVisible(false); @@ -1411,6 +1439,7 @@ public class TagTreeContextMenu extends JPopupMenu { moveFrameToMenu.removeAll(); copyTagToMenu.removeAll(); copyTagToWithDependenciesMenu.removeAll(); + copyTagToReplaceByClassMenu.removeAll(); copyFrameToMenu.removeAll(); List tagItems = new ArrayList<>(); @@ -1447,7 +1476,7 @@ public class TagTreeContextMenu extends JPopupMenu { copyTagToMenu.setVisible(true); copyTagToWithDependenciesMenu.setVisible(true); - } + } if (allSelectedIsFrameInSameTimeline) { /*JMenuItem copyFrameToSubClipboardMenuItem = new JMenuItem(AppStrings.translate("contextmenu.clipboard.frame") + " (CTRL+C)", View.getIcon("clipboard16")); copyFrameToSubClipboardMenuItem.addActionListener(new ActionListener() { @@ -1476,20 +1505,32 @@ public class TagTreeContextMenu extends JPopupMenu { */ } - if (allSelectedIsInTheSameSwf && allSelectedIsTag && swfs.size() > 1) { + if (allSelectedIsInTheSameSwf && allSelectedIsTag && swfs.size() > 1) { for (OpenableList targetSwfList : swfs) { if ((targetSwfList.size() == 1) && (targetSwfList.get(0) == singleSwf)) { continue; } - addCopyMoveToMenusSwfList(KIND_TAG_MOVETO, singleSwf, targetSwfList, moveTagToMenu, tagItems); - addCopyMoveToMenusSwfList(KIND_TAG_MOVETODEPS, singleSwf, targetSwfList, moveTagToWithDependenciesMenu, tagItems); - addCopyMoveToMenusSwfList(KIND_TAG_COPYTO, singleSwf, targetSwfList, copyTagToMenu, tagItems); - addCopyMoveToMenusSwfList(KIND_TAG_COPYTODEPS, singleSwf, targetSwfList, copyTagToWithDependenciesMenu, tagItems); + addCopyMoveToMenusSwfList(KIND_TAG_MOVETO, singleSwf, targetSwfList, moveTagToMenu, tagItems, true, true); + addCopyMoveToMenusSwfList(KIND_TAG_MOVETODEPS, singleSwf, targetSwfList, moveTagToWithDependenciesMenu, tagItems, true, true); + addCopyMoveToMenusSwfList(KIND_TAG_COPYTO, singleSwf, targetSwfList, copyTagToMenu, tagItems, true, true); + addCopyMoveToMenusSwfList(KIND_TAG_COPYTODEPS, singleSwf, targetSwfList, copyTagToWithDependenciesMenu, tagItems, true, true); + if (allSelectedIsTagWithClassName || allSelectedIsTagWithExportName) { + addCopyMoveToMenusSwfList(KIND_TAG_COPYTOCLASSOREXPORTNAME, singleSwf, targetSwfList, copyTagToReplaceByClassMenu, tagItems, allSelectedIsTagWithExportName, allSelectedIsTagWithClassName); + } } moveTagToMenu.setVisible(true); moveTagToWithDependenciesMenu.setVisible(true); copyTagToMenu.setVisible(true); copyTagToWithDependenciesMenu.setVisible(true); + if (allSelectedIsTagWithClassName || allSelectedIsTagWithExportName) { + copyTagToReplaceByClassMenu.setVisible(true); + } + if (allSelectedIsTagWithClassName) { + copyTagToReplaceByClassMenu.setText(AppStrings.translate("contextmenu.copyTagToReplaceByClass")); + } + if (allSelectedIsTagWithExportName) { + copyTagToReplaceByClassMenu.setText(AppStrings.translate("contextmenu.copyTagToReplaceByExportName")); + } } if (allSelectedIsBinaryData) { @@ -2000,6 +2041,199 @@ public class TagTreeContextMenu extends JPopupMenu { Timelined timelined = selectPositionDialog.getSelectedTimelined(); copyOrMoveTags(new LinkedHashSet(items), false, timelined, position); } + + private class AlterCharacterTag extends CharacterTag { + + private int characterId; + + public AlterCharacterTag(SWF swf, int characterId) { + super(swf, 10000, "Alter", new ByteArrayRange(new byte[]{})); + this.characterId = characterId; + } + + + @Override + public void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException, InterruptedException { + } + + @Override + public void getData(SWFOutputStream sos) throws IOException { + } + + @Override + public int getCharacterId() { + return characterId; + } + + @Override + public void setCharacterId(int characterId) { + this.characterId = characterId; + } + } + + private void copyTagToReplaceByClassOrExportNameActionPerformed(ActionEvent evt, List items, SWF targetSwf) { + Set sourceSwfs = new LinkedHashSet<>(); + try { + List newTags = new ArrayList<>(); + Map changedCharacterIds = new HashMap<>(); + + Map classMappedAlterTags = new HashMap<>(); + Map classMappedTags = new HashMap<>(); + + Set ignoredItems = new LinkedIdentityHashSet<>(); + + for (TreeItem item : items) { + Tag tag = (Tag) item; + if (tag.getSwf() == null) { + continue; + } + Timelined realTargetTimelined; + CharacterTag targetSameNameCharacter = null; + + + CharacterTag chtag = (CharacterTag) tag; + + for (String className : chtag.getClassNames()) { + targetSameNameCharacter = targetSwf.getCharacterByClass(className); + if (targetSameNameCharacter != null) { + break; + } + } + + if (targetSameNameCharacter == null && chtag.getExportName() != null) { + targetSameNameCharacter = targetSwf.getCharacterByExportName(chtag.getExportName()); + } + + if (targetSameNameCharacter == null) { + ignoredItems.add(item); + continue; + } + + realTargetTimelined = targetSameNameCharacter.getTimelined(); + + AlterCharacterTag alterTag = new AlterCharacterTag(targetSwf, targetSameNameCharacter.getCharacterId()); + + Set needed = new LinkedHashSet<>(); + targetSameNameCharacter.getNeededCharacters(needed, targetSwf); + int ind = realTargetTimelined.indexOfTag(targetSameNameCharacter); + realTargetTimelined.removeTag(ind); + realTargetTimelined.addTag(ind, alterTag); + alterTag.setTimelined(realTargetTimelined); + targetSwf.computeDependentCharacters(); + removeAloneCharacters(needed, targetSwf); + classMappedAlterTags.put(tag, alterTag); + changedCharacterIds.put(((CharacterTag) tag).getCharacterId(), targetSameNameCharacter.getCharacterId()); + classMappedTags.put(tag, targetSameNameCharacter); + } + items.removeAll(ignoredItems); + + Set allDeps = new LinkedIdentityHashSet<>(); + allDeps.addAll(items); + Map depToItem = new HashMap<>(); + for (TreeItem item : items) { + Tag tag = (Tag) item; + Set deps = getDependenciesSet(Arrays.asList(item)); + for (TreeItem dep : deps) { + if (!allDeps.contains(dep)) { + allDeps.add(dep); + depToItem.put(dep, tag); + } + } + } + allDeps.removeAll(items); + allDeps.addAll(items); + + Set itemsSet = new LinkedIdentityHashSet<>(); + itemsSet.addAll(items); + + for (TreeItem item : allDeps) { + Tag tag = (Tag) item; + if (tag.getSwf() == null) { + continue; + } + SWF sourceSwf = tag.getSwf(); + sourceSwfs.add(sourceSwf); + + Timelined realTargetTimelined; + Tag realPosition; + + if (itemsSet.contains(tag)) { + realTargetTimelined = classMappedAlterTags.get(tag).getTimelined(); + realPosition = classMappedAlterTags.get(tag); + } else { + realTargetTimelined = classMappedAlterTags.get(depToItem.get(tag)).getTimelined(); + realPosition = classMappedAlterTags.get(depToItem.get(tag)); + } + + ReadOnlyTagList tags = realTargetTimelined.getTags(); + int positionInt = realPosition == null ? tags.size() : tags.indexOf(realPosition); + + Tag copyTag = tag.cloneTag(); + copyTag.setSwf(targetSwf, true); + copyTag.setTimelined(realTargetTimelined); + if (!itemsSet.contains(tag) + && (tag instanceof CharacterTag)) { + CharacterTag characterTag = (CharacterTag) copyTag; + int oldCharacterId = characterTag.getCharacterId(); + int newCharacterId = checkUniqueCharacterId(copyTag); + + changedCharacterIds.put(oldCharacterId, newCharacterId); + } + realTargetTimelined.addTag(positionInt, copyTag); + if (itemsSet.contains(tag)) { + realTargetTimelined.removeTag(classMappedAlterTags.get(tag)); + ((CharacterTag) copyTag).setClassNames(classMappedTags.get(tag).getClassNames()); + ((CharacterTag) copyTag).setExportName(classMappedTags.get(tag).getExportName()); + } + + targetSwf.updateCharacters(); + targetSwf.getCharacters(); // force rebuild character id cache + copyTag.setModified(true); + newTags.add(copyTag); + if (itemsSet.contains(tag)) { + mainPanel.replaceItemPin(classMappedTags.get(tag), copyTag); + } + } + for (int oldCharacterId : changedCharacterIds.keySet()) { + int newCharacterId = changedCharacterIds.get(oldCharacterId); + for (Tag newTag : newTags) { + // todo: avoid double replaces + newTag.replaceCharacter(oldCharacterId, newCharacterId); + if ((newTag instanceof CharacterIdTag) && !(newTag instanceof CharacterTag)) { + CharacterIdTag characterIdTag = (CharacterIdTag) newTag; + if (characterIdTag.getCharacterId() == oldCharacterId) { + characterIdTag.setCharacterId(newCharacterId); + } + } + } + } + + for (SWF sourceSwf : sourceSwfs) { + if (sourceSwf != targetSwf) { + sourceSwf.assignExportNamesToSymbols(); + sourceSwf.assignClassesToSymbols(); + sourceSwf.clearImageCache(); + sourceSwf.clearShapeCache(); + sourceSwf.updateCharacters(); + sourceSwf.computeDependentCharacters(); + sourceSwf.computeDependentFrames(); + sourceSwf.resetTimelines(sourceSwf); + } + } + targetSwf.assignExportNamesToSymbols(); + targetSwf.assignClassesToSymbols(); + targetSwf.clearImageCache(); + targetSwf.clearShapeCache(); + targetSwf.updateCharacters(); + targetSwf.computeDependentCharacters(); + targetSwf.computeDependentFrames(); + targetSwf.resetTimelines(targetSwf); + + mainPanel.refreshTree(targetSwf); + } catch (InterruptedException | IOException ex) { + Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, null, ex); + } + } private void copyFrameToActionPerformed(ActionEvent evt, List items, SWF targetSwf) { SelectFramePositionDialog selectPositionDialog = new SelectFramePositionDialog(mainPanel.getMainFrame().getWindow(), targetSwf); @@ -3513,7 +3747,7 @@ public class TagTreeContextMenu extends JPopupMenu { addFrames(false); //this works because timeline is selected, no frame } - private void addCopyMoveToMenusSwfList(int kind, SWF singleSwf, OpenableList targetSwfList, JMenuItem menu, List items) { + private void addCopyMoveToMenusSwfList(int kind, SWF singleSwf, OpenableList targetSwfList, JMenuItem menu, List items, boolean as12, boolean as3) { if (targetSwfList.isBundle()) { JMenu bundleMenu = new JMenu(targetSwfList.name); bundleMenu.setIcon(AbstractTagTree.getIconForType(AbstractTagTree.getTreeNodeType(targetSwfList))); @@ -3523,6 +3757,13 @@ public class TagTreeContextMenu extends JPopupMenu { for (final Openable targetOpenable : targetSwfList) { if (targetOpenable instanceof SWF) { if (targetOpenable != singleSwf) { + SWF swf = (SWF) targetOpenable; + if (!as12 && !swf.isAS3()) { + continue; + } + if (!as3 && swf.isAS3()) { + continue; + } addCopyMoveToMenus(kind, menu, items, targetOpenable.getShortFileName(), (SWF) targetOpenable); } } @@ -3551,6 +3792,9 @@ public class TagTreeContextMenu extends JPopupMenu { case KIND_FRAME_COPYTO: copyFrameToActionPerformed(ae, items, targetSwf); break; + case KIND_TAG_COPYTOCLASSOREXPORTNAME: + copyTagToReplaceByClassOrExportNameActionPerformed(ae, items, targetSwf); + break; } }); swfItem.setIcon(View.getIcon("flash16")); @@ -4203,7 +4447,7 @@ public class TagTreeContextMenu extends JPopupMenu { Timelined realTargetTimelined = targetTimelined; Tag realPosition = position; - + if (!Configuration.allowPlacingDefinesIntoSprites.get()) { //do not place Define tags in DefineSprites if ((tag instanceof CharacterTag) && (targetTimelined instanceof DefineSpriteTag)) { @@ -4236,14 +4480,6 @@ public class TagTreeContextMenu extends JPopupMenu { } if (move) { - /*if (tag == position) { - int index = tag.getTimelined().indexOfTag(position); - if (tag.getTimelined().getTags().size() > index + 1) { - position = tag.getTimelined().getTags().get(index + 1); - } else { - position = null; - } - }*/ tag.getTimelined().removeTag(tag); } @@ -4317,6 +4553,36 @@ public class TagTreeContextMenu extends JPopupMenu { Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, null, ex); } } + + private void removeAloneCharacters(Set aloneCharacterIds, SWF swf) { + for (int ch : aloneCharacterIds) { + CharacterTag ct = swf.getCharacter(ch); + if (ct == null) { + continue; + } + if (!ct.getClassNames().isEmpty()) { + continue; + } + if (ct.getExportName() != null) { + continue; + } + Set dependentCharacters = swf.getDependentCharacters(ch); + if (dependentCharacters.isEmpty()) { + Set needed = new LinkedHashSet<>(); + ct.getNeededCharacters(needed, swf); + List attachedTags = swf.getCharacterIdTags(ch); + for (CharacterIdTag cit : attachedTags) { + if (cit instanceof Tag) { + Tag citt = (Tag) cit; + citt.getTimelined().removeTag(citt); + } + } + ct.getTimelined().removeTag(ct); + swf.computeDependentCharacters(); + removeAloneCharacters(needed, swf); + } + } + } public void importScriptsActionPerformed(ActionEvent evt) { Openable openable = getCurrentItem().getOpenable();