From 1e9bf6be887f95a6f2b8d33cea4730ca5908d0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 29 Aug 2025 00:02:38 +0200 Subject: [PATCH] Added: Generator Templates (.swt) files support and related tags (Flash 3-5) (CharacterSet, GenCommand, NameCharacter, DefineTextFormat, FontRef) DefineVideo tag support (Flash 4) SerialNumber tag support (before Flash 7) --- CHANGELOG.md | 4 + .../decompiler/flash/OpenableSourceInfo.java | 3 +- .../decompiler/flash/SWFInputStream.java | 138 ++++++++++- .../decompiler/flash/SWFOutputStream.java | 72 ++++++ .../flash/importers/SwfXmlImporter.java | 12 +- .../flash/tags/CharacterSetTag.java | 57 +++++ .../flash/tags/DefineButtonTag.java | 2 +- .../flash/tags/DefineTextFormatTag.java | 96 ++++++++ .../decompiler/flash/tags/DefineVideoTag.java | 79 ++++++ .../decompiler/flash/tags/FontRefTag.java | 79 ++++++ .../decompiler/flash/tags/GenCommandTag.java | 225 ++++++++++++++++++ .../flash/tags/NameCharacterTag.java | 20 +- .../decompiler/flash/tags/ProductInfoTag.java | 6 +- .../flash/tags/SerialNumberTag.java | 69 ++++++ .../decompiler/flash/tags/ShowFrameTag.java | 1 + .../com/jpexs/decompiler/flash/tags/Tag.java | 21 +- .../jpexs/decompiler/flash/tags/TagStub.java | 20 +- .../flash/tags/text/package-info.java | 2 +- .../text/format/ControlTextFormatRecord.java | 128 ++++++++++ .../text/format/EndTextFormatRecord.java | 28 +++ .../types/text/format/TextFormatRecord.java | 29 +++ .../text/format/TextTextFormatRecord.java | 30 +++ .../flash/types/text/format/package-info.java | 4 + src/com/jpexs/decompiler/flash/gui/Main.java | 66 ++++- .../flash/gui/locales/MainFrame.properties | 5 +- .../flash/gui/tagtree/AbstractTagTree.java | 15 +- .../flash/gui/tagtree/TagIdClassMap.java | 41 +++- .../decompiler/flash/gui/tagtree/TagTree.java | 17 +- .../flash/gui/tagtree/TagTreeContextMenu.java | 27 ++- 29 files changed, 1243 insertions(+), 53 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/CharacterSetTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineTextFormatTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/FontRefTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/GenCommandTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SerialNumberTag.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/ControlTextFormatRecord.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/EndTextFormatRecord.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextFormatRecord.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextTextFormatRecord.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/package-info.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8be88a3eb..683151416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ All notable changes to this project will be documented in this file. - AS Debugging - Variables with flag DontEnumerate are hidden by default (can be changed in Advanced Settings) - Basic support for PlaceImagePrivate tag +- Generator Templates (.swt) files support and related tags (Flash 3-5) + (CharacterSet, GenCommand, NameCharacter, DefineTextFormat, FontRef) +- DefineVideo tag support (Flash 4) +- SerialNumber tag support (before Flash 7) ### Fixed - [#2474] Gotos incorrectly decompiled diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/OpenableSourceInfo.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/OpenableSourceInfo.java index 05023d3f1..c446d873e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/OpenableSourceInfo.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/OpenableSourceInfo.java @@ -207,8 +207,9 @@ public class OpenableSourceInfo { || !( extension.equals(".swf") || extension.equals(".spl") + || extension.equals(".swt") || extension.equals(".gfx") - || extension.equals(".abc") + || extension.equals(".abc") ) ); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java index b7079e44b..20dd58f12 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -128,6 +128,7 @@ import com.jpexs.decompiler.flash.dumpview.DumpInfo; import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial; import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; import com.jpexs.decompiler.flash.tags.CSMSettingsTag; +import com.jpexs.decompiler.flash.tags.CharacterSetTag; import com.jpexs.decompiler.flash.tags.DebugIDTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; @@ -160,8 +161,10 @@ import com.jpexs.decompiler.flash.tags.DefineShapeTag; import com.jpexs.decompiler.flash.tags.DefineSoundTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineText2Tag; +import com.jpexs.decompiler.flash.tags.DefineTextFormatTag; import com.jpexs.decompiler.flash.tags.DefineTextTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.DefineVideoTag; import com.jpexs.decompiler.flash.tags.DoABC2Tag; import com.jpexs.decompiler.flash.tags.DoABCTag; import com.jpexs.decompiler.flash.tags.DoActionTag; @@ -172,9 +175,11 @@ import com.jpexs.decompiler.flash.tags.EnableTelemetryTag; import com.jpexs.decompiler.flash.tags.EndTag; import com.jpexs.decompiler.flash.tags.ExportAssetsTag; import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.FontRefTag; import com.jpexs.decompiler.flash.tags.FrameLabelTag; import com.jpexs.decompiler.flash.tags.FreeAllTag; import com.jpexs.decompiler.flash.tags.FreeCharacterTag; +import com.jpexs.decompiler.flash.tags.GenCommandTag; import com.jpexs.decompiler.flash.tags.ImportAssets2Tag; import com.jpexs.decompiler.flash.tags.ImportAssetsTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; @@ -191,6 +196,7 @@ import com.jpexs.decompiler.flash.tags.ProtectTag; import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; import com.jpexs.decompiler.flash.tags.RemoveObjectTag; import com.jpexs.decompiler.flash.tags.ScriptLimitsTag; +import com.jpexs.decompiler.flash.tags.SerialNumberTag; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.SetTabIndexTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; @@ -275,6 +281,10 @@ import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.decompiler.flash.types.text.format.ControlTextFormatRecord; +import com.jpexs.decompiler.flash.types.text.format.EndTextFormatRecord; +import com.jpexs.decompiler.flash.types.text.format.TextFormatRecord; +import com.jpexs.decompiler.flash.types.text.format.TextTextFormatRecord; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.FakeMemoryInputStream; @@ -1633,7 +1643,9 @@ public class SWFInputStream implements AutoCloseable { case 37: ret = new DefineEditTextTag(sis, data); break; - //case 38: DefineVideo / DefineMouseTarget + case 38: // or DefineMouseTarget + ret = new DefineVideoTag(sis, data); + break; case 39: ret = new DefineSpriteTag(sis, level, data, parallel, skipUnusualTags); break; @@ -1641,9 +1653,15 @@ public class SWFInputStream implements AutoCloseable { ret = new NameCharacterTag(sis, data); break; case 41: //or NameObject - ret = new ProductInfoTag(sis, data); + if (swf == null || swf.version >= 7) { + ret = new ProductInfoTag(sis, data); + } else { + ret = new SerialNumberTag(sis, data); + } + break; + case 42: + ret = new DefineTextFormatTag(sis, data); break; - //case 42: DefineTextFormat case 43: ret = new FrameLabelTag(sis, data); break; @@ -1658,10 +1676,16 @@ public class SWFInputStream implements AutoCloseable { case 48: ret = new DefineFont2Tag(sis, data); break; - //case 49: GenCommand + case 49: + ret = new GenCommandTag(sis, data); + break; //case 50: DefineCommandObj - //case 51: CharacterSet - //case 52: FontRef + case 51: + ret = new CharacterSetTag(sis, data); + break; + case 52: + ret = new FontRefTag(sis, data); + break; //case 53: DefineFunction //case 54: PlaceFunction //case 55: GenTagObject @@ -3796,6 +3820,108 @@ public class SWFInputStream implements AutoCloseable { return ret; } + /** + * Reads one TextFormatRecord value from the stream + * @param name Name + * @return TextFormatRecord value + * @throws IOException On I/O error + */ + public TextFormatRecord readTextFormatRecord(String name) throws IOException { + newDumpLevel(name, "TextFormatRecord"); + int controlFlag = (int) readUB(1, "controlFlag"); + + if (controlFlag == 0) { + int length = (int) readUB(7, "length"); + if (length > 0) { + TextTextFormatRecord textRecord = new TextTextFormatRecord(); + byte[] bytes = readBytes(length * 2, "text"); + byte[] halfBytes = new byte[length]; + for (int i = 0; i < halfBytes.length; i++) { + halfBytes[i] = bytes[i * 2]; + } + textRecord.text = new String(halfBytes, swf.getCharset()); + DumpInfo di = dumpInfo; + if (di != null) { + di.name = "TextTextFormatRecord"; + } + endDumpLevel(); + return textRecord; + } + + DumpInfo di = dumpInfo; + if (di != null) { + di.name = "EndTextFormatRecord"; + } + endDumpLevel(); + return new EndTextFormatRecord(); + } + int controlType = (int) readUB(7, "controlType"); + + ControlTextFormatRecord controlRecord = new ControlTextFormatRecord(); + controlRecord.type = controlType; + switch (controlType) { + case ControlTextFormatRecord.TYPE_STYLE: + controlRecord.style = readUI8("style"); + break; + case ControlTextFormatRecord.TYPE_FONT_ID: + controlRecord.fontId = readUI16("fontId"); + break; + case ControlTextFormatRecord.TYPE_FONT_HEIGHT: + controlRecord.fontHeight = readUI16("fontHeight"); + break; + case ControlTextFormatRecord.TYPE_COLOR: + controlRecord.color = readRGBA("color"); + break; + case ControlTextFormatRecord.TYPE_SCRIPT: + controlRecord.script = readUI8("script"); + break; + case ControlTextFormatRecord.TYPE_KERNING: + controlRecord.kerning = readSI16("kerning"); + break; + case ControlTextFormatRecord.TYPE_ALIGN: + controlRecord.align = readUI8("align"); + break; + case ControlTextFormatRecord.TYPE_INDENT: + controlRecord.indent = readSI16("indent"); + break; + case ControlTextFormatRecord.TYPE_LEFT_MARGIN: + controlRecord.leftMargin = readSI16("leftMargin"); + break; + case ControlTextFormatRecord.TYPE_RIGHT_MARGIN: + controlRecord.rightMargin = readSI16("rightMargin"); + break; + case ControlTextFormatRecord.TYPE_LINE_SPACE: + controlRecord.lineSpace = readSI16("lineSpace"); + break; + } + DumpInfo di = dumpInfo; + if (di != null) { + di.name = "ControlTextFormatRecord"; + } + endDumpLevel(); + return controlRecord; + } + + /** + * Reads List of TextFormatRecord values from the stream + * @param name Name + * @return List of TextFormatRecord values + * @throws IOException On I/O error + */ + public List readTextFormatRecords(String name) throws IOException { + newDumpLevel(name, "TextFormatRecords"); + List records = new ArrayList<>(); + while (true) { + TextFormatRecord record = readTextFormatRecord("record"); + if (record instanceof EndTextFormatRecord) { + break; + } + records.add(record); + } + endDumpLevel(); + return records; + } + /** * Gets number of available bytes in the stream. * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java index d9885ae86..3c0fc8fcc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFOutputStream.java @@ -79,6 +79,9 @@ import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.decompiler.flash.types.text.format.ControlTextFormatRecord; +import com.jpexs.decompiler.flash.types.text.format.TextFormatRecord; +import com.jpexs.decompiler.flash.types.text.format.TextTextFormatRecord; import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.ByteArrayOutputStream; @@ -2151,4 +2154,73 @@ public class SWFOutputStream extends OutputStream { public void writeAmf3Object(Amf3Value value) throws IOException, NoSerializerExistsException { writeAmf3Object(value, new HashMap<>()); } + + /** + * Writes List of TextFormatRecord values to the stream. Terminates with zero. + * @param records List of records + * @throws IOException On I/O error + */ + public void writeTextFormatRecords(List records) throws IOException { + for (TextFormatRecord record : records) { + writeTextFormatRecord(record); + } + writeUI8(0); + } + + /** + * Writes TextFormatRecord value to the stream. + * + * @param value TextFormatRecord value + * @throws IOException On I/O error + */ + public void writeTextFormatRecord(TextFormatRecord value) throws IOException { + if (value instanceof TextTextFormatRecord) { + TextTextFormatRecord textRecord = (TextTextFormatRecord) value; + byte[] halfBytes = textRecord.text.getBytes(charset); + writeUI8(halfBytes.length); + for (int i = 0; i < halfBytes.length; i++) { + writeUI8(halfBytes[i]); + writeUI8(0); + } + } + if (value instanceof ControlTextFormatRecord) { + ControlTextFormatRecord controlRecord = (ControlTextFormatRecord) value; + writeUI8(0x80 | controlRecord.type); + switch (controlRecord.type) { + case ControlTextFormatRecord.TYPE_STYLE: + writeUI8(controlRecord.style); + break; + case ControlTextFormatRecord.TYPE_FONT_ID: + writeUI16(controlRecord.fontId); + break; + case ControlTextFormatRecord.TYPE_FONT_HEIGHT: + writeUI16(controlRecord.fontHeight); + break; + case ControlTextFormatRecord.TYPE_COLOR: + writeRGBA(controlRecord.color); + break; + case ControlTextFormatRecord.TYPE_SCRIPT: + writeUI8(controlRecord.script); + break; + case ControlTextFormatRecord.TYPE_KERNING: + writeSI16(controlRecord.kerning); + break; + case ControlTextFormatRecord.TYPE_ALIGN: + writeUI8(controlRecord.align); + break; + case ControlTextFormatRecord.TYPE_INDENT: + writeSI16(controlRecord.indent); + break; + case ControlTextFormatRecord.TYPE_LEFT_MARGIN: + writeSI16(controlRecord.leftMargin); + break; + case ControlTextFormatRecord.TYPE_RIGHT_MARGIN: + writeSI16(controlRecord.rightMargin); + break; + case ControlTextFormatRecord.TYPE_LINE_SPACE: + writeSI16(controlRecord.lineSpace); + break; + } + } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java index d90688ee2..549992f8b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SwfXmlImporter.java @@ -157,13 +157,15 @@ public class SwfXmlImporter { static { Map tags = new HashMap<>(); - Map knownTags = Tag.getKnownClasses(); + Map> knownTags = Tag.getKnownClasses(); for (Integer key : knownTags.keySet()) { - Class cls = knownTags.get(key).getCls(); - if (!ReflectionTools.canInstantiate(cls)) { - System.err.println("Can't instantiate: " + cls.getName()); + for (TagTypeInfo tagTypeInfo : knownTags.get(key)) { + Class cls = tagTypeInfo.getCls(); + if (!ReflectionTools.canInstantiate(cls)) { + System.err.println("Can't instantiate: " + cls.getName()); + } + tags.put(cls.getSimpleName(), cls); } - tags.put(cls.getSimpleName(), cls); } swfTags = tags; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/CharacterSetTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/CharacterSetTag.java new file mode 100644 index 000000000..78fd14f58 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/CharacterSetTag.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010-2025 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.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class CharacterSetTag extends Tag { + public static final int ID = 51; + + public static final String NAME = "CharacterSet"; + + + public long unknown; + + public CharacterSetTag(SWF swf) { + super(swf, ID, NAME, null); + } + + public CharacterSetTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + unknown = sis.readUI32("unknown"); + } + + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI32(unknown); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java index a26eac98d..d4b66d144 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java @@ -285,7 +285,7 @@ public class DefineButtonTag extends ButtonTag implements ASMSourceContainer { if (r.buttonStateHitTest) { frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, frameHit, false)); if (!r.buttonStateDown) { - frameDown.layers.get(r.placeDepth).key = true; + frameHit.layers.get(r.placeDepth).key = true; } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineTextFormatTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineTextFormatTag.java new file mode 100644 index 000000000..8f69eb266 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineTextFormatTag.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010-2025 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.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.text.format.TextFormatRecord; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * DefineTextFormat tag - define text format. Used by Flash Generator Templates. + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class DefineTextFormatTag extends Tag implements CharacterIdTag { + + public static final int ID = 42; + + public static final String NAME = "DefineTextFormat"; + + @SWFType(BasicType.UI16) + public int textId; + + public RECT bounds; + + public List records = new ArrayList<>(); + + + /** + * Constructor + * + * @param swf SWF + */ + public DefineTextFormatTag(SWF swf) { + super(swf, ID, NAME, null); + } + + public DefineTextFormatTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + textId = sis.readUI16("textId"); + bounds = sis.readRECT("textBounds"); + records = sis.readTextFormatRecords("textFormatRecords"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws IOException On I/O error + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(textId); + sos.writeRECT(bounds); + sos.writeTextFormatRecords(records); + } + + @Override + public int getCharacterId() { + return textId; + } + + @Override + public void setCharacterId(int characterId) { + this.textId = characterId; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoTag.java new file mode 100644 index 000000000..3801c7b6b --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoTag.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010-2025 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.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; + +/** + * DefineVideo tag - references QuickTime video by URL. + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class DefineVideoTag extends Tag { + + public static final int ID = 38; + + public static final String NAME = "DefineVideo"; + + @SWFType(BasicType.UI16) + public int videoId; + + /** + * QuickTime video path (.mov) + */ + public String videoFileName; + + /** + * Constructor + * + * @param swf SWF + */ + public DefineVideoTag(SWF swf) { + super(swf, ID, NAME, null); + } + + public DefineVideoTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + videoId = sis.readUI16("videoId"); + videoFileName = sis.readString("videoFileName"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws IOException On I/O error + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(videoId); + sos.writeString(videoFileName); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/FontRefTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/FontRefTag.java new file mode 100644 index 000000000..a30b08e1a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/FontRefTag.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010-2025 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.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; + +/** + * FontRef tag - external font reference. Used by Flash Generator Templates. + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class FontRefTag extends Tag { + + public static final int ID = 52; + + public static final String NAME = "FontRef"; + + @SWFType(BasicType.UI16) + public int fontId; + + /** + * File name with "fft" extension - it's a SWF file containing DefineFont2 + Protect + */ + public String fontFileName; + + /** + * Constructor + * + * @param swf SWF + */ + public FontRefTag(SWF swf) { + super(swf, ID, NAME, null); + } + + public FontRefTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + fontId = sis.readUI16("fontId"); + fontFileName = sis.readString("fontFileName"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws IOException On I/O error + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(fontId); + sos.writeString(fontFileName); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/GenCommandTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/GenCommandTag.java new file mode 100644 index 000000000..6ba561b22 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/GenCommandTag.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2010-2025 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.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.SWFArray; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.annotations.Table; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * GenCommand tag - Command to Generator in Flash Templates. + * @author JPEXS + */ +@SWFVersion(from = 3) +public class GenCommandTag extends Tag { + public static final int ID = 49; + + public static final String NAME = "GenCommand"; + + public static final int TYPE_MOVIE = 0x01; + public static final int TYPE_BUTTON = 0x02; + public static final int TYPE_GRAPHICS = 0x04; + public static final int TYPE_GLOBAL = 0x08; + + public static final int FLAG_LOOP = 0x0010; + public static final int FLAG_PLAY_ONCE = 0x0020; + public static final int FLAG_SINGLE_FRAME = 0x0040; + + + + public boolean typeMovie = false; + + public boolean typeButton = false; + + public boolean typeGraphics = false; + + public boolean typeGlobal = false; + + @SWFType(BasicType.UI16) + public int depth = 0; + + @Conditional("typeGraphics") + public boolean flagLoop = false; + + @Conditional("typeGraphics") + public boolean flagPlayOnce = false; + + @Conditional("typeGraphics") + public boolean flagSingleFrame = false; + + @SWFType(BasicType.UI16) + @Conditional("typeGraphics") + public int frameNum = 0; + + public String command = "NoCommand"; + + + @SWFArray(value = "name", countField = "paramCount") + @Table(value = "parameters", itemName = "parameter") + public List parameterNames = new ArrayList<>(); + + @SWFArray(value = "value", countField = "paramCount") + @Table(value = "parameters", itemName = "parameter") + public List commandValues = new ArrayList<>(); + + + public GenCommandTag(SWF swf) { + super(swf, ID, NAME, null); + } + + public GenCommandTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + int type = sis.readUI16("type"); + typeMovie = (type & TYPE_MOVIE) > 0; + typeButton = (type & TYPE_BUTTON) > 0; + typeGraphics = (type & TYPE_GRAPHICS) > 0; + typeGlobal = (type & TYPE_GLOBAL) > 0; + depth = sis.readUI16("depth"); + if (typeGraphics) { + int flags = sis.readUI16("flags"); + flagLoop = (flags & FLAG_LOOP) > 0; + flagPlayOnce = (flags & FLAG_PLAY_ONCE) > 0; + flagSingleFrame = (flags & FLAG_SINGLE_FRAME) > 0; + + frameNum = sis.readUI16("frameNum"); + } + String fullCommandStr = sis.readString("command"); + if (!fullCommandStr.contains(" ")) { + this.command = fullCommandStr; + return; + } + this.command = fullCommandStr.substring(0, fullCommandStr.indexOf(" ")); + + boolean inValue = false; + StringBuilder sb = new StringBuilder(); + String name = ""; + for (int i = fullCommandStr.indexOf(" ") + 1; i < fullCommandStr.length(); i++) { + Character c = fullCommandStr.charAt(i); + Character cNext = i == fullCommandStr.length() - 1 ? null : fullCommandStr.charAt(i + 1); + + if (inValue) { + + if (c == '/' && cNext == 'n') { + sb.append('\n'); + i++; + } else if (c == '/' && cNext == 'r') { + sb.append('\r'); + i++; + } else if (c == '\\' && cNext != null) { + sb.append(cNext); + i++; + } else if (c == '"') { + String value = sb.toString(); + parameterNames.add(name); + commandValues.add(value); + sb = new StringBuilder(); + inValue = false; + i++; + } else { + sb.append(c); + } + } else if (c == '=' && cNext == '"') { + inValue = true; + name = sb.toString(); + sb = new StringBuilder(); + i++; + } else { + sb.append(c); + } + } + } + + public String getCommandString() { + StringBuilder sb = new StringBuilder(); + sb.append(command); + sb.append(" "); + for (int i = 0; i < parameterNames.size(); i++) { + String name = parameterNames.get(i); + String value = commandValues.get(i); + sb.append(name); + sb.append("=\""); + sb.append(value.replace("\n", "/n").replace("\r", "/r").replace("\\", "\\\\").replace("\"", "\\\"")); + sb.append("\""); + sb.append(" "); + } + return sb.toString(); + } + + @Override + public void getData(SWFOutputStream sos) throws IOException { + int type = 0; + if (typeMovie) { + type |= TYPE_MOVIE; + } + if (typeButton) { + type |= TYPE_BUTTON; + } + if (typeGraphics) { + type |= TYPE_GRAPHICS; + } + if (typeGlobal) { + type |= TYPE_GLOBAL; + } + sos.writeUI16(type); + sos.writeUI16(depth); + if (typeGraphics) { + int flags = 0; + if (flagLoop) { + flags |= FLAG_LOOP; + } + if (flagPlayOnce) { + flags |= FLAG_PLAY_ONCE; + } + if (flagSingleFrame) { + flags |= FLAG_SINGLE_FRAME; + } + sos.writeUI16(flags); + sos.writeUI16(frameNum); + } + sos.writeString(getCommandString()); + } + + @Override + public Map getNameProperties() { + Map ret = super.getNameProperties(); + String shortCommand = command; + if (shortCommand.contains(".")) { + shortCommand = shortCommand.substring(shortCommand.lastIndexOf(".") + 1); + } + ret.put("cmd", shortCommand); + ret.put("dpt", "" + depth); + return ret; + } + + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/NameCharacterTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/NameCharacterTag.java index 4354bbb1d..410d3ed89 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/NameCharacterTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/NameCharacterTag.java @@ -28,7 +28,8 @@ import java.io.IOException; import java.util.Set; /** - * NameCharacter tag - undocumented. + * NameCharacter tag - Assign a Library name to a character. + * Used in Flash Templates. * * @author JPEXS */ @@ -39,17 +40,26 @@ public class NameCharacterTag extends Tag implements CharacterIdTag { public static final String NAME = "NameCharacter"; + + public static final int TYPE_BITMAP = 1; + public static final int TYPE_SYMBOL = 6; + public static final int TYPE_SOUND = 0xFFFF; + + /** * ID of character to name */ @SWFType(BasicType.UI16) - public int characterId; + public int characterId = 0; /** * Name of the character */ - public String name; + public String name = "Symbol"; + @SWFType(BasicType.UI16) + public int type = TYPE_SYMBOL; + /** * Constructor * @@ -57,8 +67,6 @@ public class NameCharacterTag extends Tag implements CharacterIdTag { */ public NameCharacterTag(SWF swf) { super(swf, ID, NAME, null); - this.characterId = swf.getNextCharacterId(); - this.name = "unknown"; } public NameCharacterTag(SWFInputStream sis, ByteArrayRange data) throws IOException { @@ -70,6 +78,7 @@ public class NameCharacterTag extends Tag implements CharacterIdTag { public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { characterId = sis.readUI16("characterId"); name = sis.readString("name"); + type = sis.readUI16("type"); } /** @@ -82,6 +91,7 @@ public class NameCharacterTag extends Tag implements CharacterIdTag { public void getData(SWFOutputStream sos) throws IOException { sos.writeUI16(characterId); sos.writeString(name); + sos.writeUI16(type); } @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProductInfoTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProductInfoTag.java index 8fd14046f..4937826fc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProductInfoTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ProductInfoTag.java @@ -30,13 +30,13 @@ import java.io.IOException; * * @author JPEXS */ -@SWFVersion(from = 3) +@SWFVersion(from = 7) public class ProductInfoTag extends Tag { public static final int ID = 41; - public static final String NAME = "ProductInfo"; - + public static final String NAME = "ProductInfo"; + @SWFType(BasicType.UI32) public long productID; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SerialNumberTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SerialNumberTag.java new file mode 100644 index 000000000..edf040edd --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SerialNumberTag.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010-2025 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.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; + +/** + * SerialNumber tag - Serial number of Flash Generator + * + * @author JPEXS + */ +@SWFVersion(to = 6) +public class SerialNumberTag extends Tag { + + public static final int ID = 41; + + public static final String NAME = "SerialNumber"; + + public String serialNumber = ""; + + /** + * Constructor + * + * @param swf SWF + */ + public SerialNumberTag(SWF swf) { + super(swf, ID, NAME, null); + } + + public SerialNumberTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + serialNumber = sis.readString("serialNumber"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws IOException On I/O error + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeString(serialNumber); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ShowFrameTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ShowFrameTag.java index 92346e15f..3df248023 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ShowFrameTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/ShowFrameTag.java @@ -52,6 +52,7 @@ public class ShowFrameTag extends Tag { add(VideoFrameTag.ID); add(SoundStreamBlockTag.ID); add(SetTabIndexTag.ID); + add(GenCommandTag.ID); /*add(SoundStreamHeadTag.ID); add(SoundStreamHead2Tag.ID);*/ } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/Tag.java index 036602dde..c4be9f6de 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/Tag.java @@ -292,7 +292,7 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable { private static volatile Integer[] knownTagIds; - private static volatile Map knownTagInfosById; + private static volatile Map> knownTagInfosById; private static volatile Map knownTagInfosByName; @@ -319,11 +319,11 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable { * Gets known classes. * @return Known classes */ - public static Map getKnownClasses() { + public static Map> getKnownClasses() { if (knownTagInfosById == null) { synchronized (lockObject) { if (knownTagInfosById == null) { - Map map = new HashMap<>(); + Map> map = new HashMap<>(); Map map2 = new HashMap<>(); addTagInfo(map, map2, CSMSettingsTag.ID, CSMSettingsTag.class, CSMSettingsTag.NAME); addTagInfo(map, map2, DebugIDTag.ID, DebugIDTag.class, DebugIDTag.NAME); @@ -405,6 +405,14 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable { 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); + + addTagInfo(map, map2, DefineVideoTag.ID, DefineVideoTag.class, DefineVideoTag.NAME); + addTagInfo(map, map2, GenCommandTag.ID, GenCommandTag.class, GenCommandTag.NAME); + addTagInfo(map, map2, FontRefTag.ID, FontRefTag.class, FontRefTag.NAME); + addTagInfo(map, map2, DefineTextFormatTag.ID, DefineTextFormatTag.class, DefineTextFormatTag.NAME); + addTagInfo(map, map2, NameCharacterTag.ID, NameCharacterTag.class, NameCharacterTag.NAME); + addTagInfo(map, map2, CharacterSetTag.ID, CharacterSetTag.class, CharacterSetTag.NAME); + addTagInfo(map, map2, SerialNumberTag.ID, SerialNumberTag.class, SerialNumberTag.NAME); knownTagInfosById = map; knownTagInfosByName = map2; } @@ -426,8 +434,11 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable { return knownTagInfosByName; } - private static void addTagInfo(Map map, Map map2, int id, Class cls, String name) { - map.put(id, new TagTypeInfo(id, cls, name)); + private static void addTagInfo(Map> map, Map map2, int id, Class cls, String name) { + if (!map.containsKey(id)) { + map.put(id, new ArrayList<>()); + } + map.get(id).add(new TagTypeInfo(id, cls, name)); map2.put(name, new TagTypeInfo(id, cls, name)); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/TagStub.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/TagStub.java index 762070753..34cea5dd5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/TagStub.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/TagStub.java @@ -19,8 +19,10 @@ 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.types.annotations.SWFVersion; import com.jpexs.helpers.ByteArrayRange; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -78,9 +80,21 @@ public class TagStub extends Tag { @Override public Map getNameProperties() { Map ret = super.getNameProperties(); - Map classes = Tag.getKnownClasses(); - if (classes.containsKey(id)) { - ret.put("tcl", classes.get(id).getName()); + Map> classes = Tag.getKnownClasses(); + int swfVersion = getSwf().version; + + if (classes.containsKey(id)) { + TagTypeInfo selectedTagTypeInfo = classes.get(id).get(0); + if (classes.get(id).size() > 1) { + for (TagTypeInfo tagTypeInfo : classes.get(id)) { + SWFVersion ver = (SWFVersion) tagTypeInfo.getCls().getAnnotation(SWFVersion.class); + if (swfVersion >= ver.from() && swfVersion <= ver.to()) { + selectedTagTypeInfo = tagTypeInfo; + break; + } + } + } + ret.put("tcl", selectedTagTypeInfo.getName()); } ret.put("tid", "" + id); return ret; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/text/package-info.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/text/package-info.java index a52ede54a..d85811bae 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/text/package-info.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/text/package-info.java @@ -1,4 +1,4 @@ /** - * Text tags. + * Text tag types. */ package com.jpexs.decompiler.flash.tags.text; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/ControlTextFormatRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/ControlTextFormatRecord.java new file mode 100644 index 000000000..2147c034d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/ControlTextFormatRecord.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010-2025 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.types.text.format; + +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.EnumValue; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import java.awt.Color; + +/** + * + * @author JPEXS + */ +public class ControlTextFormatRecord implements TextFormatRecord { + public static final int TYPE_STYLE = 0; + public static final int TYPE_FONT_ID = 1; + public static final int TYPE_FONT_HEIGHT = 2; + public static final int TYPE_COLOR = 3; + public static final int TYPE_SCRIPT = 4; + public static final int TYPE_KERNING = 5; + public static final int TYPE_ALIGN = 8; + public static final int TYPE_INDENT = 9; + public static final int TYPE_LEFT_MARGIN = 10; + public static final int TYPE_RIGHT_MARGIN = 11; + public static final int TYPE_LINE_SPACE = 12; + + public static final int STYLE_NORMAL = 0; + public static final int STYLE_BOLD = 1; + public static final int STYLE_ITALIC = 2; + + + public static final int SCRIPT_NORMAL = 0; + public static final int SCRIPT_SUPERSCRIPT = 1; + public static final int SCRIPT_SUBSCRIPT = 2; + + public static final int ALIGN_LEFT = 0; + public static final int ALIGN_CENTER = 1; + public static final int ALIGN_RIGHT = 2; + public static final int ALIGN_JUSTIFY = 3; + + @EnumValue(value = TYPE_STYLE, text = "style") + @EnumValue(value = TYPE_FONT_ID, text = "font id") + @EnumValue(value = TYPE_FONT_HEIGHT, text = "font height") + @EnumValue(value = TYPE_COLOR, text = "color") + @EnumValue(value = TYPE_SCRIPT, text = "script") + @EnumValue(value = TYPE_KERNING, text = "kerning") + @EnumValue(value = TYPE_ALIGN, text = "align") + @EnumValue(value = TYPE_INDENT, text = "indent") + @EnumValue(value = TYPE_LEFT_MARGIN, text = "left margin") + @EnumValue(value = TYPE_RIGHT_MARGIN, text = "right margin") + @EnumValue(value = TYPE_LINE_SPACE, text = "line space") + public int type; + + @Conditional(value = "type", options = {TYPE_STYLE}) + @SWFType(BasicType.UI8) + @EnumValue(value = STYLE_NORMAL, text = "normal") + @EnumValue(value = STYLE_BOLD, text = "bold") + @EnumValue(value = STYLE_ITALIC, text = "italic") + @EnumValue(value = STYLE_BOLD | STYLE_ITALIC, text = "bold + italic") + public int style; + + @Conditional(value = "type", options = {TYPE_FONT_ID}) + @SWFType(BasicType.UI16) + public int fontId; + + @Conditional(value = "type", options = {TYPE_FONT_HEIGHT}) + @SWFType(BasicType.UI16) + public int fontHeight; + + @Conditional(value = "type", options = {TYPE_COLOR}) + public RGBA color = new RGBA(Color.BLACK); + + @Conditional(value = "type", options = {TYPE_SCRIPT}) + @SWFType(BasicType.UI8) + @EnumValue(value = SCRIPT_NORMAL, text = "normal") + @EnumValue(value = SCRIPT_SUPERSCRIPT, text = "superscript") + @EnumValue(value = SCRIPT_SUBSCRIPT, text = "subscript") + public int script; + + @Conditional(value = "type", options = {TYPE_KERNING}) + @SWFType(BasicType.SI16) + public int kerning; + + @Conditional(value = "type", options = {TYPE_ALIGN}) + @SWFType(BasicType.UI8) + @EnumValue(value = ALIGN_LEFT, text = "left") + @EnumValue(value = ALIGN_CENTER, text = "center") + @EnumValue(value = ALIGN_RIGHT, text = "right") + @EnumValue(value = ALIGN_JUSTIFY, text = "justify") + public int align; + + @Conditional(value = "type", options = {TYPE_INDENT}) + @SWFType(BasicType.SI16) + public int indent; + + @Conditional(value = "type", options = {TYPE_LEFT_MARGIN}) + @SWFType(BasicType.SI16) + public int leftMargin; + + @Conditional(value = "type", options = {TYPE_RIGHT_MARGIN}) + @SWFType(BasicType.SI16) + public int rightMargin; + + @Conditional(value = "type", options = {TYPE_LINE_SPACE}) + @SWFType(BasicType.SI16) + public int lineSpace; + + @Override + public int getFormatRecordId() { + return 0x80 + type; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/EndTextFormatRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/EndTextFormatRecord.java new file mode 100644 index 000000000..912e73f1d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/EndTextFormatRecord.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010-2025 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.types.text.format; + +/** + * + * @author JPEXS + */ +public class EndTextFormatRecord implements TextFormatRecord { + @Override + public int getFormatRecordId() { + return 0; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextFormatRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextFormatRecord.java new file mode 100644 index 000000000..9e6a033a5 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextFormatRecord.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010-2025 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.types.text.format; + +import com.jpexs.helpers.ConcreteClasses; +import java.io.Serializable; + +/** + * + * @author JPEXS + */ +@ConcreteClasses({ControlTextFormatRecord.class, TextTextFormatRecord.class}) +public interface TextFormatRecord extends Serializable { + public int getFormatRecordId(); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextTextFormatRecord.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextTextFormatRecord.java new file mode 100644 index 000000000..3bdba0534 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/TextTextFormatRecord.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010-2025 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.types.text.format; + +/** + * + * @author JPEXS + */ +public class TextTextFormatRecord implements TextFormatRecord { + public String text = ""; + + @Override + public int getFormatRecordId() { + return text.length(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/package-info.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/package-info.java new file mode 100644 index 000000000..aabfd6c1a --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/text/format/package-info.java @@ -0,0 +1,4 @@ +/** + * Text format tag types. + */ +package com.jpexs.decompiler.flash.types.text.format; diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index ee9a02936..f6f9b1eee 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -1346,7 +1346,7 @@ public class Main { JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); FileFilter allSupportedFilter = new FileFilter() { - private final String[] supportedExtensions = new String[]{".swf", ".spl", ".gfx"}; + private final String[] supportedExtensions = new String[]{".swf", ".spl", ".swt", ".gfx"}; @Override public boolean accept(File f) { @@ -1371,16 +1371,31 @@ public class Main { public boolean accept(File f) { return (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swf")) || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".spl")) + || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swt")) || (f.isDirectory()); } @Override public String getDescription() { - return AppStrings.translate("filter.swf_spl"); + return AppStrings.translate("filter.swf_spl_swt"); } }; fc.addChoosableFileFilter(swfFilter); + FileFilter swtFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swt")) + || f.isDirectory(); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.swt"); + } + }; + fc.addChoosableFileFilter(swtFilter); + FileFilter gfxFilter = new FileFilter() { @Override public boolean accept(File f) { @@ -2235,6 +2250,18 @@ public class Main { } }; + FileFilter swtFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swt")) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.swt"); + } + }; + FileFilter gfxFilter = new FileFilter() { @Override public boolean accept(File f) { @@ -2337,8 +2364,16 @@ public class Main { fc.addChoosableFileFilter(swfFilter); fc.setFileFilter(gfxFilter); } else if (openable instanceof SWF) { - fc.setFileFilter(swfFilter); + SWF swf = (SWF) openable; + if (swf.getFile() != null && swf.getFile().toLowerCase(Locale.ENGLISH).endsWith(".swt")) { + fc.setFileFilter(swtFilter); + fc.addChoosableFileFilter(swfFilter); + } else { + fc.setFileFilter(swfFilter); + fc.addChoosableFileFilter(swtFilter); + } fc.addChoosableFileFilter(gfxFilter); + } else if (openable instanceof ABC) { fc.setFileFilter(abcFilter); } @@ -2362,6 +2397,12 @@ public class Main { } ((SWF) openable).gfx = true; } + + if (selFilter == swtFilter) { + if (!fileName.toLowerCase(Locale.ENGLISH).endsWith(".swt")) { + fileName += ".swt"; + } + } if (selFilter == abcFilter) { if (!fileName.toLowerCase(Locale.ENGLISH).endsWith(".abc")) { @@ -2446,7 +2487,7 @@ public class Main { } fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); FileFilter allSupportedFilter = new FileFilter() { - private final String[] supportedExtensions = new String[]{".swf", ".spl", ".gfx", ".swc", ".zip", ".iggy", ".abc"}; + private final String[] supportedExtensions = new String[]{".swf", ".spl", ".swt", ".gfx", ".swc", ".zip", ".iggy", ".abc"}; @Override public boolean accept(File f) { @@ -2471,16 +2512,31 @@ public class Main { public boolean accept(File f) { return (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swf")) || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".spl")) + || (f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swt")) || (f.isDirectory()); } @Override public String getDescription() { - return AppStrings.translate("filter.swf_spl"); + return AppStrings.translate("filter.swf_spl_swt"); } }; fc.addChoosableFileFilter(swfFilter); + FileFilter swtFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return f.getName().toLowerCase(Locale.ENGLISH).endsWith(".swt") + || f.isDirectory(); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.swt"); + } + }; + fc.addChoosableFileFilter(swtFilter); + FileFilter swcFilter = new FileFilter() { @Override public boolean accept(File f) { diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index d27f7cafd..429fdb30a 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1096,4 +1096,7 @@ menu.settings.autoDeobfuscateIdentifiers = Deobfuscate identifiers deobfuscate_options.deobfuscateIdentifiers = Deobfuscate identifiers node.unknown = unknown -node.errored = errored \ No newline at end of file +node.errored = errored + +filter.swf_spl_swt = SWF files (*.swf, *.spl, *.swt) +filter.swt = Generator templates (*.swt) diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java index 06bf26621..fd821d70e 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/AbstractTagTree.java @@ -53,6 +53,8 @@ import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; import com.jpexs.decompiler.flash.tags.DefineSceneAndFrameLabelDataTag; import com.jpexs.decompiler.flash.tags.DefineSoundTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DefineTextFormatTag; +import com.jpexs.decompiler.flash.tags.DefineTextTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.DoActionTag; import com.jpexs.decompiler.flash.tags.DoInitActionTag; @@ -67,6 +69,7 @@ import com.jpexs.decompiler.flash.tags.ImportAssets2Tag; import com.jpexs.decompiler.flash.tags.ImportAssetsTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; import com.jpexs.decompiler.flash.tags.MetadataTag; +import com.jpexs.decompiler.flash.tags.NameCharacterTag; import com.jpexs.decompiler.flash.tags.PlaceImagePrivateTag; import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; import com.jpexs.decompiler.flash.tags.PlaceObject3Tag; @@ -101,6 +104,7 @@ import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.StaticTextTag; import com.jpexs.decompiler.flash.tags.base.SymbolClassTypeTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; @@ -1111,11 +1115,14 @@ public abstract class AbstractTagTree extends JTree { public static List getMappedTagIdsForClass(Class cls) { if (cls == DefineSpriteTag.class) { - return Arrays.asList(DefineScalingGridTag.ID, DoInitActionTag.ID); + return Arrays.asList(DefineScalingGridTag.ID, DoInitActionTag.ID, NameCharacterTag.ID); } if (FontTag.class.isAssignableFrom(cls)) { return Arrays.asList(DefineFontNameTag.ID, DefineFontAlignZonesTag.ID, DefineFontInfoTag.ID, DefineFontInfo2Tag.ID); } + if (StaticTextTag.class.isAssignableFrom(cls)) { + return Arrays.asList(CSMSettingsTag.ID, DefineTextFormatTag.ID); + } if (TextTag.class.isAssignableFrom(cls)) { return Arrays.asList(CSMSettingsTag.ID); } @@ -1125,6 +1132,12 @@ public abstract class AbstractTagTree extends JTree { if (cls == DefineButton2Tag.class) { return Arrays.asList(DefineButtonSoundTag.ID, DefineScalingGridTag.ID); } + if (ImageTag.class.isAssignableFrom(cls)) { + return Arrays.asList(NameCharacterTag.ID); + } + if (cls == DefineSoundTag.class) { + return Arrays.asList(NameCharacterTag.ID); + } return new ArrayList<>(); } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagIdClassMap.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagIdClassMap.java index b353da4cb..eb1e634db 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagIdClassMap.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagIdClassMap.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui.tagtree; import com.jpexs.decompiler.flash.tags.CSMSettingsTag; +import com.jpexs.decompiler.flash.tags.CharacterSetTag; import com.jpexs.decompiler.flash.tags.DebugIDTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; @@ -49,8 +50,10 @@ import com.jpexs.decompiler.flash.tags.DefineShapeTag; import com.jpexs.decompiler.flash.tags.DefineSoundTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineText2Tag; +import com.jpexs.decompiler.flash.tags.DefineTextFormatTag; import com.jpexs.decompiler.flash.tags.DefineTextTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.DefineVideoTag; import com.jpexs.decompiler.flash.tags.DoABC2Tag; import com.jpexs.decompiler.flash.tags.DoABCTag; import com.jpexs.decompiler.flash.tags.DoActionTag; @@ -61,11 +64,14 @@ import com.jpexs.decompiler.flash.tags.EnableTelemetryTag; import com.jpexs.decompiler.flash.tags.EndTag; import com.jpexs.decompiler.flash.tags.ExportAssetsTag; import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.FontRefTag; import com.jpexs.decompiler.flash.tags.FrameLabelTag; +import com.jpexs.decompiler.flash.tags.GenCommandTag; import com.jpexs.decompiler.flash.tags.ImportAssets2Tag; import com.jpexs.decompiler.flash.tags.ImportAssetsTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; import com.jpexs.decompiler.flash.tags.MetadataTag; +import com.jpexs.decompiler.flash.tags.NameCharacterTag; import com.jpexs.decompiler.flash.tags.PlaceImagePrivateTag; import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; import com.jpexs.decompiler.flash.tags.PlaceObject3Tag; @@ -76,6 +82,7 @@ import com.jpexs.decompiler.flash.tags.ProtectTag; import com.jpexs.decompiler.flash.tags.RemoveObject2Tag; import com.jpexs.decompiler.flash.tags.RemoveObjectTag; import com.jpexs.decompiler.flash.tags.ScriptLimitsTag; +import com.jpexs.decompiler.flash.tags.SerialNumberTag; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.SetTabIndexTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; @@ -96,7 +103,10 @@ 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.types.annotations.SWFVersion; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -104,7 +114,7 @@ import java.util.Map; */ public class TagIdClassMap { - private static final Map> tagIdClassMap = new HashMap<>(); + private static final Map>> tagIdClassMap = new HashMap<>(); private static final Map classTagIdMap = new HashMap<>(); @@ -189,15 +199,38 @@ public class TagIdClassMap { addTag(DefineSubImage.ID, DefineSubImage.class); addTag(ExporterInfo.ID, ExporterInfo.class); addTag(FontTextureInfo.ID, FontTextureInfo.class); + + addTag(DefineVideoTag.ID, DefineVideoTag.class); + addTag(GenCommandTag.ID, GenCommandTag.class); + addTag(FontRefTag.ID, FontRefTag.class); + addTag(DefineTextFormatTag.ID, DefineTextFormatTag.class); + addTag(NameCharacterTag.ID, NameCharacterTag.class); + addTag(CharacterSetTag.ID, CharacterSetTag.class); + addTag(SerialNumberTag.ID, SerialNumberTag.class); } private static void addTag(int tagId, Class cl) { - tagIdClassMap.put(tagId, cl); + if (!tagIdClassMap.containsKey(tagId)) { + tagIdClassMap.put(tagId, new ArrayList<>()); + } + tagIdClassMap.get(tagId).add(cl); classTagIdMap.put(cl, tagId); } - public static Class getClassByTagId(int tagId) { - return tagIdClassMap.get(tagId); + public static Class getClassByTagId(int tagId, int swfVersion) { + List> classes = tagIdClassMap.get(tagId); + if (classes.size() == 1) { + return classes.get(0); + } + for (Class cls : classes) { + SWFVersion ver = cls.getAnnotation(SWFVersion.class); + if (ver != null) { + if (swfVersion >= ver.from() && swfVersion <= ver.to()) { + return cls; + } + } + } + return null; } public static Integer getTagIdByClass(Class cl) { diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java index 6fe7c34b9..f34ba8724 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTree.java @@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.TreeNodeType; import com.jpexs.decompiler.flash.gui.View; +import com.jpexs.decompiler.flash.tags.CharacterSetTag; import com.jpexs.decompiler.flash.tags.DebugIDTag; import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; @@ -46,8 +47,10 @@ import com.jpexs.decompiler.flash.tags.DefineShapeTag; import com.jpexs.decompiler.flash.tags.DefineSoundTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; import com.jpexs.decompiler.flash.tags.DefineText2Tag; +import com.jpexs.decompiler.flash.tags.DefineTextFormatTag; import com.jpexs.decompiler.flash.tags.DefineTextTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.DefineVideoTag; import com.jpexs.decompiler.flash.tags.DoABC2Tag; import com.jpexs.decompiler.flash.tags.DoABCTag; import com.jpexs.decompiler.flash.tags.DoActionTag; @@ -57,11 +60,14 @@ import com.jpexs.decompiler.flash.tags.EnableDebuggerTag; import com.jpexs.decompiler.flash.tags.EnableTelemetryTag; import com.jpexs.decompiler.flash.tags.ExportAssetsTag; import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.FontRefTag; import com.jpexs.decompiler.flash.tags.FrameLabelTag; +import com.jpexs.decompiler.flash.tags.GenCommandTag; import com.jpexs.decompiler.flash.tags.ImportAssets2Tag; import com.jpexs.decompiler.flash.tags.ImportAssetsTag; import com.jpexs.decompiler.flash.tags.JPEGTablesTag; import com.jpexs.decompiler.flash.tags.MetadataTag; +import com.jpexs.decompiler.flash.tags.NameCharacterTag; import com.jpexs.decompiler.flash.tags.PlaceImagePrivateTag; import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; import com.jpexs.decompiler.flash.tags.PlaceObject3Tag; @@ -269,7 +275,7 @@ public class TagTree extends AbstractTagTree { } break; case TagTreeModel.FOLDER_MOVIES: - ret = Arrays.asList(DefineVideoStreamTag.ID); + ret = Arrays.asList(DefineVideoStreamTag.ID, DefineVideoTag.ID); break; case TagTreeModel.FOLDER_SOUNDS: ret = Arrays.asList(DefineSoundTag.ID); @@ -281,7 +287,7 @@ public class TagTree extends AbstractTagTree { if (gfx) { ret = Arrays.asList(DefineFontTag.ID, DefineFont2Tag.ID, DefineFont3Tag.ID, DefineFont4Tag.ID, DefineCompactedFont.ID); } else { - ret = Arrays.asList(DefineFontTag.ID, DefineFont2Tag.ID, DefineFont3Tag.ID, DefineFont4Tag.ID); + ret = Arrays.asList(DefineFontTag.ID, DefineFont2Tag.ID, DefineFont3Tag.ID, DefineFont4Tag.ID, FontRefTag.ID); } break; case TagTreeModel.FOLDER_BINARY_DATA: @@ -293,7 +299,8 @@ public class TagTree extends AbstractTagTree { RemoveObjectTag.ID, RemoveObject2Tag.ID, ShowFrameTag.ID, FrameLabelTag.ID, StartSoundTag.ID, StartSound2Tag.ID, VideoFrameTag.ID, SoundStreamBlockTag.ID, SoundStreamHeadTag.ID, SoundStreamHead2Tag.ID, - SetTabIndexTag.ID, PlaceImagePrivateTag.ID); + SetTabIndexTag.ID, PlaceImagePrivateTag.ID, + GenCommandTag.ID); break; case TagTreeModel.FOLDER_OTHERS: ret = Arrays.asList( @@ -308,7 +315,9 @@ public class TagTree extends AbstractTagTree { JPEGTablesTag.ID, MetadataTag.ID, ProductInfoTag.ID, ProtectTag.ID, ScriptLimitsTag.ID, SetBackgroundColorTag.ID, //SetTabIndexTag.ID, - SymbolClassTag.ID); + SymbolClassTag.ID, + CharacterSetTag.ID + ); break; } diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index 0c679f2f6..a5efb653c 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -142,6 +142,7 @@ import com.jpexs.decompiler.flash.types.CLIPACTIONS; import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA; import com.jpexs.decompiler.flash.types.HasCharacterId; import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import com.jpexs.decompiler.graph.CompilationException; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.ByteArrayRange; @@ -2021,17 +2022,29 @@ public class TagTreeContextMenu extends JPopupMenu { void call(ActionEvent evt, TreeItem item, Class cl, TreeNodeType createNodeType); } - private void addAddTagMenuFolder(JMenu addTagMenu, String folder, boolean gfx, TreeItem item, AddTagActionListener listener) { + @SuppressWarnings("unchecked") + private void addAddTagMenuFolder(JMenu addTagMenu, String folder, int swfVersion, boolean gfx, TreeItem item, AddTagActionListener listener) { String folderTranslated = AppStrings.translate("node." + folder); JMenu folderMenu = new JMenu(folderTranslated); folderMenu.setIcon(View.getIcon("folder" + folder.toLowerCase(Locale.ENGLISH) + "16")); - Map classes = Tag.getKnownClasses(); + Map> classes = Tag.getKnownClasses(); List allowedTagTypes = new ArrayList<>(TagTree.getSwfFolderItemNestedTagIds(folder, gfx)); Set mappedTagTypes = new LinkedHashSet<>(); for (int i : allowedTagTypes) { - mappedTagTypes.addAll(AbstractTagTree.getMappedTagIdsForClass(classes.get(i).getCls())); + List tagTypeInfos = classes.get(i); + for (TagTypeInfo tagTypeInfo : tagTypeInfos) { + if (tagTypeInfos.size() > 1) { + SWFVersion ver = (SWFVersion) tagTypeInfo.getCls().getAnnotation(SWFVersion.class); + if (ver != null) { + if (swfVersion < ver.from() || swfVersion > ver.to()) { + continue; + } + } + } + mappedTagTypes.addAll(AbstractTagTree.getMappedTagIdsForClass(tagTypeInfo.getCls())); + } } if (allowedTagTypes.isEmpty() && mappedTagTypes.isEmpty()) { return; @@ -2053,7 +2066,6 @@ public class TagTreeContextMenu extends JPopupMenu { private void addAddTagInsideMenuItems(TreeItem item) { AddTagActionListener listener = this::addTagInsideActionPerformed; - Map classes = Tag.getKnownClasses(); SWF currentSwf = mainPanel.getCurrentSwf(); if (currentSwf == null) { return; @@ -2104,8 +2116,6 @@ public class TagTreeContextMenu extends JPopupMenu { if (parent == null) { return; } - Map classes = Tag.getKnownClasses(); - SWF currentSwf = mainPanel.getCurrentSwf(); if (currentSwf == null) { @@ -2175,6 +2185,7 @@ public class TagTreeContextMenu extends JPopupMenu { } private void addAddTagMenuItems(List allowedTagTypes, JMenu addTagMenu, TreeItem item, AddTagActionListener listener, String parentFolder) { + int swfVersion = mainPanel.getCurrentSwf().version; if (allowedTagTypes == null) { boolean gfx = mainPanel.getCurrentSwf().gfx; @@ -2193,14 +2204,14 @@ public class TagTreeContextMenu extends JPopupMenu { TagTreeModel.FOLDER_OTHERS }; for (String folder : folders) { - addAddTagMenuFolder(addTagMenu, folder, gfx, item, listener); + addAddTagMenuFolder(addTagMenu, folder, swfVersion, gfx, item, listener); } return; } for (Integer tagId : allowedTagTypes) { - final Class cl = TagIdClassMap.getClassByTagId(tagId); + final Class cl = TagIdClassMap.getClassByTagId(tagId, swfVersion); String className = cl.getSimpleName(); if (className.endsWith("Tag")) { className = className.substring(0, className.length() - 3);