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)
This commit is contained in:
Jindra Petřík
2025-08-29 00:02:38 +02:00
parent d7665cf093
commit 1e9bf6be88
29 changed files with 1243 additions and 53 deletions

View File

@@ -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")
)
);
}

View File

@@ -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<TextFormatRecord> readTextFormatRecords(String name) throws IOException {
newDumpLevel(name, "TextFormatRecords");
List<TextFormatRecord> 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.
*

View File

@@ -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<TextFormatRecord> 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;
}
}
}
}

View File

@@ -157,13 +157,15 @@ public class SwfXmlImporter {
static {
Map<String, Class> tags = new HashMap<>();
Map<Integer, TagTypeInfo> knownTags = Tag.getKnownClasses();
Map<Integer, List<TagTypeInfo>> 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;

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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<TextFormatRecord> 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> parameterNames = new ArrayList<>();
@SWFArray(value = "value", countField = "paramCount")
@Table(value = "parameters", itemName = "parameter")
public List<String> 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<String, String> getNameProperties() {
Map<String, String> 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;
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);*/
}

View File

@@ -292,7 +292,7 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable {
private static volatile Integer[] knownTagIds;
private static volatile Map<Integer, TagTypeInfo> knownTagInfosById;
private static volatile Map<Integer, List<TagTypeInfo>> knownTagInfosById;
private static volatile Map<String, TagTypeInfo> knownTagInfosByName;
@@ -319,11 +319,11 @@ public abstract class Tag implements NeedsCharacters, Exportable, Serializable {
* Gets known classes.
* @return Known classes
*/
public static Map<Integer, TagTypeInfo> getKnownClasses() {
public static Map<Integer, List<TagTypeInfo>> getKnownClasses() {
if (knownTagInfosById == null) {
synchronized (lockObject) {
if (knownTagInfosById == null) {
Map<Integer, TagTypeInfo> map = new HashMap<>();
Map<Integer, List<TagTypeInfo>> map = new HashMap<>();
Map<String, TagTypeInfo> 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<Integer, TagTypeInfo> map, Map<String, TagTypeInfo> map2, int id, Class cls, String name) {
map.put(id, new TagTypeInfo(id, cls, name));
private static void addTagInfo(Map<Integer, List<TagTypeInfo>> map, Map<String, TagTypeInfo> 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));
}

View File

@@ -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<String, String> getNameProperties() {
Map<String, String> ret = super.getNameProperties();
Map<Integer, TagTypeInfo> classes = Tag.getKnownClasses();
if (classes.containsKey(id)) {
ret.put("tcl", classes.get(id).getName());
Map<Integer, List<TagTypeInfo>> 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;

View File

@@ -1,4 +1,4 @@
/**
* Text tags.
* Text tag types.
*/
package com.jpexs.decompiler.flash.tags.text;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Text format tag types.
*/
package com.jpexs.decompiler.flash.types.text.format;