/*
* Copyright (C) 2010-2016 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.decompiler.flash.tags;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.abc.CopyOutputStream;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
import com.jpexs.decompiler.flash.tags.base.CharacterIdTag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.Exportable;
import com.jpexs.decompiler.flash.tags.base.NeedsCharacters;
import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont;
import com.jpexs.decompiler.flash.tags.gfx.DefineExternalGradient;
import com.jpexs.decompiler.flash.tags.gfx.DefineExternalImage;
import com.jpexs.decompiler.flash.tags.gfx.DefineExternalImage2;
import com.jpexs.decompiler.flash.tags.gfx.DefineExternalSound;
import com.jpexs.decompiler.flash.tags.gfx.DefineExternalStreamSound;
import com.jpexs.decompiler.flash.tags.gfx.DefineGradientMap;
import com.jpexs.decompiler.flash.tags.gfx.DefineSubImage;
import com.jpexs.decompiler.flash.tags.gfx.ExporterInfo;
import com.jpexs.decompiler.flash.tags.gfx.FontTextureInfo;
import com.jpexs.decompiler.flash.timeline.Timelined;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents Tag inside SWF file
*
* @author JPEXS
*/
public abstract class Tag implements NeedsCharacters, Exportable, Serializable {
/**
* Identifier of tag type
*/
protected int id;
/**
* If true, then Tag is written to the stream as longer than 0x3f even if it
* is not
*/
@Internal
public boolean forceWriteAsLong = false;
protected String tagName;
@Internal
protected transient SWF swf;
@Internal
protected transient Timelined timelined;
@Internal
private boolean modified;
@Internal
protected boolean imported = false;
public void setImported(boolean imported) {
this.imported = imported;
}
public boolean isImported() {
return imported;
}
/**
* Original tag data
*/
@Internal
private ByteArrayRange originalRange;
private final HashSet<TagChangedListener> listeners = new HashSet<>();
@Internal
public ByteArrayRange remainingData;
/**
* Constructor
*
* @param swf The SWF
* @param id Tag type identifier
* @param name Tag name
* @param data Original tag data
*/
public Tag(SWF swf, int id, String name, ByteArrayRange data) {
this.id = id;
this.tagName = name;
this.originalRange = data;
this.swf = swf;
if (swf == null) {
throw new Error("swf parameter cannot be null.");
}
if (data == null) { // it is tag build by constructor
modified = true;
}
}
public String getTagName() {
return tagName;
}
public String getName() {
return tagName;
}
@Override
public String getExportFileName() {
return tagName;
}
/**
* Returns identifier of tag type
*
* @return Identifier of tag type
*/
public int getId() {
return id;
}
@Override
public SWF getSwf() {
return swf;
}
public void setSwf(SWF swf) {
setSwf(swf, false);
}
public void setSwf(SWF swf, boolean deep) {
this.swf = swf;
if (deep) {
if (this instanceof DefineSpriteTag) {
DefineSpriteTag sprite = (DefineSpriteTag) this;
for (Tag subTag : sprite.getTags()) {
subTag.setSwf(swf);
}
}
}
}
public Timelined getTimelined() {
return timelined;
}
public void setTimelined(Timelined timelined) {
this.timelined = timelined;
}
public abstract void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException, InterruptedException;
private static final Object lockObject = new Object();
private volatile static Integer[] knownTagIds;
private volatile static Map<Integer, TagTypeInfo> knownTagInfosById;
private volatile static Map<String, TagTypeInfo> knownTagInfosByName;
private volatile static List<Integer> requiredTagIds;
public static Integer[] getKnownTags() {
if (knownTagIds == null) {
synchronized (lockObject) {
if (knownTagIds == null) {
Set<Integer> keySet = getKnownClasses().keySet();
Integer[] tagIds = keySet.toArray(new Integer[keySet.size()]);
knownTagIds = tagIds;
}
}
}
return knownTagIds;
}
public static Map<Integer, TagTypeInfo> getKnownClasses() {
if (knownTagInfosById == null) {
synchronized (lockObject) {
if (knownTagInfosById == null) {
Map<Integer, TagTypeInfo> map = new HashMap<>();
Map<String, TagTypeInfo> map2 = new HashMap<>();
addTagInfo(map, map2, CSMTextSettingsTag.ID, CSMTextSettingsTag.class, CSMTextSettingsTag.NAME);
addTagInfo(map, map2, DebugIDTag.ID, DebugIDTag.class, DebugIDTag.NAME);
addTagInfo(map, map2, DefineBinaryDataTag.ID, DefineBinaryDataTag.class, DefineBinaryDataTag.NAME);
addTagInfo(map, map2, DefineBitsJPEG2Tag.ID, DefineBitsJPEG2Tag.class, DefineBitsJPEG2Tag.NAME);
addTagInfo(map, map2, DefineBitsJPEG3Tag.ID, DefineBitsJPEG3Tag.class, DefineBitsJPEG3Tag.NAME);
addTagInfo(map, map2, DefineBitsJPEG4Tag.ID, DefineBitsJPEG4Tag.class, DefineBitsJPEG4Tag.NAME);
addTagInfo(map, map2, DefineBitsLossless2Tag.ID, DefineBitsLossless2Tag.class, DefineBitsLossless2Tag.NAME);
addTagInfo(map, map2, DefineBitsLosslessTag.ID, DefineBitsLosslessTag.class, DefineBitsLosslessTag.NAME);
addTagInfo(map, map2, DefineBitsTag.ID, DefineBitsTag.class, DefineBitsTag.NAME);
addTagInfo(map, map2, DefineButton2Tag.ID, DefineButton2Tag.class, DefineButton2Tag.NAME);
addTagInfo(map, map2, DefineButtonCxformTag.ID, DefineButtonCxformTag.class, DefineButtonCxformTag.NAME);
addTagInfo(map, map2, DefineButtonSoundTag.ID, DefineButtonSoundTag.class, DefineButtonSoundTag.NAME);
addTagInfo(map, map2, DefineButtonTag.ID, DefineButtonTag.class, DefineButtonTag.NAME);
addTagInfo(map, map2, DefineEditTextTag.ID, DefineEditTextTag.class, DefineEditTextTag.NAME);
addTagInfo(map, map2, DefineFont2Tag.ID, DefineFont2Tag.class, DefineFont2Tag.NAME);
addTagInfo(map, map2, DefineFont3Tag.ID, DefineFont3Tag.class, DefineFont3Tag.NAME);
addTagInfo(map, map2, DefineFont4Tag.ID, DefineFont4Tag.class, DefineFont4Tag.NAME);
addTagInfo(map, map2, DefineFontAlignZonesTag.ID, DefineFontAlignZonesTag.class, DefineFontAlignZonesTag.NAME);
addTagInfo(map, map2, DefineFontInfo2Tag.ID, DefineFontInfo2Tag.class, DefineFontInfo2Tag.NAME);
addTagInfo(map, map2, DefineFontInfoTag.ID, DefineFontInfoTag.class, DefineFontInfoTag.NAME);
addTagInfo(map, map2, DefineFontNameTag.ID, DefineFontNameTag.class, DefineFontNameTag.NAME);
addTagInfo(map, map2, DefineFontTag.ID, DefineFontTag.class, DefineFontTag.NAME);
addTagInfo(map, map2, DefineMorphShape2Tag.ID, DefineMorphShape2Tag.class, DefineMorphShape2Tag.NAME);
addTagInfo(map, map2, DefineMorphShapeTag.ID, DefineMorphShapeTag.class, DefineMorphShapeTag.NAME);
addTagInfo(map, map2, DefineScalingGridTag.ID, DefineScalingGridTag.class, DefineScalingGridTag.NAME);
addTagInfo(map, map2, DefineSceneAndFrameLabelDataTag.ID, DefineSceneAndFrameLabelDataTag.class, DefineSceneAndFrameLabelDataTag.NAME);
addTagInfo(map, map2, DefineShape2Tag.ID, DefineShape2Tag.class, DefineShape2Tag.NAME);
addTagInfo(map, map2, DefineShape3Tag.ID, DefineShape3Tag.class, DefineShape3Tag.NAME);
addTagInfo(map, map2, DefineShape4Tag.ID, DefineShape4Tag.class, DefineShape4Tag.NAME);
addTagInfo(map, map2, DefineShapeTag.ID, DefineShapeTag.class, DefineShapeTag.NAME);
addTagInfo(map, map2, DefineSoundTag.ID, DefineSoundTag.class, DefineSoundTag.NAME);
addTagInfo(map, map2, DefineSpriteTag.ID, DefineSpriteTag.class, DefineSpriteTag.NAME);
addTagInfo(map, map2, DefineText2Tag.ID, DefineText2Tag.class, DefineText2Tag.NAME);
addTagInfo(map, map2, DefineTextTag.ID, DefineTextTag.class, DefineTextTag.NAME);
addTagInfo(map, map2, DefineVideoStreamTag.ID, DefineVideoStreamTag.class, DefineVideoStreamTag.NAME);
addTagInfo(map, map2, DoABC2Tag.ID, DoABC2Tag.class, DoABC2Tag.NAME);
addTagInfo(map, map2, DoABCTag.ID, DoABCTag.class, DoABCTag.NAME);
addTagInfo(map, map2, DoActionTag.ID, DoActionTag.class, DoActionTag.NAME);
addTagInfo(map, map2, DoInitActionTag.ID, DoInitActionTag.class, DoInitActionTag.NAME);
addTagInfo(map, map2, EnableDebugger2Tag.ID, EnableDebugger2Tag.class, EnableDebugger2Tag.NAME);
addTagInfo(map, map2, EnableDebuggerTag.ID, EnableDebuggerTag.class, EnableDebuggerTag.NAME);
addTagInfo(map, map2, EnableTelemetryTag.ID, EnableTelemetryTag.class, EnableTelemetryTag.NAME);
addTagInfo(map, map2, EndTag.ID, EndTag.class, EndTag.NAME);
addTagInfo(map, map2, ExportAssetsTag.ID, ExportAssetsTag.class, ExportAssetsTag.NAME);
addTagInfo(map, map2, FileAttributesTag.ID, FileAttributesTag.class, FileAttributesTag.NAME);
addTagInfo(map, map2, FrameLabelTag.ID, FrameLabelTag.class, FrameLabelTag.NAME);
addTagInfo(map, map2, ImportAssets2Tag.ID, ImportAssets2Tag.class, ImportAssets2Tag.NAME);
addTagInfo(map, map2, ImportAssetsTag.ID, ImportAssetsTag.class, ImportAssetsTag.NAME);
addTagInfo(map, map2, JPEGTablesTag.ID, JPEGTablesTag.class, JPEGTablesTag.NAME);
addTagInfo(map, map2, MetadataTag.ID, MetadataTag.class, MetadataTag.NAME);
addTagInfo(map, map2, PlaceObject2Tag.ID, PlaceObject2Tag.class, PlaceObject2Tag.NAME);
addTagInfo(map, map2, PlaceObject3Tag.ID, PlaceObject3Tag.class, PlaceObject3Tag.NAME);
addTagInfo(map, map2, PlaceObject4Tag.ID, PlaceObject4Tag.class, PlaceObject4Tag.NAME);
addTagInfo(map, map2, PlaceObjectTag.ID, PlaceObjectTag.class, PlaceObjectTag.NAME);
addTagInfo(map, map2, ProductInfoTag.ID, ProductInfoTag.class, ProductInfoTag.NAME);
addTagInfo(map, map2, ProtectTag.ID, ProtectTag.class, ProtectTag.NAME);
addTagInfo(map, map2, RemoveObject2Tag.ID, RemoveObject2Tag.class, RemoveObject2Tag.NAME);
addTagInfo(map, map2, RemoveObjectTag.ID, RemoveObjectTag.class, RemoveObjectTag.NAME);
addTagInfo(map, map2, ScriptLimitsTag.ID, ScriptLimitsTag.class, ScriptLimitsTag.NAME);
addTagInfo(map, map2, SetBackgroundColorTag.ID, SetBackgroundColorTag.class, SetBackgroundColorTag.NAME);
addTagInfo(map, map2, SetTabIndexTag.ID, SetTabIndexTag.class, SetTabIndexTag.NAME);
addTagInfo(map, map2, ShowFrameTag.ID, ShowFrameTag.class, ShowFrameTag.NAME);
addTagInfo(map, map2, SoundStreamBlockTag.ID, SoundStreamBlockTag.class, SoundStreamBlockTag.NAME);
addTagInfo(map, map2, SoundStreamHead2Tag.ID, SoundStreamHead2Tag.class, SoundStreamHead2Tag.NAME);
addTagInfo(map, map2, SoundStreamHeadTag.ID, SoundStreamHeadTag.class, SoundStreamHeadTag.NAME);
addTagInfo(map, map2, StartSound2Tag.ID, StartSound2Tag.class, StartSound2Tag.NAME);
addTagInfo(map, map2, StartSoundTag.ID, StartSoundTag.class, StartSoundTag.NAME);
addTagInfo(map, map2, SymbolClassTag.ID, SymbolClassTag.class, SymbolClassTag.NAME);
addTagInfo(map, map2, VideoFrameTag.ID, VideoFrameTag.class, VideoFrameTag.NAME);
addTagInfo(map, map2, DefineCompactedFont.ID, DefineCompactedFont.class, DefineCompactedFont.NAME);
addTagInfo(map, map2, DefineExternalGradient.ID, DefineExternalGradient.class, DefineExternalGradient.NAME);
addTagInfo(map, map2, DefineExternalImage.ID, DefineExternalImage.class, DefineExternalImage.NAME);
addTagInfo(map, map2, DefineExternalImage2.ID, DefineExternalImage2.class, DefineExternalImage2.NAME);
addTagInfo(map, map2, DefineExternalSound.ID, DefineExternalSound.class, DefineExternalSound.NAME);
addTagInfo(map, map2, DefineExternalStreamSound.ID, DefineExternalStreamSound.class, DefineExternalStreamSound.NAME);
addTagInfo(map, map2, DefineGradientMap.ID, DefineGradientMap.class, DefineGradientMap.NAME);
addTagInfo(map, map2, DefineSubImage.ID, DefineSubImage.class, DefineSubImage.NAME);
addTagInfo(map, map2, ExporterInfo.ID, ExporterInfo.class, ExporterInfo.NAME);
addTagInfo(map, map2, FontTextureInfo.ID, FontTextureInfo.class, FontTextureInfo.NAME);
knownTagInfosById = map;
knownTagInfosByName = map2;
}
}
}
return knownTagInfosById;
}
public static Map<String, TagTypeInfo> getKnownClassesByName() {
// map is filled together with knownTagInfosById
if (knownTagInfosByName == null) {
getKnownClasses();
}
return knownTagInfosByName;
}
private static void addTagInfo(Map<Integer, TagTypeInfo> map, Map<String, TagTypeInfo> map2, int id, Class cls, String name) {
map.put(id, new TagTypeInfo(id, cls, name));
map2.put(name, new TagTypeInfo(id, cls, name));
}
public static List<Integer> getRequiredTags() {
if (requiredTagIds == null) {
synchronized (lockObject) {
if (requiredTagIds == null) {
List<Integer> tagIds = Arrays.asList(
DefineBinaryDataTag.ID,
DefineBitsJPEG2Tag.ID,
DefineBitsJPEG3Tag.ID,
DefineBitsJPEG4Tag.ID,
DefineBitsLossless2Tag.ID,
DefineBitsLosslessTag.ID,
DefineBitsTag.ID,
DefineButton2Tag.ID,
DefineButtonCxformTag.ID,
DefineButtonSoundTag.ID,
DefineButtonTag.ID,
DefineEditTextTag.ID,
DefineFont2Tag.ID,
DefineFont3Tag.ID,
DefineFont4Tag.ID,
DefineFontAlignZonesTag.ID,
DefineFontInfo2Tag.ID,
DefineFontInfoTag.ID,
DefineFontNameTag.ID,
DefineFontTag.ID,
DefineMorphShape2Tag.ID,
DefineMorphShapeTag.ID,
DefineScalingGridTag.ID,
DefineSceneAndFrameLabelDataTag.ID,
DefineShape2Tag.ID,
DefineShape3Tag.ID,
DefineShape4Tag.ID,
DefineShapeTag.ID,
DefineSoundTag.ID,
DefineSpriteTag.ID,
DefineText2Tag.ID,
DefineTextTag.ID,
DefineVideoStreamTag.ID,
DoABC2Tag.ID,
DoABCTag.ID,
DoActionTag.ID,
DoInitActionTag.ID,
ShowFrameTag.ID);
requiredTagIds = tagIds;
}
}
}
return requiredTagIds;
}
public int getVersion() {
if (swf == null) {
return SWF.DEFAULT_VERSION;
}
return swf.version;
}
protected byte[] getHeader(int dataLength) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
SWFOutputStream sos = new SWFOutputStream(baos, swf.version);
int tagLength = dataLength;
int tagID = getId();
int tagIDLength = (tagID << 6);
if ((tagLength <= 62) && (!forceWriteAsLong)) {
tagIDLength += tagLength;
sos.writeUI16(tagIDLength);
} else {
tagIDLength += 0x3f;
sos.writeUI16(tagIDLength);
sos.writeSI32(tagLength);
}
} catch (IOException iex) {
throw new Error("This should never happen.", iex);
}
return baos.toByteArray();
}
public static byte[] getTagHeader(int tagIDTagLength, long tagLength, boolean writeLong, int version) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
SWFOutputStream sos = new SWFOutputStream(baos, version);
sos.writeUI16(tagIDTagLength);
if (writeLong) {
sos.writeSI32(tagLength);
}
} catch (IOException iex) {
throw new Error("This should never happen.", iex);
}
return baos.toByteArray();
}
/**
* Writes Tag value to the stream
*
* @param sos SWF output stream
* @throws IOException
*/
public void writeTag(SWFOutputStream sos) throws IOException {
if (Configuration._debugCopy.get() || isModified()) {
byte[] newData = getData();
byte[] newHeaderData = getHeader(newData.length);
sos.write(newHeaderData);
sos.write(newData);
} else {
sos.write(originalRange.getArray(), originalRange.getPos(), originalRange.getLength());
}
}
public Tag cloneTag() throws InterruptedException, IOException {
byte[] data = getData();
SWFInputStream tagDataStream = new SWFInputStream(swf, data, getDataPos(), data.length);
TagStub copy = new TagStub(swf, getId(), "Unresolved", getOriginalRange(), tagDataStream);
copy.forceWriteAsLong = forceWriteAsLong;
return SWFInputStream.resolveTag(copy, 0, false, true, false);
}
public Tag getOriginalTag() throws InterruptedException, IOException {
byte[] data = getOriginalData();
SWFInputStream tagDataStream = new SWFInputStream(swf, data, getDataPos(), data.length);
TagStub copy = new TagStub(swf, getId(), "Unresolved", getOriginalRange(), tagDataStream);
copy.forceWriteAsLong = forceWriteAsLong;
return SWFInputStream.resolveTag(copy, 0, false, true, false);
}
public boolean canUndo() {
return originalRange != null && isModified();
}
public void undo() throws InterruptedException, IOException {
byte[] data = getOriginalData();
if (data == null) { //If the tag is newly created in GUI it has no original data
return;
}
SWFInputStream tagDataStream = new SWFInputStream(swf, data, getDataPos(), data.length);
readData(tagDataStream, getOriginalRange(), 0, false, true, false);
setModified(false);
}
/**
* Returns string representation of the object
*
* @return String representation of the object
*/
@Override
public String toString() {
return getName();
}
/**
* Gets data bytes
*
* @param sos SWF output stream
* @throws java.io.IOException
*/
public abstract void getData(SWFOutputStream sos) throws IOException;
/**
* Gets data bytes
*
* @return Bytes of data
*/
public byte[] getData() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream os = baos;
if (Configuration._debugCopy.get()) {
byte[] originalData = getOriginalData();
if (originalData != null) {
os = new CopyOutputStream(os, new ByteArrayInputStream(getOriginalData()));
}
}
try (SWFOutputStream sos = new SWFOutputStream(os, getVersion())) {
getData(sos);
if (remainingData != null) {
sos.write(remainingData);
}
} catch (IOException e) {
throw new Error("This should never happen.", e);
}
return baos.toByteArray();
}
public final ByteArrayRange getOriginalRange() {
return originalRange;
}
/**
* Returns the original inner data of the tag, without the 2-6 bytes length
* header Call this method only from debug codes
*
* @return The data
*/
public final byte[] getOriginalData() {
if (originalRange == null) {
return null;
}
int dataLength = getOriginalDataLength();
int pos = (int) (originalRange.getPos() + originalRange.getLength() - dataLength);
byte[] data = new byte[dataLength];
System.arraycopy(originalRange.getArray(), pos, data, 0, dataLength);
return data;
}
public final int getOriginalDataLength() {
if (originalRange == null) {
return 0;
}
return originalRange.getLength() - (isLongOriginal() ? 6 : 2);
}
private boolean isLongOriginal() {
int shortLength = originalRange.getArray()[(int) originalRange.getPos()] & 0x003F;
return shortLength == 0x3f;
}
public long getPos() {
if (originalRange == null) {
return -1;
}
return originalRange.getPos();
}
public long getDataPos() {
if (originalRange == null) {
return -1;
}
return originalRange.getPos() + (isLongOriginal() ? 6 : 2);
}
public void setModified(boolean value) {
boolean oldValue = modified;
modified = value;
if (value && oldValue != value) {
informListeners();
}
}
public final void addEventListener(TagChangedListener listener) {
listeners.add(listener);
}
public final void removeEventListener(TagChangedListener listener) {
listeners.remove(listener);
}
protected void informListeners() {
for (TagChangedListener listener : listeners) {
listener.handleEvent(this);
}
}
public void createOriginalData() {
byte[] data = getData();
byte[] headerData = getHeader(data.length);
byte[] tagData = new byte[data.length + headerData.length];
System.arraycopy(headerData, 0, tagData, 0, headerData.length);
System.arraycopy(data, 0, tagData, headerData.length, data.length);
originalRange = new ByteArrayRange(tagData);
}
@Override
public boolean isModified() {
return modified;
}
public boolean isReadOnly() {
return isImported();
}
@Override
public void getNeededCharacters(Set<Integer> needed) {
}
@Override
public boolean replaceCharacter(int oldCharacterId, int newCharacterId) {
return false;
}
@Override
public boolean removeCharacter(int characterId) {
return false;
}
public void getNeededCharactersDeep(Set<Integer> needed) {
Set<Integer> visited = new HashSet<>();
Set<Integer> needed2 = new LinkedHashSet<>();
getNeededCharacters(needed2);
while (visited.size() != needed2.size()) {
for (int characterId : needed2) {
if (!visited.contains(characterId)) {
visited.add(characterId);
if (swf.getCharacters().containsKey(characterId)) {
swf.getCharacter(characterId).getNeededCharacters(needed2);
break;
}
}
}
}
for (Integer characterId : needed2) {
if (swf.getCharacters().containsKey(characterId)) {
needed.add(characterId);
}
}
}
public void getDependentCharacters(Set<Integer> dependent) {
for (Tag tag : swf.getTags()) {
if (tag instanceof CharacterTag) {
Set<Integer> needed = new HashSet<>();
tag.getNeededCharactersDeep(needed);
for (int dep : dependent) {
if (needed.contains(dep)) {
dependent.add(((CharacterTag) tag).getCharacterId());
break;
}
}
}
}
}
public void getTagInfo(TagInfo tagInfo) {
tagInfo.addInfo("general", "tagType", String.format("%s (%d)", tagName, id));
if (this instanceof CharacterIdTag) {
CharacterIdTag characterIdTag = (CharacterIdTag) this;
tagInfo.addInfo("general", "characterId", characterIdTag.getCharacterId());
}
if (originalRange != null) {
int pos = originalRange.getPos();
int length = originalRange.getLength();
tagInfo.addInfo("general", "offset", String.format("%d (0x%x)", pos, pos));
tagInfo.addInfo("general", "length", String.format("%d (0x%x)", length, length));
}
if (this instanceof BoundedTag) {
BoundedTag boundedIdTag = (BoundedTag) this;
RECT bounds = boundedIdTag.getRect();
tagInfo.addInfo("general", "bounds",
String.format("(%.2f, %.2f)[%.2f x %.2f]", bounds.Xmin / SWF.unitDivisor,
bounds.Ymin / SWF.unitDivisor,
bounds.getWidth() / SWF.unitDivisor,
bounds.getHeight() / SWF.unitDivisor));
}
Set<Integer> needed = new LinkedHashSet<>();
getNeededCharactersDeep(needed);
if (needed.size() > 0) {
tagInfo.addInfo("general", "neededCharacters", Helper.joinStrings(needed, ", "));
}
if (this instanceof CharacterTag) {
int characterId = ((CharacterTag) this).getCharacterId();
Set<Integer> dependent = swf.getDependentCharacters(characterId);
if (dependent != null) {
if (dependent.size() > 0) {
tagInfo.addInfo("general", "dependentCharacters", Helper.joinStrings(dependent, ", "));
}
}
}
}
}