diff --git a/CHANGELOG.md b/CHANGELOG.md index d27d8ff36..f14ad7516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. - Updated Dutch translation - [#2259] Optional resampling sound to 44kHz on playback and on export - Set AS3 class linkage dialog (uses SymbolClass tag) in the context menu for characters +- Set AS1/2 linkage dialog (uses ExportAssets tag) in the context menu for characters - [#2189] Search bar in replace character (+ replace references) window - [#2011], [#2215] Option to ignore frame background color when exporting (make transparent) - ABC Explorer - list of usages of all items diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java index 9bc4a2349..c981f82c7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java @@ -17,12 +17,29 @@ package com.jpexs.decompiler.flash.tags.base; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.action.ActionTreeOperation; +import com.jpexs.decompiler.flash.action.model.CallMethodActionItem; +import com.jpexs.decompiler.flash.action.model.DirectValueActionItem; +import com.jpexs.decompiler.flash.action.model.GetMemberActionItem; +import com.jpexs.decompiler.flash.action.model.GetVariableActionItem; +import com.jpexs.decompiler.flash.action.swf7.ActionTry; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.NulWriter; import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.Reference; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -115,4 +132,107 @@ public abstract class CharacterTag extends Tag implements CharacterIdTag { } return "" + getCharacterId(); } + + private String getMembersToClassName(GraphTargetItem item) { + List ret = new ArrayList<>(); + while (item instanceof GetMemberActionItem) { + GetMemberActionItem mem = (GetMemberActionItem) item; + if (!(mem.memberName instanceof DirectValueActionItem)) { + return null; + } + DirectValueActionItem dv = ((DirectValueActionItem) mem.memberName); + if (!dv.isString()) { + return null; + } + ret.add(0, dv.getAsString()); + item = mem.object; + } + if (!(item instanceof GetVariableActionItem)) { + return null; + } + GetVariableActionItem gv = (GetVariableActionItem) item; + if (!(gv.name instanceof DirectValueActionItem)) { + return null; + } + DirectValueActionItem dv = ((DirectValueActionItem) gv.name); + if (!dv.isString()) { + return null; + } + String varName = dv.getAsString(); + ret.add(0, varName); + return String.join(".", ret); + } + + public String getAs2ClassName() { + String linkageIdentifier = getExportName(); + if (linkageIdentifier == null) { + return null; + } + Reference classNameRef = new Reference<>(null); + for (Tag t : getSwf().getTags()) { + if (t instanceof DoInitActionTag) { + DoInitActionTag as = (DoInitActionTag) t; + if (as.spriteId == getCharacterId()) { + GraphTextWriter writer = new NulWriter(); + try { + List ops = new ArrayList<>(); + ops.add(new ActionTreeOperation() { + @Override + public void run(List tree) { + List listToRemove = new ArrayList<>(); + List newClassNames = new ArrayList<>(); + for (int i = 0; i < tree.size(); i++) { + GraphTargetItem item = tree.get(i); + if (!(item instanceof CallMethodActionItem)) { + continue; + } + CallMethodActionItem callMethod = (CallMethodActionItem) item; + if (!(callMethod.scriptObject instanceof GetVariableActionItem)) { + continue; + } + GetVariableActionItem methodObject = (GetVariableActionItem) callMethod.scriptObject; + if (!(methodObject.name instanceof DirectValueActionItem)) { + continue; + } + if (methodObject.name == null || !methodObject.name.toString().equals("Object")) { + continue; + } + if (!(callMethod.methodName instanceof DirectValueActionItem)) { + continue; + } + if (!callMethod.methodName.toString().equals("registerClass")) { + continue; + } + if (callMethod.arguments.size() != 2) { + continue; + } + if (!(callMethod.arguments.get(0) instanceof DirectValueActionItem)) { + continue; + } + if (linkageIdentifier != null && !linkageIdentifier.equals(callMethod.arguments.get(0).toString())) { + continue; + } + String className = getMembersToClassName(callMethod.arguments.get(1)); + if (className == null) { + continue; + } + newClassNames.add(className); + listToRemove.add(i); + } + //There's only single one + if (listToRemove.size() != 1) { + return; + } + classNameRef.setVal(newClassNames.get(0)); + } + }); + as.getActionScriptSource(writer, null, ops); + } catch (InterruptedException ex) { + Logger.getLogger(CharacterTag.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + return classNameRef.getVal(); + } } diff --git a/src/com/jpexs/decompiler/flash/gui/AsLinkageDialog.java b/src/com/jpexs/decompiler/flash/gui/AsLinkageDialog.java new file mode 100644 index 000000000..7d815f3ec --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/AsLinkageDialog.java @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2010-2024 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.gui.abc.*; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineFont4Tag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.ExportAssetsTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.timeline.Timelined; +import java.awt.Component; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.util.HashMap; +import java.util.Map; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * + * @author JPEXS + */ +public class AsLinkageDialog extends AppDialog { + + private final JButton proceedButton = new JButton(translate("button.proceed")); + private final JButton cancelButton = new JButton(translate("button.cancel")); + + private final JTextField identifierTextField = new JTextField(30); + private final JTextField classNameTextField = new JTextField(30); + private final JTextField parentClassNameTextField = new JTextField(30); + private String selectedIdentifier = null; + private String selectedClass = null; + private String selectedParentClass = null; + private ExportAssetsTag originalExportAssetsTag; + private ExportAssetsTag selectedExportAssetsTag; + private String originalClassName; + private Tag selectedPosition; + private Timelined selectedTimelined; + private int result = ERROR_OPTION; + + private final SWF swf; + + private final int characterId; + + private int characterFrame = -1; + + private int exportAssetsCount = 0; + + private final JLabel errorLabel = new JLabel(); + + private final JLabel parentClassNameLabel = new JLabel(translate("class.parentname")); + + private final JRadioButton existingExportAssetsTagRadioButton = new JRadioButton(translate("linkage.notfound.exportAssets.where.existing")); + private final JRadioButton newExportAssetsTagRadioButton = new JRadioButton(translate("linkage.notfound.exportAssets.where.new")); + + private static final Map, String> tagTypeToParentClass = new HashMap<>(); + + static { + tagTypeToParentClass.put(SoundTag.class, "flash.media.Sound"); + tagTypeToParentClass.put(ImageTag.class, "flash.display.Bitmap"); + tagTypeToParentClass.put(FontTag.class, "flash.text.Font"); + tagTypeToParentClass.put(DefineFont4Tag.class, "flash.text.Font"); + tagTypeToParentClass.put(DefineBinaryDataTag.class, "flash.utils.ByteArray"); + tagTypeToParentClass.put(DefineSpriteTag.class, "flash.display.Sprite"); + } + + public static String getParentClassFromCharacter(CharacterTag ch) { + for (Class cls : tagTypeToParentClass.keySet()) { + if (cls.isAssignableFrom(ch.getClass())) { + return tagTypeToParentClass.get(cls); + } + } + return null; + } + + public AsLinkageDialog(Window owner, SWF swf, int characterId) { + super(owner); + + this.swf = swf; + this.characterId = characterId; + + CharacterTag ch = swf.getCharacter(characterId); + if (ch == null) { + throw new RuntimeException("Character " + characterId + " not found"); + } + + //parentClassNameTextField.setText(getParentClassFromCharacter(ch)); + + int frame = 1; + for (Tag t : swf.getTags()) { + if (t == ch) { + characterFrame = frame; + } + if (t instanceof ShowFrameTag) { + frame++; + } + } + frame = 1; + for (Tag t : swf.getTags()) { + if (frame >= characterFrame) { + if (t instanceof ExportAssetsTag) { + ExportAssetsTag ea = (ExportAssetsTag) t; + if (ea.tags.contains(characterId)) { + originalExportAssetsTag = ea; + } + exportAssetsCount++; + } + } + if (t instanceof ShowFrameTag) { + frame++; + } + } + + String originalIdentifier = ch.getExportName(); + + identifierTextField.setText(originalIdentifier); + + originalClassName = ch.getAs2ClassName(); + + if (originalClassName != null) { + classNameTextField.setText(originalClassName); + classNameTextField.setEnabled(false); + identifierTextField.setEnabled(false); + } + + setDefaultCloseOperation(HIDE_ON_CLOSE); + setTitle(translate("dialog.title")); + + Container cnt = getContentPane(); + cnt.setLayout(new BoxLayout(cnt, BoxLayout.Y_AXIS)); + + cnt.add(new JLabel(translate("identifier"))); + cnt.add(identifierTextField); + + cnt.add(new JLabel(translate("classname"))); + cnt.add(classNameTextField); + if (originalClassName == null) { + cnt.add(parentClassNameLabel); + cnt.add(parentClassNameTextField); + } + cnt.add(errorLabel); + + if (originalClassName == null && originalExportAssetsTag == null) { + ButtonGroup whereToStoreMappingButtonGroup = new ButtonGroup(); + whereToStoreMappingButtonGroup.add(existingExportAssetsTagRadioButton); + whereToStoreMappingButtonGroup.add(newExportAssetsTagRadioButton); + cnt.add(new JLabel(translate("linkage.notfound.exportAssets.where"))); + + JPanel whereToStoreMappingPanel = new JPanel(new FlowLayout()); + whereToStoreMappingPanel.add(existingExportAssetsTagRadioButton); + whereToStoreMappingPanel.add(newExportAssetsTagRadioButton); + cnt.add(whereToStoreMappingPanel); + existingExportAssetsTagRadioButton.setSelected(true); + } + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + proceedButton.addActionListener(this::okButtonActionPerformed); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + buttonsPanel.add(proceedButton); + buttonsPanel.add(cancelButton); + + cnt.add(buttonsPanel); + + setCentralAlignment((JComponent) cnt); + + if (exportAssetsCount == 0) { + newExportAssetsTagRadioButton.setSelected(true); + newExportAssetsTagRadioButton.setEnabled(false); + existingExportAssetsTagRadioButton.setEnabled(false); + } + + existingExportAssetsTagRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + checkEnabled(); + } + }); + newExportAssetsTagRadioButton.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + checkEnabled(); + } + }); + + classNameTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + checkEnabled(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + checkEnabled(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + checkEnabled(); + } + }); + + classNameTextField.addActionListener(this::okButtonActionPerformed); + checkEnabled(); + pack(); + setModal(true); + setResizable(false); + View.setWindowIcon(this); + View.centerScreen(this); + } + + private void setCentralAlignment(JComponent container) { + Component[] components = container.getComponents(); + for (Component component : components) { + if (component instanceof JComponent) { + ((JComponent) component).setAlignmentX(Component.CENTER_ALIGNMENT); + } + } + } + + private void checkEnabled() { + + boolean ok = true; + + String newIdentifier = identifierTextField.getText(); + String newClassName = classNameTextField.getText(); + + if (originalClassName != null) { + ok = false; + proceedButton.setText(translate("button.ok")); + errorLabel.setText(""); + } + + if (!classNameTextField.isEnabled() && newIdentifier.isEmpty()) { + ok = false; + proceedButton.setText(translate("button.ok")); + errorLabel.setText(translate("error.cannotRemoveIdentifierClassExists")); + } + + if (newClassName.endsWith(".")) { + ok = false; + proceedButton.setText(translate("button.ok")); + errorLabel.setText(""); + } + + if (newIdentifier.isEmpty() && !newClassName.isEmpty()) { + ok = false; + proceedButton.setText(translate("button.ok")); + errorLabel.setText(""); + } + + if (ok) { + CharacterTag ch = swf.getCharacter(characterId); + String oldClassName = ch.getAs2ClassName(); + if (oldClassName == null && newClassName != null && ch.getSwf().getCharacterByExportName("__Packages." + newClassName) != null) { + ok = false; + errorLabel.setText(translate("error.alreadyExistsClass")); + } + } + + if (ok) { + if (originalExportAssetsTag != null) { + proceedButton.setText(translate("button.ok")); + } else if (existingExportAssetsTagRadioButton.isSelected() && exportAssetsCount == 1) { + proceedButton.setText(translate("button.ok")); + } else { + proceedButton.setText(translate("button.proceed")); + } + } else { + proceedButton.setText(translate("button.ok")); + } + + proceedButton.setEnabled(ok); + } + + private void okButtonActionPerformed(ActionEvent evt) { + if (!proceedButton.isEnabled()) { + return; + } + setVisible(false); + if (originalExportAssetsTag != null) { + selectedExportAssetsTag = originalExportAssetsTag; + } else { + if (existingExportAssetsTagRadioButton.isSelected()) { + SelectTagOfTypeDialog selectExportAssetsDialog = new SelectTagOfTypeDialog(owner, swf, ExportAssetsTag.class, "ExportAssets", characterFrame); + selectedExportAssetsTag = (ExportAssetsTag) selectExportAssetsDialog.showDialog(); + if (selectedExportAssetsTag == null) { + cancelButtonActionPerformed(evt); + return; + } + } + if (newExportAssetsTagRadioButton.isSelected()) { + SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "ExportAssets", characterFrame); + if (selectTagPositionDialog.showDialog() != OK_OPTION) { + cancelButtonActionPerformed(evt); + return; + } + selectedPosition = selectTagPositionDialog.getSelectedTag(); + selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); + } + } + result = OK_OPTION; + selectedIdentifier = identifierTextField.getText(); + selectedClass = classNameTextField.getText(); + selectedParentClass = parentClassNameTextField.getText(); + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + selectedIdentifier = null; + selectedClass = null; + selectedParentClass = null; + selectedPosition = null; + selectedTimelined = null; + selectedExportAssetsTag = null; + characterFrame = -1; + result = CANCEL_OPTION; + setVisible(false); + } + + public int showDialog() { + selectedIdentifier = null; + selectedClass = null; + selectedParentClass = null; + selectedPosition = null; + selectedTimelined = null; + selectedExportAssetsTag = null; + setVisible(true); + return result; + } + + public Tag getSelectedPosition() { + return selectedPosition; + } + + public String getSelectedClass() { + return selectedClass; + } + + public String getSelectedParentClass() { + return selectedParentClass; + } + + public ExportAssetsTag getSelectedExportAssetsTag() { + return selectedExportAssetsTag; + } + + public Timelined getSelectedTimelined() { + return selectedTimelined; + } + + public int getCharacterFrame() { + return characterFrame; + } + + public String getSelectedIdentifier() { + return selectedIdentifier; + } + +} diff --git a/src/com/jpexs/decompiler/flash/gui/abc/As3ClassLinkageDialog.java b/src/com/jpexs/decompiler/flash/gui/abc/As3ClassLinkageDialog.java index 24f28c17d..45458d2ec 100644 --- a/src/com/jpexs/decompiler/flash/gui/abc/As3ClassLinkageDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/abc/As3ClassLinkageDialog.java @@ -73,6 +73,7 @@ public class As3ClassLinkageDialog extends AppDialog { private String selectedParentClass = null; private ABCContainerTag selectedAbcContainer; private SymbolClassTag selectedSymbolClassTag; + private SymbolClassTag originalSymbolClassTag; private Tag selectedPosition; private Timelined selectedTimelined; private int result = ERROR_OPTION; @@ -165,6 +166,10 @@ public class As3ClassLinkageDialog extends AppDialog { abcContainers.add((ABCContainerTag) t); } if (t instanceof SymbolClassTag) { + SymbolClassTag sc = (SymbolClassTag) t; + if (sc.tags.contains(characterId)) { + originalSymbolClassTag = sc; + } symbolClassCount++; } } @@ -240,12 +245,15 @@ public class As3ClassLinkageDialog extends AppDialog { ButtonGroup whereToStoreMappingButtonGroup = new ButtonGroup(); whereToStoreMappingButtonGroup.add(existingSymbolClassTagRadioButton); whereToStoreMappingButtonGroup.add(newSymbolClassTagRadioButton); - doNotCreateNewClassPanel.add(new JLabel(translate("class.notfound.onlySetClassName.symbolClass.where"))); + + if (originalSymbolClassTag == null) { + doNotCreateNewClassPanel.add(new JLabel(translate("class.notfound.onlySetClassName.symbolClass.where"))); - JPanel whereToStoreMappingPanel = new JPanel(new FlowLayout()); - whereToStoreMappingPanel.add(existingSymbolClassTagRadioButton); - whereToStoreMappingPanel.add(newSymbolClassTagRadioButton); - doNotCreateNewClassPanel.add(whereToStoreMappingPanel); + JPanel whereToStoreMappingPanel = new JPanel(new FlowLayout()); + whereToStoreMappingPanel.add(existingSymbolClassTagRadioButton); + whereToStoreMappingPanel.add(newSymbolClassTagRadioButton); + doNotCreateNewClassPanel.add(whereToStoreMappingPanel); + } existingSymbolClassTagRadioButton.setSelected(true); setCentralAlignment(doNotCreateNewClassPanel); @@ -381,10 +389,10 @@ public class As3ClassLinkageDialog extends AppDialog { String newClassName = classNameTextField.getText(); - if (newClassName.isEmpty()) { + /*if (newClassName.isEmpty()) { ok = false; errorLabel.setText(""); - } + }*/ if (newClassName.endsWith(".")) { ok = false; @@ -400,7 +408,7 @@ public class As3ClassLinkageDialog extends AppDialog { } else if (oldClassNames.size() == 1 && newClassName.equals(oldClassNames.iterator().next())) { ok = false; errorLabel.setText(translate("error.needToModify")); - } else if (swf.getCharacterByClass(newClassName) != null) { + } else if (!newClassName.isEmpty() && swf.getCharacterByClass(newClassName) != null) { ok = false; errorLabel.setText(translate("error.alreadyAssignedClass")); } @@ -409,42 +417,48 @@ public class As3ClassLinkageDialog extends AppDialog { CardLayout cl = (CardLayout) classFoundOrNotOrErrorPanel.getLayout(); if (ok) { - List classNames = new ArrayList<>(); - classNames.add(newClassName); - foundInAbcContainer = null; - try { - List scriptPacks = swf.getScriptPacksByClassNames(classNames); - if (!scriptPacks.isEmpty()) { - ABC foundInAbc = scriptPacks.get(0).abc; //Assume there is only single pack with the class + if (newClassName.isEmpty()) { + proceedButton.setText(translate("button.ok")); + cl.show(classFoundOrNotOrErrorPanel, ERROR_CARD); + errorLabel.setText(""); + } else { + List classNames = new ArrayList<>(); + classNames.add(newClassName); + foundInAbcContainer = null; + try { + List scriptPacks = swf.getScriptPacksByClassNames(classNames); + if (!scriptPacks.isEmpty()) { + ABC foundInAbc = scriptPacks.get(0).abc; //Assume there is only single pack with the class - boolean foundContainer = false; - for (ABCContainerTag cnt : abcContainers) { - if (cnt.getABC() == foundInAbc) { - foundInAbcContainer = cnt; - break; + boolean foundContainer = false; + for (ABCContainerTag cnt : abcContainers) { + if (cnt.getABC() == foundInAbc) { + foundInAbcContainer = cnt; + break; + } } } + } catch (Exception ex) { + //ignore } - } catch (Exception ex) { - //ignore - } - if (foundInAbcContainer != null) { - cl.show(classFoundOrNotOrErrorPanel, CLASS_FOUND_CARD); - } else { - cl.show(classFoundOrNotOrErrorPanel, CLASS_NOT_FOUND_CARD); - - if (createClassRadioButton.isSelected()) { - if (existingAbcTagRadioButton.isSelected() && abcCount == 1) { - proceedButton.setText(translate("button.ok")); - } else { - proceedButton.setText(translate("button.proceed")); - } + if (foundInAbcContainer != null) { + cl.show(classFoundOrNotOrErrorPanel, CLASS_FOUND_CARD); } else { - if (existingSymbolClassTagRadioButton.isSelected() && symbolClassCount == 1) { - proceedButton.setText(translate("button.ok")); + cl.show(classFoundOrNotOrErrorPanel, CLASS_NOT_FOUND_CARD); + + if (createClassRadioButton.isSelected()) { + if (existingAbcTagRadioButton.isSelected() && abcCount == 1) { + proceedButton.setText(translate("button.ok")); + } else { + proceedButton.setText(translate("button.proceed")); + } } else { - proceedButton.setText(translate("button.proceed")); + if (originalSymbolClassTag != null || (existingSymbolClassTagRadioButton.isSelected() && symbolClassCount == 1)) { + proceedButton.setText(translate("button.ok")); + } else { + proceedButton.setText(translate("button.proceed")); + } } } } @@ -461,46 +475,55 @@ public class As3ClassLinkageDialog extends AppDialog { return; } setVisible(false); - if (foundInAbcContainer == null) { - if (createClassRadioButton.isSelected()) { - if (existingAbcTagRadioButton.isSelected()) { - SelectTagOfTypeDialog selectDoABCDialog = new SelectTagOfTypeDialog(owner, swf, ABCContainerTag.class, "DoABC", characterFrame); - selectedAbcContainer = (ABCContainerTag) selectDoABCDialog.showDialog(); - if (selectedAbcContainer == null) { - cancelButtonActionPerformed(evt); - return; + boolean emptyClassName = classNameTextField.getText().isEmpty(); + if (emptyClassName) { + selectedSymbolClassTag = originalSymbolClassTag; + } else { + if (foundInAbcContainer == null) { + if (createClassRadioButton.isSelected()) { + if (existingAbcTagRadioButton.isSelected()) { + SelectTagOfTypeDialog selectDoABCDialog = new SelectTagOfTypeDialog(owner, swf, ABCContainerTag.class, "DoABC", characterFrame); + selectedAbcContainer = (ABCContainerTag) selectDoABCDialog.showDialog(); + if (selectedAbcContainer == null) { + cancelButtonActionPerformed(evt); + return; + } } - } - if (newAbcTagRadioButton.isSelected()) { - SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "DoABC", characterFrame); - if (selectTagPositionDialog.showDialog() != OK_OPTION) { - cancelButtonActionPerformed(evt); - return; + if (newAbcTagRadioButton.isSelected()) { + SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "DoABC", characterFrame); + if (selectTagPositionDialog.showDialog() != OK_OPTION) { + cancelButtonActionPerformed(evt); + return; + } + selectedPosition = selectTagPositionDialog.getSelectedTag(); + selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); + } + } else { + if (originalSymbolClassTag != null) { + selectedSymbolClassTag = originalSymbolClassTag; + } else { + if (existingSymbolClassTagRadioButton.isSelected()) { + SelectTagOfTypeDialog selectSymbolClassDialog = new SelectTagOfTypeDialog(owner, swf, SymbolClassTag.class, "SymbolClass", characterFrame); + selectedSymbolClassTag = (SymbolClassTag) selectSymbolClassDialog.showDialog(); + if (selectedSymbolClassTag == null) { + cancelButtonActionPerformed(evt); + return; + } + } + if (newSymbolClassTagRadioButton.isSelected()) { + SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "SymbolClass", characterFrame); + if (selectTagPositionDialog.showDialog() != OK_OPTION) { + cancelButtonActionPerformed(evt); + return; + } + selectedPosition = selectTagPositionDialog.getSelectedTag(); + selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); + } } - selectedPosition = selectTagPositionDialog.getSelectedTag(); - selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); } } else { - if (existingSymbolClassTagRadioButton.isSelected()) { - SelectTagOfTypeDialog selectSymbolClassDialog = new SelectTagOfTypeDialog(owner, swf, SymbolClassTag.class, "SymbolClass", characterFrame); - selectedSymbolClassTag = (SymbolClassTag) selectSymbolClassDialog.showDialog(); - if (selectedSymbolClassTag == null) { - cancelButtonActionPerformed(evt); - return; - } - } - if (newSymbolClassTagRadioButton.isSelected()) { - SelectTagPositionDialog selectTagPositionDialog = new SelectTagPositionDialog(owner, swf, false, "SymbolClass", characterFrame); - if (selectTagPositionDialog.showDialog() != OK_OPTION) { - cancelButtonActionPerformed(evt); - return; - } - selectedPosition = selectTagPositionDialog.getSelectedTag(); - selectedTimelined = selectTagPositionDialog.getSelectedTimelined(); - } + selectedAbcContainer = foundInAbcContainer; } - } else { - selectedAbcContainer = foundInAbcContainer; } if (selectedAbcContainer != null) { @@ -516,7 +539,7 @@ public class As3ClassLinkageDialog extends AppDialog { } } - createClass = foundInAbcContainer == null && createClassRadioButton.isSelected(); + createClass = !emptyClassName && foundInAbcContainer == null && createClassRadioButton.isSelected(); result = OK_OPTION; selectedClass = classNameTextField.getText(); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AsLinkageDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AsLinkageDialog.properties new file mode 100644 index 000000000..29fb56594 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/AsLinkageDialog.properties @@ -0,0 +1,30 @@ +# Copyright (C) 2024 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = AS linkage +button.ok = OK +button.proceed = Proceed +button.cancel = Cancel + +identifier = ActionScript identifier: +classname = ActionScript2 class (fully qualified): +class.parentname = Parent class name (fully qualified): + +error.alreadyExistsClass = Error: This class is already exists +error.cannotRemoveIdentifierClassExists = Error: Cannot remove identifier, class already exists + +linkage.notfound.exportAssets.where = Where to store the linkage data: +linkage.notfound.exportAssets.where.existing = Existing ExportAssets tag +linkage.notfound.exportAssets.where.new = New ExportAssets tag \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AsLinkageDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AsLinkageDialog_cs.properties new file mode 100644 index 000000000..fd42c2662 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/AsLinkageDialog_cs.properties @@ -0,0 +1,30 @@ +# Copyright (C) 2024 JPEXS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +dialog.title = AS vazba +button.ok = OK +button.proceed = Pokra\u010dovat +button.cancel = Storno + +identifier = ActionScript identifik\u00e1tor: +classname = ActionScript2 t\u0159\u00edda (pln\u011b kvalifikovan\u00e1): +class.parentname = N\u00e1zev rodi\u010dovsk\u00e9 t\u0159\u00eddy (pln\u011b kvalifikovan\u00fd): + +error.alreadyExistsClass = Chyba: Tato t\u0159\u00edda ji\u017e existuje +error.cannotRemoveIdentifierClassExists = Chyba: Nelze odstranit idenitifik\u00e1tor, t\u0159\u00edda ji\u017e existuje + +linkage.notfound.exportAssets.where = Kam um\u00edstit data o vazb\u011b: +linkage.notfound.exportAssets.where.existing = Existuj\u00edc\u00ed ExportAssets tag +linkage.notfound.exportAssets.where.new = Nov\u00fd ExportAssets tag \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 602404977..5e95fcab9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1271,4 +1271,6 @@ warning.cleanAbc = This action will remove items from ABC which have zero usages tagInfo.idType = Type of the id -contextmenu.configurePathResolving = Configure path resolving... \ No newline at end of file +contextmenu.configurePathResolving = Configure path resolving... + +contextmenu.setAsLinkage = Set AS linkage \ 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 971ac0cfd..6ed0ae388 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1246,4 +1246,6 @@ warning.cleanAbc = Tato akce odstran\u00ed z ABC polo\u017eky, kter\u00e9 maj\u0 tagInfo.idType = Typ id -contextmenu.configurePathResolving = Nastavit resolvov\u00e1n\u00ed cest... \ No newline at end of file +contextmenu.configurePathResolving = Nastavit resolvov\u00e1n\u00ed cest... + +contextmenu.setAsLinkage = Nastavit AS vazbu \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog.properties index 0dff7875c..f258210c9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog.properties @@ -38,6 +38,6 @@ class.notfound.create.abc.where.existing = Existing DoABC tag class.notfound.create.abc.where.new = New DoABC tag class.notfound.onlySetClassName = No, just assign class name -class.notfound.onlySetClassName.symbolClass.where = Where to store the mapping: +class.notfound.onlySetClassName.symbolClass.where = Where to store the linkage data: class.notfound.onlySetClassName.symbolClass.where.existing = Existing SymbolClass tag class.notfound.onlySetClassName.symbolClass.where.new = New SymbolClass tag \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog_cs.properties index f39dce287..0e05286aa 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/abc/As3ClassLinkageDialog_cs.properties @@ -38,6 +38,6 @@ class.notfound.create.abc.where.existing = Existuj\u00edc\u00ed DoABC tag class.notfound.create.abc.where.new = Nov\u00fd DoABC tag class.notfound.onlySetClassName = Ne, jen p\u0159i\u0159adit n\u00e1zev t\u0159\u00eddy -class.notfound.onlySetClassName.symbolClass.where = Kam um\u00edstit mapov\u00e1n\u00ed: +class.notfound.onlySetClassName.symbolClass.where = Kam um\u00edstit data o vazb\u011b: class.notfound.onlySetClassName.symbolClass.where.existing = Existuj\u00edc\u00ed SymbolClass tag class.notfound.onlySetClassName.symbolClass.where.new = Nov\u00fd SymbolClass 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 5e2cefe67..f4a649875 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -36,6 +36,7 @@ import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys; import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration; import com.jpexs.decompiler.flash.gui.AppDialog; import com.jpexs.decompiler.flash.gui.AppStrings; +import com.jpexs.decompiler.flash.gui.AsLinkageDialog; import com.jpexs.decompiler.flash.gui.ClipboardType; import com.jpexs.decompiler.flash.gui.CollectDepthAsSpritesDialog; import com.jpexs.decompiler.flash.gui.Main; @@ -174,6 +175,8 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem configurePathResolvingMenuItem; + private JMenuItem setAsLinkageMenuItem; + private JMenuItem setAs3ClassLinkageMenuItem; private JMenuItem expandRecursiveMenuItem; @@ -451,7 +454,12 @@ public class TagTreeContextMenu extends JPopupMenu { replaceRefsWithTagMenuItem.setIcon(View.getIcon("replacewithtag16")); add(replaceRefsWithTagMenuItem); - setAs3ClassLinkageMenuItem = new JMenuItem(mainPanel.translate("contextmenu.setAs3ClassLinkage")); //FIXME + setAsLinkageMenuItem = new JMenuItem(mainPanel.translate("contextmenu.setAsLinkage")); + setAsLinkageMenuItem.addActionListener(this::setAsLinkageActionPerformed); + setAsLinkageMenuItem.setIcon(View.getIcon("asclass16")); + add(setAsLinkageMenuItem); + + setAs3ClassLinkageMenuItem = new JMenuItem(mainPanel.translate("contextmenu.setAs3ClassLinkage")); setAs3ClassLinkageMenuItem.addActionListener(this::setAs3ClassLinkageActionPerformed); setAs3ClassLinkageMenuItem.setIcon(View.getIcon("asclass16")); add(setAs3ClassLinkageMenuItem); @@ -1126,6 +1134,7 @@ public class TagTreeContextMenu extends JPopupMenu { boolean hasExportableNodes = tree.hasExportableNodes(); + setAsLinkageMenuItem.setVisible(false); setAs3ClassLinkageMenuItem.setVisible(false); expandRecursiveMenuItem.setVisible(false); collapseRecursiveMenuItem.setVisible(false); @@ -1372,6 +1381,12 @@ public class TagTreeContextMenu extends JPopupMenu { if (cht.getSwf().isAS3() && As3ClassLinkageDialog.getParentClassFromCharacter(cht) != null) { setAs3ClassLinkageMenuItem.setVisible(true); } + if (!cht.getSwf().isAS3()) { + String ename = cht.getExportName(); + if (ename == null || !ename.startsWith("__Packages.")) { + setAsLinkageMenuItem.setVisible(true); + } + } } if (tree.getModel().getChildCount(firstItem) > 0) { @@ -2546,13 +2561,175 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.setTagTreeSelectedNode(mainPanel.getCurrentTree(), ((SWF) itemj.getOpenable()).getCharacter(hasCharacterId.getCharacterId())); } + /* + How setting AS linkage should work: + User selects linkage identifier (the one really stored in ExportAssets of the character) + If the file is AS1/2: + If exists DoInitAction tag for that character, that has Object.registerClass("oldLinkageIdentifier", cls) + readonly class name is displayed + readonly identifier is displayed + exit + else + (optional) User selects desired AS2 classname and parent class name to be created + If the linkage did not exist before (no ExportAssets had such id) and linkage identifier is not empty, + user can select whether to use existing ExportAssets tag (if some exists) or to create new + + On pressing OK: + If the linkage did not exist before (no ExportAssets had such id) and the new identifier is not empty, + user selects existing ExportAssets tag via dialog or selects position for new + if the dialog is cancelled, exit + If the file is AS1/2: + If the user choosed to create new class (cls is its fully qualified name) + Frame of Exportassets is determined + New DoInitAction for the character is created in the EA frame which has Object.registerClass("linkageIdentifier", cls) in code + New empty DefineSprite is created in frame 1, and its name in ExportAssets tag is set to "__Packages." + cls + New DoInitAction for the DefineSprite in frame 1 is created and it's filled with new cls Class code + The Exportassets tag is modified with the new linkage identifier + */ + private void setAsLinkageActionPerformed(ActionEvent evt) { + CharacterTag ch = (CharacterTag) getCurrentItem(); + SWF swf = ch.getSwf(); + AsLinkageDialog d = new AsLinkageDialog(Main.getDefaultDialogsOwner(), swf, ch.getCharacterId()); + if (d.showDialog() != AppDialog.OK_OPTION) { + return; + } + String identifier = d.getSelectedIdentifier(); + String className = d.getSelectedClass(); + String classParent = d.getSelectedParentClass(); + ExportAssetsTag ea = d.getSelectedExportAssetsTag(); + + if (ea == null) { + ea = new ExportAssetsTag(swf); + ea.setTimelined(swf); + if (d.getSelectedPosition() == null) { + swf.addTag(ea); + } else { + swf.addTag(swf.indexOfTag(d.getSelectedPosition()), ea); + } + } + + if (!className.isEmpty()) { + int frame = 1; + int eaFrame = -1; + int regInsertPos = -1; + ReadOnlyTagList tags = swf.getTags(); + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t == ea) { + eaFrame = frame; + } + if (t instanceof ShowFrameTag) { + if (frame == eaFrame) { + regInsertPos = i; + break; + } + frame++; + } + } + + DoInitActionTag regDoInit = new DoInitActionTag(swf); + regDoInit.spriteId = ch.getCharacterId(); + regDoInit.setTimelined(swf); + + ActionScript2Parser regParser = new ActionScript2Parser(swf, regDoInit); + + String[] parts = className.contains(".") ? className.split("\\.") : new String[]{className}; + DottedChain classDottedChain = new DottedChain(parts); + + try { + List regActions = regParser.actionsFromString("Object.registerClass(\"" + Helper.escapePCodeString(identifier) + "\"," + classDottedChain.toPrintableString(false) + ");", swf.getCharset()); + regDoInit.setActions(regActions); + } catch (ActionParseException | IOException | CompilationException | InterruptedException ex) { + //ignore + } + + swf.addTag(regInsertPos, regDoInit); + + int insertPos = 0; + + tags = swf.getTags(); + + for (int i = 0; i < tags.size(); i++) { + Tag t = tags.get(i); + if (t instanceof ShowFrameTag) { + insertPos = i; + break; + } + } + + + int classCharacterId = swf.getNextCharacterId(); + DefineSpriteTag classSprite = new DefineSpriteTag(swf); + classSprite.spriteId = classCharacterId; + classSprite.hasEndTag = true; + classSprite.setTimelined(swf); + + String exportName = "__Packages." + className; + + ExportAssetsTag classExportAssets = new ExportAssetsTag(swf); + classExportAssets.names = new ArrayList<>(); + classExportAssets.names.add(exportName); + classExportAssets.tags = new ArrayList<>(); + classExportAssets.tags.add(classCharacterId); + classExportAssets.setTimelined(swf); + + DoInitActionTag classDoInit = new DoInitActionTag(swf); + classDoInit.spriteId = classCharacterId; + classDoInit.setTimelined(swf); + + ActionScript2Parser parser = new ActionScript2Parser(swf, classDoInit); + + String[] partsParent = classParent.contains(".") ? classParent.split("\\.") : new String[]{classParent}; + DottedChain dcParent = new DottedChain(partsParent); + + try { + List actions = parser.actionsFromString("class " + classDottedChain.toPrintableString(false) + (classParent.isEmpty() ? "" : " extends " + dcParent.toPrintableString(false)) + "{}", swf.getCharset()); + classDoInit.setActions(actions); + } catch (ActionParseException | IOException | CompilationException | InterruptedException ex) { + //ignore + } + + classSprite.setExportName(exportName); + + swf.addTag(insertPos, classSprite); + swf.addTag(insertPos + 1, classExportAssets); + swf.addTag(insertPos + 2, classDoInit); + + } + + boolean found = false; + for (int i = ea.names.size() - 1; i >= 0; i--) { + if (ea.tags.get(i) == ch.getCharacterId()) { + if (identifier.isEmpty()) { + ea.tags.remove(i); + ea.names.remove(i); + } else { + ea.names.set(i, identifier); + found = true; + } + } + } + if (!identifier.isEmpty() && !found) { + ea.tags.add(ch.getCharacterId()); + ea.names.add(identifier); + } + ea.setModified(true); + if (ea.names.isEmpty()) { + swf.removeTag(ea); + } + + swf.clearAllCache(); + swf.assignExportNamesToSymbols(); + swf.setModified(true); + mainPanel.refreshTree(swf); + } /* How set class to character mapping work in AS3: a) a character is selected b) if the character already has assigned more than 1 class then exit with a message that user must do this manually c) new class name is entered, must be different than previous d) if new class name is already assigned to different character, exit - e) find the ABC, where class with that name is defined + e) if the classname is not empty find the ABC, where class with that name is defined if found determine its frame find nearest frame >= ABC frame, such that the character is defined in it (or is defined earlier in the file) @@ -2569,14 +2746,15 @@ public class TagTreeContextMenu extends JPopupMenu { otherwise create new SymbolClass in that frame or suggest not creating class, set only its name - choose whether to use existing Symbolclass or add new - AFTER PRESSING OK: - select existing SymbolClass or add new SymbolClass (on selected position). - (Selected frame for target SymbolClass must have defined the character in it or earlier in the file) + if the character did not have assigned name before (= no SymbolClass used) + choose whether to use existing Symbolclass or add new + AFTER PRESSING OK: + select existing SymbolClass or add new SymbolClass (on selected position). + (Selected frame for target SymbolClass must have defined the character in it or earlier in the file) AFTER PRESSING OK: I. if there is previously assigned classname, find associated SymbolClass and remove the mapping from it - II. if the previous Symbolclass is empty, remove it - III. add new character mapping to new SymbolClass determined + II. if the previous Symbolclass is empty and is not target SymbolClass, remove it + III. if the new classname is not empty, add new character mapping to new SymbolClass determined */ private void setAs3ClassLinkageActionPerformed(ActionEvent evt) { CharacterTag ch = (CharacterTag) getCurrentItem(); @@ -2592,59 +2770,73 @@ public class TagTreeContextMenu extends JPopupMenu { SymbolClassTag selectedSymbolClass = d.getSelectedSymbolClassTag(); ABCContainerTag selectedAbcContainer = d.getSelectedAbcContainer(); String className = d.getSelectedClass(); - String parentClassName = d.getSelectedParentClass(); - - if (!d.isClassFound() && !d.doCreateClass() && selectedSymbolClass == null) { //we selected position of new SymbolClass - selectedSymbolClass = new SymbolClassTag(swf); - selectedSymbolClass.setTimelined(swf); - Tag pos = d.getSelectedPosition(); - if (pos == null) { - swf.addTag(selectedSymbolClass); - } else { - swf.addTag(swf.indexOfTag(pos), selectedSymbolClass); + + if (className.isEmpty() && !ch.getClassNames().isEmpty()) { + SymbolClassTag sct = d.getSelectedSymbolClassTag(); + for (int i = sct.tags.size() - 1; i >= 0; i--) { + if (sct.tags.get(i) == ch.getCharacterId()) { + sct.names.remove(i); + sct.tags.remove(i); + } } - } + if (sct.names.isEmpty()) { + swf.removeTag(sct); + } + } else { + String parentClassName = d.getSelectedParentClass(); - if (!d.isClassFound() && d.doCreateClass()) { - if (d.getSelectedAbcContainer() == null) { - selectedAbcContainer = new DoABC2Tag(swf); - ((Tag) selectedAbcContainer).setTimelined(swf); + if (!d.isClassFound() && !d.doCreateClass() && selectedSymbolClass == null) { //we selected position of new SymbolClass + selectedSymbolClass = new SymbolClassTag(swf); + selectedSymbolClass.setTimelined(swf); Tag pos = d.getSelectedPosition(); if (pos == null) { - swf.addTag((Tag) selectedAbcContainer); + swf.addTag(selectedSymbolClass); } else { - swf.addTag(swf.indexOfTag(pos), (Tag) selectedAbcContainer); + swf.addTag(swf.indexOfTag(pos), selectedSymbolClass); } } - String pkg = className.contains(".") ? className.substring(0, className.lastIndexOf(".")) : ""; - String classSimpleName = className.contains(".") ? className.substring(className.lastIndexOf(".") + 1) : className; - String fileName = className.replace(".", "/"); - String[] pkgParts = new String[0]; - if (!pkg.isEmpty()) { - if (pkg.contains(".")) { - pkgParts = pkg.split("\\."); - } else { - pkgParts = new String[]{pkg}; + if (!d.isClassFound() && d.doCreateClass()) { + if (d.getSelectedAbcContainer() == null) { + selectedAbcContainer = new DoABC2Tag(swf); + ((Tag) selectedAbcContainer).setTimelined(swf); + Tag pos = d.getSelectedPosition(); + if (pos == null) { + swf.addTag((Tag) selectedAbcContainer); + } else { + swf.addTag(swf.indexOfTag(pos), (Tag) selectedAbcContainer); + } } - } - try { - AbcIndexing abcIndex = swf.getAbcIndex(); - abcIndex.selectAbc(selectedAbcContainer.getABC()); - ActionScript3Parser parser = new ActionScript3Parser(abcIndex); - DottedChain dc = new DottedChain(pkgParts); - String script = "package " + dc.toPrintableString(true) + " {" - + (parentClassName.isEmpty() ? "" : "import " + parentClassName + ";") - + "public class " + IdentifiersDeobfuscation.printIdentifier(true, classSimpleName) + (parentClassName.isEmpty() ? "" : " extends " + parentClassName) + " {" - + " }" - + "}"; - parser.addScript(script, fileName, 0, 0, swf.getDocumentClass()); - } catch (IOException | InterruptedException | AVM2ParseException | CompilationException ex) { - Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, "Error during script compilation", ex); - } + String pkg = className.contains(".") ? className.substring(0, className.lastIndexOf(".")) : ""; + String classSimpleName = className.contains(".") ? className.substring(className.lastIndexOf(".") + 1) : className; + String fileName = className.replace(".", "/"); + String[] pkgParts = new String[0]; + if (!pkg.isEmpty()) { + if (pkg.contains(".")) { + pkgParts = pkg.split("\\."); + } else { + pkgParts = new String[]{pkg}; + } + } + try { + AbcIndexing abcIndex = swf.getAbcIndex(); + abcIndex.selectAbc(selectedAbcContainer.getABC()); + ActionScript3Parser parser = new ActionScript3Parser(abcIndex); - ((Tag) selectedAbcContainer).setModified(true); + DottedChain dc = new DottedChain(pkgParts); + String script = "package " + dc.toPrintableString(true) + " {" + + (parentClassName.isEmpty() ? "" : "import " + parentClassName + ";") + + "public class " + IdentifiersDeobfuscation.printIdentifier(true, classSimpleName) + (parentClassName.isEmpty() ? "" : " extends " + parentClassName) + " {" + + " }" + + "}"; + parser.addScript(script, fileName, 0, 0, swf.getDocumentClass()); + } catch (IOException | InterruptedException | AVM2ParseException | CompilationException ex) { + Logger.getLogger(TagTreeContextMenu.class.getName()).log(Level.SEVERE, "Error during script compilation", ex); + } + + ((Tag) selectedAbcContainer).setModified(true); + } } if (selectedSymbolClass == null) {