| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package flash.swf; |
| |
| import flash.swf.debug.DebugTable; |
| import flash.swf.tags.*; |
| import flash.swf.types.ButtonCondAction; |
| import flash.swf.types.ButtonRecord; |
| import flash.swf.types.CXForm; |
| import flash.swf.types.CXFormWithAlpha; |
| import flash.swf.types.CurvedEdgeRecord; |
| import flash.swf.types.FillStyle; |
| import flash.swf.types.Filter; |
| import flash.swf.types.FlashUUID; |
| import flash.swf.types.GlyphEntry; |
| import flash.swf.types.GradRecord; |
| import flash.swf.types.ImportRecord; |
| import flash.swf.types.KerningRecord; |
| import flash.swf.types.LineStyle; |
| import flash.swf.types.Matrix; |
| import flash.swf.types.MorphFillStyle; |
| import flash.swf.types.MorphGradRecord; |
| import flash.swf.types.MorphLineStyle; |
| import flash.swf.types.Rect; |
| import flash.swf.types.Shape; |
| import flash.swf.types.ShapeRecord; |
| import flash.swf.types.ShapeWithStyle; |
| import flash.swf.types.SoundInfo; |
| import flash.swf.types.StraightEdgeRecord; |
| import flash.swf.types.StyleChangeRecord; |
| import flash.swf.types.TextRecord; |
| import flash.swf.types.DropShadowFilter; |
| import flash.swf.types.BlurFilter; |
| import flash.swf.types.GlowFilter; |
| import flash.swf.types.BevelFilter; |
| import flash.swf.types.GradientGlowFilter; |
| import flash.swf.types.ConvolutionFilter; |
| import flash.swf.types.ColorMatrixFilter; |
| import flash.swf.types.GradientBevelFilter; |
| import flash.swf.types.Gradient; |
| import flash.swf.types.FocalGradient; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.DataInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.zip.InflaterInputStream; |
| |
| /** |
| * A SWF tag decoder. It is typically used by passing an InputStream |
| * to the constructor and then calling parse() with a TagHandler. |
| */ |
| public final class TagDecoder |
| implements TagValues |
| { |
| public TagDecoder(InputStream swfIn) |
| { |
| this.swfIn = swfIn; |
| this.swdIn = null; |
| } |
| |
| public TagDecoder(InputStream swfIn, InputStream swdIn) |
| { |
| this.swfIn = swfIn; |
| this.swdIn = swdIn; |
| } |
| |
| public TagDecoder(InputStream in, URL swfUrl) |
| { |
| this.swfIn = in; |
| this.swfUrl = swfUrl; |
| } |
| |
| private Header header; |
| private InputStream swfIn; |
| private InputStream swdIn; |
| private URL swfUrl; |
| private DebugTable swd; |
| private SwfDecoder r; |
| private GenericTag jpegTables; |
| private TagHandler handler; |
| private boolean keepOffsets; |
| |
| private Dictionary dict = new Dictionary(); |
| |
| /** |
| * thrown by decoders when we have a fatal error. Many errors |
| * are not fatal. In those cases, the error is reported but |
| * parsing continues. |
| */ |
| public static class FatalParseException extends Exception { |
| |
| private static final long serialVersionUID = 5819679367367802771L;} |
| |
| public void setKeepOffsets(boolean b) |
| { |
| keepOffsets = b; |
| } |
| |
| /** |
| * process the whole SWF stream, and close the input streams when finished. |
| * @param handler |
| * @throws IOException |
| */ |
| public void parse(TagHandler handler) throws IOException |
| { |
| this.handler = handler; |
| try |
| { |
| try |
| { |
| handler.setDecoderDictionary(dict); |
| |
| header = decodeHeader(); |
| handler.header(header); |
| |
| decodeTags(handler); |
| handler.finish(); |
| } |
| catch( FatalParseException e ) |
| { |
| // errors already reported to TagHandler. |
| } |
| finally |
| { |
| if (swfIn != null) |
| swfIn.close(); |
| } |
| } |
| finally |
| { |
| if (swdIn != null) |
| swdIn.close(); |
| } |
| } |
| |
| public int getSwfVersion() |
| { |
| return header.version; |
| } |
| |
| private void decodeTags(TagHandler handler) throws IOException |
| { |
| int type, h, length, currentOffset; |
| |
| do |
| { |
| currentOffset = r.getOffset(); |
| |
| type = (h = r.readUI16()) >> 6; |
| |
| // is this a long tag header (>=63 bytes)? |
| if (((length = h & 0x3F) == 0x3F)) |
| { |
| // [ed] the player treats this as a signed field and stops if it is negative. |
| length = r.readSI32(); |
| if (length < 0) |
| { |
| handler.error("negative tag length: " + length + " at offset " + currentOffset); |
| break; |
| } |
| } |
| int o = r.getOffset(); |
| int eat = 0; |
| |
| |
| if (type != 0) |
| { |
| Tag t = decodeTag(type, length); |
| if (r.getOffset() - o != length) |
| { |
| handler.error("offset mismatch after " + Tag.names[t.code] + ": read " + (r.getOffset() - o) + ", expected " + length); |
| if (r.getOffset() - o < length) |
| { |
| eat = length - (r.getOffset() - o); |
| |
| } |
| } |
| handler.setOffsetAndSize(currentOffset, r.getOffset() - currentOffset); |
| handler.any( t ); |
| t.visit(handler); |
| if (eat > 0) // try to recover. (flash 8 sometimes writes nonsense, usually in fonts) |
| { |
| r.read( new byte[eat] ); |
| |
| } |
| } |
| } |
| while (type != 0); |
| } |
| |
| private Tag decodeTag(int type, int length) throws IOException |
| { |
| Tag t; |
| int pos = r.getOffset(); |
| |
| switch (type) |
| { |
| case stagProductInfo: |
| t = decodeSerialNumber(); |
| break; |
| case stagShowFrame: |
| t = new ShowFrame(); |
| break; |
| case stagMetadata: |
| t = decodeMetadata(); |
| break; |
| case stagDefineShape: |
| case stagDefineShape2: |
| case stagDefineShape3: |
| case stagDefineShape4: |
| t = decodeDefineShape(type); |
| break; |
| case stagPlaceObject: |
| t = decodePlaceObject(length); |
| break; |
| case stagRemoveObject: |
| case stagRemoveObject2: |
| t = decodeRemoveObject(type); |
| break; |
| case stagDefineBinaryData: |
| t = decodeDefineBinaryData(length); |
| break; |
| case stagDefineBits: |
| t = decodeDefineBits(length); |
| break; |
| case stagDefineButton: |
| t = decodeDefineButton(length); |
| break; |
| case stagJPEGTables: |
| t = jpegTables = decodeJPEGTables(length); |
| break; |
| case stagSetBackgroundColor: |
| t = decodeSetBackgroundColor(); |
| break; |
| case stagDefineFont: |
| t = decodeDefineFont(); |
| break; |
| case stagDefineText: |
| case stagDefineText2: |
| t = decodeDefineText(type); |
| break; |
| case stagDoAction: |
| t = decodeDoAction(length); |
| break; |
| case stagDefineFontInfo: |
| case stagDefineFontInfo2: |
| t = decodeDefineFontInfo(type, length); |
| break; |
| case stagDefineSound: |
| t = decodeDefineSound(length); |
| break; |
| case stagStartSound: |
| t = decodeStartSound(); |
| break; |
| case stagDefineButtonSound: |
| t = decodeDefineButtonSound(); |
| break; |
| case stagSoundStreamHead2: |
| case stagSoundStreamHead: |
| t = decodeSoundStreamHead(type); |
| break; |
| case stagSoundStreamBlock: |
| t = decodeSoundStreamBlock(length); |
| break; |
| case stagDefineBitsLossless: |
| t = decodeDefineBitsLossless(length); |
| break; |
| case stagDefineBitsJPEG2: |
| t = decodeDefineJPEG2(length); |
| break; |
| case stagDefineButtonCxform: |
| t = decodeDefineButtonCxform(); |
| break; |
| case stagProtect: |
| t = decodeProtect(length); |
| break; |
| case stagPlaceObject2: |
| t = decodePlaceObject23(stagPlaceObject2, length); |
| break; |
| case stagPlaceObject3: |
| t = decodePlaceObject23(stagPlaceObject3, length); |
| break; |
| case stagDefineButton2: |
| t = decodeDefineButton2(length); |
| break; |
| case stagDefineBitsJPEG3: |
| t = decodeDefineJPEG3(length); |
| break; |
| case stagDefineBitsLossless2: |
| t = decodeDefineBitsLossless2(length); |
| break; |
| case stagDefineEditText: |
| t = decodeDefineEditText(); |
| break; |
| case stagDefineSprite: |
| t = decodeDefineSprite(pos+length); |
| break; |
| case stagDefineScalingGrid: |
| t = decodeDefineScalingGrid(); |
| break; |
| case stagFrameLabel: |
| t = decodeFrameLabel(length); |
| break; |
| case stagDefineMorphShape: |
| t = decodeDefineMorphShape(); |
| break; |
| case stagDefineMorphShape2: |
| t = decodeDefineMorphShape2(); |
| break; |
| case stagDefineFont2: |
| t = decodeDefineFont2(); |
| break; |
| case stagDefineFont3: |
| t = decodeDefineFont3(); |
| break; |
| case stagDefineFont4: |
| t = decodeDefineFont4(length); |
| break; |
| case stagExportAssets: |
| t = decodeExportAssets(); |
| break; |
| case stagImportAssets: |
| case stagImportAssets2: |
| t = decodeImportAssets(type); |
| break; |
| case stagEnableDebugger2: |
| case stagEnableDebugger: |
| t = decodeEnableDebugger(type); |
| break; |
| case stagDoInitAction: |
| t = decodeDoInitAction(length); |
| break; |
| case stagDefineVideoStream: |
| t = decodeDefineVideoStream(); |
| break; |
| case stagVideoFrame: |
| t = decodeVideoFrame(length); |
| break; |
| case stagDebugID: |
| t = decodeDebugID(type, length); |
| break; |
| case stagScriptLimits: |
| t = decodeScriptLimits(); |
| break; |
| case stagSetTabIndex: |
| t = decodeSetTabIndex(); |
| break; |
| case stagDoABC: |
| case stagDoABC2: |
| t = decodeDoABC(type, length); |
| break; |
| case stagSymbolClass: |
| t = decodeSymbolClass(); |
| break; |
| case stagFileAttributes: |
| t = decodeFileAttributes(); |
| break; |
| case stagEnableTelemetry: |
| t = decodeEnableTelemetry(); |
| break; |
| case stagDefineFontAlignZones: |
| t = decodeDefineFontAlignZones(); |
| break; |
| case stagCSMTextSettings: |
| t = decodeCSMTextSettings(); |
| break; |
| case stagDefineSceneAndFrameLabelData: |
| t = decodeDefineSceneAndFrameData(length); |
| break; |
| case stagDefineFontName: |
| t = decodeDefineFontName(); |
| break; |
| default: |
| t = decodeUnknown(length, type); |
| break; |
| } |
| |
| int consumed = r.getOffset() - pos; |
| |
| // [preilly] It looks like past Authoring tools have generated some SWF's with |
| // stagSoundStreamHead tags of length 4 with compression set to mp3, but the tag |
| // really has 6 bytes in it and the player always reads the 6 bytes, so ignore the |
| // difference between the consumed and the length for this special case. |
| if ((consumed != length) && (type == stagSoundStreamHead) && (consumed != (length + 2))) |
| { |
| throw new SwfFormatException(TagValues.names[type] + " at pos "+pos+ ": " + consumed + |
| " bytes were read. " + length + " byte were required."); |
| } |
| return t; |
| } |
| |
| private Tag decodeDefineSceneAndFrameData(int length) throws IOException |
| { |
| DefineSceneAndFrameLabelData t = new DefineSceneAndFrameLabelData(); |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| return t; |
| } |
| |
| private Tag decodeDoABC(int type, int length) throws IOException |
| { |
| DoABC t; |
| |
| if (type == stagDoABC2) |
| { |
| int pos = r.getOffset(); |
| int skip = r.readSI32(); |
| String name = r.readString(); |
| t = new DoABC( name , skip ); |
| // cannot just use length of string, because might not match |
| // the number of bytes in the string for nonascii characters |
| //length -= (4 + name.length() + 1); |
| length -= (r.getOffset() - pos); |
| } |
| else |
| { |
| t = new DoABC(); |
| } |
| |
| t.abc = new byte[length]; |
| r.readFully(t.abc); |
| return t; |
| } |
| |
| private Tag decodeSymbolClass() throws IOException |
| { |
| SymbolClass t = new SymbolClass(); |
| int count = r.readUI16(); |
| |
| t.class2tag = new HashMap<String, Tag>(count); |
| |
| for (int i=0; i < count; i++) |
| { |
| int idref = r.readUI16(); |
| String name = r.readString(); |
| if (idref == 0) |
| { |
| t.topLevelClass = name; |
| continue; |
| } |
| DefineTag ref = dict.getTag(idref); |
| t.class2tag.put(name, ref); |
| if (ref.name != null) |
| { |
| if (!ref.name.equals(name)) |
| { |
| handler.error("SymbolClass: symbol " + idref + " already exported as " + ref.name); |
| } |
| //else |
| //{ |
| // FIXME: is this right? seem to be getting redundant message in swfdumps that work right in the player |
| // FIXME: We should eventually enforce that export names not be used in zaphod movies, |
| // FIXME: but in the short term, all this message means is that the symbol was both exported |
| // FIXME: via ExportAssets and also associated with a class via SymbolClass. They have |
| // FIXME: different semantic meanings, so this error is a bit off-base. --rg |
| // handler.error("Redundant SymbolClass of " + ref.name + ". Found " + ref.getClass().getName() + " of same name in dictionary."); |
| //} |
| } |
| else |
| { |
| DefineTag other = dict.getTag(name); |
| if (other != null) |
| { |
| int id = dict.getId(other); |
| handler.error("Symbol " + name + " already refers to ID " + id); |
| } |
| ref.name = name; |
| dict.addName(ref, name); |
| } |
| } |
| return t; |
| } |
| |
| private Tag decodeSetTabIndex() throws IOException |
| { |
| int depth = r.readUI16(); |
| int index = r.readUI16(); |
| return new SetTabIndex(depth, index); |
| } |
| |
| private Tag decodeUnknown(int length, int code) throws IOException |
| { |
| GenericTag t; |
| t = new GenericTag(code); |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| return t; |
| } |
| |
| private ScriptLimits decodeScriptLimits() throws IOException |
| { |
| ScriptLimits scriptLimits = new ScriptLimits(r.readUI16(), r.readUI16()); |
| return scriptLimits; |
| } |
| |
| private Tag decodeDebugID(int type, int length) throws IOException |
| { |
| DebugID t; |
| t = new DebugID(type); |
| t.uuid = decodeFlashUUID(length); |
| |
| if (swdIn != null) |
| { |
| InputStream in = swdIn; |
| DebugTable swd = new DebugTable(); |
| new DebugDecoder(in).readSwd(swd); |
| |
| if (!t.uuid.equals(swd.uuid)) |
| { |
| handler.error("SWD uuid "+swd.uuid+" doesn't match "+t.uuid); |
| } |
| else if (swd.version != getSwfVersion()) |
| { |
| handler.error("SWD version number "+swd.version+" doesn't match SWF version number "+getSwfVersion()); |
| } |
| else |
| { |
| this.swd = swd; |
| } |
| } |
| else if (swfUrl != null) |
| { |
| // look for a SWD file in the same place the player would look |
| String path = swfUrl.toString(); |
| |
| int q = path.indexOf("?"); |
| String query = null; |
| if (q != -1) |
| { |
| query = path.substring(q); |
| path = path.substring(0, q); |
| } |
| |
| URL swdUrl; |
| if (path.endsWith(".swf")) |
| { |
| path = path.substring(0,path.length()-4)+".swd"; |
| } |
| else |
| { |
| path = path + ".swd"; |
| } |
| |
| if (query != null) |
| { |
| path = path + query; |
| } |
| |
| swdUrl = new URL(path); |
| |
| try |
| { |
| InputStream in = swdUrl.openStream(); |
| DebugTable swd = new DebugTable(); |
| new DebugDecoder(in).readSwd(swd); |
| |
| if (!t.uuid.equals(swd.uuid)) |
| { |
| handler.error("SWD uuid "+swd.uuid+" doesn't match "+t.uuid); |
| } |
| else if (swd.version != getSwfVersion()) |
| { |
| handler.error("SWD version number "+swd.version+" doesn't match SWF version number "+getSwfVersion()); |
| } |
| else |
| { |
| this.swd = swd; |
| } |
| } |
| catch (FileNotFoundException ex) |
| { |
| handler.error("SWD not found at url " + swdUrl); |
| } |
| } |
| |
| return t; |
| } |
| |
| private FlashUUID decodeFlashUUID(int length) throws IOException |
| { |
| byte[] uuid = new byte[length]; |
| r.readFully(uuid); |
| return new FlashUUID(uuid); |
| } |
| |
| private Tag decodeVideoFrame(int length) throws IOException |
| { |
| VideoFrame t; |
| t = new VideoFrame(); |
| int pos = r.getOffset(); |
| |
| int idref = r.readUI16(); |
| t.stream = (DefineVideoStream) dict.getTag(idref); |
| t.frameNum = r.readUI16(); |
| |
| length -= r.getOffset() - pos; |
| |
| t.videoData = new byte[length]; |
| r.readFully(t.videoData); |
| return t; |
| } |
| |
| private Tag decodeDefineVideoStream() throws IOException |
| { |
| DefineVideoStream t; |
| t = new DefineVideoStream(); |
| int id = r.readUI16(); |
| t.numFrames = r.readUI16(); |
| t.width = r.readUI16(); |
| t.height = r.readUI16(); |
| |
| r.syncBits(); |
| |
| r.readUBits(4); // reserved |
| t.deblocking = r.readUBits(3); |
| t.smoothing = r.readBit(); |
| |
| t.codecID = r.readUI8(); |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private Tag decodeDoInitAction(int length) throws IOException |
| { |
| DoInitAction t; |
| t = new DoInitAction(); |
| int idref = r.readUI16(); |
| try |
| { |
| t.sprite = (DefineSprite) dict.getTag(idref); |
| if (t.sprite.initAction != null) |
| { |
| handler.error("Sprite " + idref + " initaction redefined"); |
| } |
| else |
| { |
| t.sprite.initAction = t; |
| } |
| } |
| catch (IllegalArgumentException e) |
| { |
| handler.error(e.getMessage()); |
| } |
| ActionDecoder actionDecoder = new ActionDecoder(r,swd); |
| actionDecoder.setKeepOffsets(keepOffsets); |
| t.actionList = actionDecoder.decode(length-2); |
| return t; |
| } |
| |
| private Tag decodeEnableDebugger(int code) throws IOException |
| { |
| EnableDebugger t; |
| t = new EnableDebugger(code); |
| if (code == stagEnableDebugger2) |
| { |
| if (getSwfVersion() < 6) |
| handler.error("EnableDebugger2 not valid before SWF 6"); |
| t.reserved = r.readUI16(); // reserved |
| } |
| t.password = r.readString(); |
| return t; |
| } |
| |
| private Tag decodeImportAssets(int code) throws IOException |
| { |
| ImportAssets t; |
| t = new ImportAssets(code); |
| |
| t.url = r.readString(); |
| if (code == stagImportAssets2) |
| { |
| t.downloadNow = (r.readUI8() == 1); |
| if (r.readUI8() == 1) // hasDigest == 1 |
| { |
| t.SHA1 = new byte[20]; |
| r.readFully(t.SHA1); |
| } |
| } |
| |
| int count = r.readUI16(); |
| t.importRecords = new ArrayList<ImportRecord>(); |
| |
| for (int i=0; i < count; i++) |
| { |
| ImportRecord ir = new ImportRecord(); |
| int id = r.readUI16(); |
| ir.name = r.readString(); |
| t.importRecords.add(ir); |
| dict.add(id, ir); |
| dict.addName(ir, ir.name); |
| } |
| return t; |
| } |
| |
| private Tag decodeExportAssets() throws IOException |
| { |
| ExportAssets t; |
| t = new ExportAssets(); |
| |
| int count = r.readUI16(); |
| |
| t.exports = new ArrayList<Tag>(count); |
| |
| for (int i=0; i < count; i++) |
| { |
| int idref = r.readUI16(); |
| String name = r.readString(); |
| DefineTag ref = dict.getTag(idref); |
| t.exports.add(ref); |
| if (ref.name != null) |
| { |
| if (!ref.name.equals(name)) |
| handler.error("ExportAsset: symbol " + idref + " already exported as " + ref.name); |
| else |
| handler.error("redundant export of "+ref.name); |
| } |
| else |
| { |
| DefineTag other = dict.getTag(name); |
| if (other != null) |
| { |
| int id = dict.getId(other); |
| handler.error("Symbol "+name+" already refers to ID "+id); |
| } |
| ref.name = name; |
| dict.addName(ref, name); |
| } |
| } |
| return t; |
| } |
| |
| private Tag decodeDefineFont2() throws IOException |
| { |
| DefineFont2 t = new DefineFont2(); |
| return decodeDefineFont2And3(t); |
| } |
| |
| private Tag decodeDefineFont3() throws IOException |
| { |
| DefineFont3 t = new DefineFont3(); |
| return decodeDefineFont2And3(t); |
| } |
| |
| private Tag decodeDefineFont2And3(DefineFont2 t) throws IOException |
| { |
| int id = r.readUI16(); |
| |
| r.syncBits(); |
| |
| t.hasLayout = r.readBit(); |
| t.shiftJIS = r.readBit(); |
| t.smallText = r.readBit(); |
| t.ansi = r.readBit(); |
| t.wideOffsets = r.readBit(); |
| t.wideCodes = r.readBit(); |
| // enable after we're sure that bug 147073 isn't a bug. If it is a bug, then this can be |
| // removed as well as the stageDefineFont3-specific code in TagEncoder.defineFont2() |
| //if (t.code == stagDefineFont3 && ! t.wideCodes) |
| //{ |
| // handler.error("widecodes must be true in DefineFont3"); |
| //} |
| |
| t.italic = r.readBit(); |
| t.bold = r.readBit(); |
| |
| t.langCode = r.readUI8(); |
| |
| t.fontName = r.readLengthString(); |
| |
| int numGlyphs = r.readUI16(); |
| |
| long[] offsets = new long[numGlyphs]; |
| for (int i = 0; i < numGlyphs; i++) |
| { |
| if (t.wideOffsets) |
| offsets[i] = r.readUI32(); |
| else |
| offsets[i] = r.readUI16(); |
| } |
| |
| long codeTableOffset = 0; |
| if (numGlyphs > 0) |
| { |
| if (t.wideOffsets) |
| codeTableOffset = r.readUI32(); |
| else |
| codeTableOffset = r.readUI16(); |
| } |
| |
| t.glyphShapeTable = new Shape[numGlyphs]; |
| |
| for (int i = 0; i < numGlyphs; i++) |
| { |
| int glyphLength; |
| if (i < (numGlyphs - 1)) |
| glyphLength = (int)(offsets[i+1] - offsets[i]); |
| else |
| glyphLength = (int)(codeTableOffset - offsets[i]); |
| |
| t.glyphShapeTable[i] = decodeGlyph(stagDefineShape3, glyphLength); |
| } |
| |
| t.codeTable = new char[numGlyphs]; |
| if (t.wideCodes) |
| { |
| for (int i = 0; i < numGlyphs; i++) |
| t.codeTable[i] = (char) r.readUI16(); |
| } |
| else |
| { |
| for (int i = 0; i < numGlyphs; i++) |
| t.codeTable[i] = (char) r.readUI8(); |
| } |
| |
| if (t.hasLayout) |
| { |
| t.ascent = r.readSI16(); |
| t.descent = r.readSI16(); |
| t.leading = r.readSI16(); |
| t.advanceTable = new short[numGlyphs]; |
| for (int i = 0; i < numGlyphs; i++) |
| { |
| t.advanceTable[i] = (short)r.readSI16(); |
| } |
| t.boundsTable = new Rect[numGlyphs]; |
| for (int i = 0; i < numGlyphs; i++) |
| { |
| t.boundsTable[i] = decodeRect(); |
| } |
| |
| t.kerningCount = r.readUI16(); |
| t.kerningTable = new KerningRecord[t.kerningCount]; |
| for (int i = 0; i < t.kerningCount; i++) |
| { |
| t.kerningTable[i] = decodeKerningRecord(t.wideCodes); |
| } |
| } |
| |
| dict.add(id, t); |
| dict.addFontFace( t ); |
| return t; |
| } |
| |
| private Tag decodeDefineFont4(int length) throws IOException |
| { |
| DefineFont4 t = new DefineFont4(); |
| int pos = r.getOffset(); |
| |
| int id = r.readUI16(); |
| |
| r.syncBits(); |
| r.readUBits(5); // reserved |
| t.hasFontData = r.readBit(); |
| //t.smallText = r.readBit(); |
| t.italic = r.readBit(); |
| t.bold = r.readBit(); |
| |
| //t.langCode = r.readUI8(); |
| t.fontName = r.readString(); |
| |
| if (t.hasFontData) |
| { |
| length -= r.getOffset() - pos; |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| } |
| |
| dict.add(id, t); |
| dict.addFontFace(t); |
| return t; |
| } |
| |
| private KerningRecord decodeKerningRecord(boolean wideCodes) throws IOException |
| { |
| KerningRecord kr = new KerningRecord(); |
| |
| kr.code1 = (wideCodes) ? r.readUI16() : r.readUI8(); |
| kr.code2 = (wideCodes) ? r.readUI16() : r.readUI8(); |
| kr.adjustment = r.readUI16(); |
| |
| return kr; |
| } |
| |
| private Tag decodeDefineMorphShape() throws IOException |
| { |
| return decodeDefineMorphShape(stagDefineMorphShape); |
| } |
| |
| private Tag decodeDefineMorphShape2() throws IOException |
| { |
| return decodeDefineMorphShape(stagDefineMorphShape2); |
| } |
| |
| private Tag decodeDefineMorphShape(int code) throws IOException |
| { |
| DefineMorphShape t = new DefineMorphShape(code); |
| int id = r.readUI16(); |
| t.startBounds = decodeRect(); |
| t.endBounds = decodeRect(); |
| if (code == stagDefineMorphShape2) |
| { |
| t.startEdgeBounds = decodeRect(); |
| t.endEdgeBounds = decodeRect(); |
| r.readUBits(6); |
| t.usesNonScalingStrokes = r.readBit(); |
| t.usesScalingStrokes = r.readBit(); |
| } |
| int offset = (int)r.readUI32(); // offset to EndEdges |
| t.fillStyles = decodeMorphFillstyles(code); |
| t.lineStyles = decodeMorphLinestyles(code); |
| t.startEdges = decodeShape(stagDefineShape3); |
| if (offset != 0) |
| t.endEdges = decodeShape(stagDefineShape3); |
| dict.add(id, t); |
| return t; |
| } |
| |
| private MorphLineStyle[] decodeMorphLinestyles(int code) throws IOException |
| { |
| int count = r.readUI8(); |
| if (count == 0xFF) |
| { |
| count = r.readUI16(); |
| } |
| |
| MorphLineStyle[] styles = new MorphLineStyle[count]; |
| |
| for (int i = 0; i < count; i++) |
| { |
| MorphLineStyle s = new MorphLineStyle(); |
| s.startWidth = r.readUI16(); |
| s.endWidth = r.readUI16(); |
| if (code == stagDefineMorphShape2) |
| { |
| s.startCapsStyle = r.readUBits(2); |
| s.jointStyle = r.readUBits(2); |
| s.hasFill = r.readBit(); |
| s.noHScale = r.readBit(); |
| s.noVScale = r.readBit(); |
| s.pixelHinting = r.readBit(); |
| r.readUBits(5); // reserved |
| s.noClose = r.readBit(); |
| s.endCapsStyle = r.readUBits(2); |
| if (s.jointStyle == 2) |
| { |
| s.miterLimit = r.readUI16(); |
| } |
| } |
| if (!s.hasFill) |
| { |
| s.startColor = decodeRGBA(r); |
| s.endColor = decodeRGBA(r); |
| } |
| if (s.hasFill) |
| { |
| s.fillType = decodeMorphFillStyle(code); |
| } |
| |
| styles[i] = s; |
| } |
| |
| return styles; |
| } |
| |
| private MorphFillStyle[] decodeMorphFillstyles(int shape) throws IOException |
| { |
| int count = r.readUI8(); |
| if (count == 0xFF) |
| { |
| count = r.readUI16(); |
| } |
| |
| MorphFillStyle[] styles = new MorphFillStyle[count]; |
| |
| for (int i = 0; i < count; i++) |
| { |
| styles[i] = decodeMorphFillStyle(shape); |
| } |
| |
| return styles; |
| } |
| |
| private MorphFillStyle decodeMorphFillStyle(int shape) throws IOException |
| { |
| MorphFillStyle s = new MorphFillStyle(); |
| |
| s.type = r.readUI8(); |
| switch (s.type) |
| { |
| case FillStyle.FILL_SOLID: // 0x00 |
| s.startColor = decodeRGBA(r); |
| s.endColor = decodeRGBA(r); |
| break; |
| case FillStyle.FILL_GRADIENT: // 0x10 linear gradient fill |
| case FillStyle.FILL_RADIAL_GRADIENT: // 0x12 radial gradient fill |
| case FillStyle.FILL_FOCAL_RADIAL_GRADIENT: // 0x13 focal radial gradient fill |
| s.startGradientMatrix = decodeMatrix(); |
| s.endGradientMatrix = decodeMatrix(); |
| s.gradRecords = decodeMorphGradient(); |
| if (s.type == FillStyle.FILL_FOCAL_RADIAL_GRADIENT && shape == stagDefineMorphShape2) |
| { |
| s.ratio1 = r.readSI16(); |
| s.ratio2 = r.readSI16(); |
| } |
| break; |
| case FillStyle.FILL_BITS: // 0x40 tiled bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP): // 0x41 clipped bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_NOSMOOTH): // 0x42 tiled non-smoothed fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP | FillStyle.FILL_BITS_NOSMOOTH): // 0x43 clipped non-smoothed fill |
| int idref = r.readUI16(); |
| try |
| { |
| s.bitmap = dict.getTag(idref); |
| } |
| catch (IllegalArgumentException ex) |
| { |
| handler.error(ex.getMessage()); |
| s.bitmap = null; |
| } |
| s.startBitmapMatrix = decodeMatrix(); |
| s.endBitmapMatrix = decodeMatrix(); |
| break; |
| default: |
| throw new SwfFormatException("unrecognized fill style type: " + s.type); |
| } |
| |
| return s; |
| } |
| |
| private MorphGradRecord[] decodeMorphGradient() throws IOException |
| { |
| int num = r.readUI8(); |
| MorphGradRecord[] gradRecords = new MorphGradRecord[num]; |
| |
| for (int i = 0; i < num; i++) |
| { |
| MorphGradRecord g = new MorphGradRecord(); |
| g.startRatio = r.readUI8(); |
| g.startColor = decodeRGBA(r); |
| g.endRatio = r.readUI8(); |
| g.endColor = decodeRGBA(r); |
| |
| gradRecords[i] = g; |
| } |
| |
| return gradRecords; |
| } |
| |
| private Tag decodeFrameLabel(int length) throws IOException |
| { |
| FrameLabel t = new FrameLabel(); |
| int pos = r.getOffset(); |
| t.label = r.readString(); |
| if (getSwfVersion() >= 6) |
| { |
| if (length - r.getOffset() + pos == 1) |
| { |
| int anchor = r.readUI8(); |
| if (anchor != 0 && anchor != 1) |
| handler.error("illegal anchor value: "+anchor+". Must be 0 or 1"); |
| // player treats any nonzero value as true |
| t.anchor = (anchor != 0); |
| } |
| } |
| return t; |
| } |
| |
| private Tag decodeDefineEditText() throws IOException |
| { |
| DefineEditText t; |
| t = new DefineEditText(); |
| int id = r.readUI16(); |
| t.bounds = decodeRect(); |
| |
| r.syncBits(); |
| |
| t.hasText = r.readBit(); |
| t.wordWrap = r.readBit(); |
| t.multiline = r.readBit(); |
| t.password = r.readBit(); |
| t.readOnly = r.readBit(); |
| t.hasTextColor = r.readBit(); |
| t.hasMaxLength = r.readBit(); |
| t.hasFont = r.readBit(); |
| t.hasFontClass = r.readBit(); // FP 9.0.45 or later |
| t.autoSize = r.readBit(); |
| t.hasLayout = r.readBit(); |
| t.noSelect = r.readBit(); |
| t.border = r.readBit(); |
| t.wasStatic = r.readBit(); |
| t.html = r.readBit(); |
| t.useOutlines = r.readBit(); |
| |
| if (t.hasFont) |
| { |
| int idref = r.readUI16(); |
| t.font = (DefineFont) dict.getTag(idref); |
| t.height = r.readUI16(); |
| } |
| |
| if (t.hasFontClass) |
| { |
| t.fontClass = r.readString(); |
| t.height = r.readUI16(); |
| } |
| |
| if (t.hasTextColor) |
| { |
| t.color = decodeRGBA(r); |
| } |
| |
| if (t.hasMaxLength) |
| { |
| t.maxLength = r.readUI16(); |
| } |
| |
| if (t.hasLayout) |
| { |
| t.align = r.readUI8(); |
| t.leftMargin = r.readUI16(); |
| t.rightMargin = r.readUI16(); |
| t.ident = r.readUI16(); |
| t.leading = r.readSI16(); // see errata, leading is signed |
| } |
| |
| t.varName = r.readString(); |
| |
| if (t.hasText) |
| { |
| t.initialText = r.readString(); |
| } |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private Tag decodeDefineScalingGrid() throws IOException |
| { |
| DefineScalingGrid t = new DefineScalingGrid(); |
| int idref = r.readUI16(); |
| try |
| { |
| t.scalingTarget = dict.getTag(idref); |
| if (t.scalingTarget instanceof DefineSprite) |
| { |
| DefineSprite targetSprite = (DefineSprite) t.scalingTarget; |
| if (targetSprite.scalingGrid != null) |
| { |
| handler.error("Sprite " + idref + " scaling grid redefined" ); |
| } |
| targetSprite.scalingGrid = t; |
| } |
| else if (t.scalingTarget instanceof DefineButton) |
| { |
| DefineButton targetButton = (DefineButton) t.scalingTarget; |
| if (targetButton.scalingGrid != null) |
| { |
| handler.error("Button " + idref + " scaling grid redefined"); |
| } |
| targetButton.scalingGrid = t; |
| } |
| } |
| catch (Exception e) |
| { |
| return null; |
| } |
| t.rect = decodeRect(); |
| return t; |
| } |
| |
| private Tag decodeDefineBitsLossless2(int length) throws IOException |
| { |
| DefineBitsLossless t; |
| t = new DefineBitsLossless(stagDefineBitsLossless2); |
| SwfDecoder r1 = r; |
| |
| int pos = r1.getOffset(); |
| |
| int id = r1.readUI16(); |
| t.format = r1.readUI8(); |
| t.width = r1.readUI16(); |
| t.height = r1.readUI16(); |
| |
| byte[] data; |
| |
| switch (t.format) |
| { |
| case 3: |
| int colorTableSize = r1.readUI8()+1; |
| length -= r1.getOffset() - pos; |
| data = new byte[length]; |
| r1.readFully(data); |
| r1 = new SwfDecoder(new InflaterInputStream(new ByteArrayInputStream(data)), getSwfVersion()); |
| decodeAlphaColorMapData(r1, t, colorTableSize); |
| break; |
| case 4: |
| case 5: |
| length -= r1.getOffset() - pos; |
| data = new byte[length]; |
| r1.readFully(data); |
| r1 = new SwfDecoder(new InflaterInputStream(new ByteArrayInputStream(data)), getSwfVersion()); |
| t.data = new byte[t.width * t.height * 4]; |
| r1.readFully(t.data); |
| break; |
| default: |
| throw new SwfFormatException("Illegal bitmap format " + t.format); |
| } |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private void decodeAlphaColorMapData(SwfDecoder r1, DefineBitsLossless tag, int tableSize) throws IOException |
| { |
| int width = tag.width; |
| int height = tag.height; |
| |
| tag.colorData = new int[tableSize]; |
| |
| for (int i = 0; i < tableSize; i++) |
| { |
| tag.colorData[i] = decodeRGBA(r1); |
| } |
| |
| if (width % 4 != 0) |
| { |
| width = (width / 4 + 1) * 4; |
| } |
| |
| int data_size = width * height; |
| |
| tag.data = new byte[data_size]; |
| //r1.read(tag.data); |
| |
| int i = 0; |
| int b; |
| while (i < data_size) |
| { |
| b = r1.readUI8(); |
| if (b != -1) |
| { |
| tag.data[i] = (byte) b; |
| i++; |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| int extra = 0; |
| while (r1.readUI8() != -1) |
| { |
| extra++; |
| } |
| |
| if (extra > 0) |
| { |
| throw new SwfFormatException(extra + " bytes of bitmap data (" + width + "x" + height + ") not read!"); |
| } |
| else if (i != data_size) |
| { |
| throw new SwfFormatException("(" + width + "x" + height + ") data buffer " + (data_size - i) + " bytes too big..."); |
| } |
| } |
| |
| private Tag decodeDefineJPEG3(int length) throws IOException |
| { |
| DefineBitsJPEG3 t; |
| t = new DefineBitsJPEG3(); |
| int pos = r.getOffset(); |
| int id = r.readUI16(); |
| t.alphaDataOffset = r.readUI32(); |
| |
| t.data = new byte[(int) t.alphaDataOffset]; |
| r.readFully(t.data); |
| |
| length -= r.getOffset() - pos; |
| byte[] temp = new byte[length]; |
| r.readFully(temp); |
| |
| SwfDecoder r1 = new SwfDecoder(new InflaterInputStream(new ByteArrayInputStream(temp)), getSwfVersion()); |
| |
| int alpha, i = 0; |
| byte[] alphaData = new byte[length]; |
| |
| while ((alpha = r1.readUI8()) != -1) |
| { |
| if (i == alphaData.length) |
| { |
| byte[] b = new byte[i + length]; |
| System.arraycopy(alphaData, 0, b, 0, alphaData.length); |
| alphaData = b; |
| } |
| alphaData[i] = (byte)alpha; |
| i++; |
| } |
| |
| t.alphaData = new byte[i]; |
| System.arraycopy(alphaData, 0, t.alphaData, 0, i); |
| |
| dict.add(id, t); |
| r1.close(); |
| return t; |
| } |
| |
| private Tag decodeDefineButton2(int length) throws IOException |
| { |
| int endpos = r.getOffset()+length; |
| DefineButton t = new DefineButton(stagDefineButton2); |
| |
| int id = r.readUI16(); |
| |
| r.syncBits(); |
| r.readUBits(7); // reserved |
| t.trackAsMenu = r.readBit(); |
| |
| int actionOffset = r.readUI16(); |
| |
| // read button data |
| ArrayList<Object> list = new ArrayList<Object>(5); |
| ButtonRecord record; |
| while ((record = decodeButtonRecord(t.code)) != null) |
| { |
| list.add(record); |
| } |
| |
| t.buttonRecords = new ButtonRecord[list.size()]; |
| list.toArray(t.buttonRecords); |
| list.clear(); |
| |
| if (actionOffset > 0) |
| { |
| list = new ArrayList<Object>(); |
| |
| int pos = r.getOffset(); |
| while ((actionOffset = r.readUI16()) > 0) |
| { |
| list.add(decodeButtonCondAction(actionOffset-2)); |
| if (r.getOffset() != pos+actionOffset) |
| { |
| throw new SwfFormatException("incorrect offset read in ButtonCondAction. read "+actionOffset+""); |
| } |
| pos = r.getOffset(); |
| } |
| // actionOffset == 0 means this will be the last record |
| list.add(decodeButtonCondAction(endpos-r.getOffset())); |
| |
| t.condActions = new ButtonCondAction[list.size()]; |
| list.toArray(t.condActions); |
| } |
| else |
| { |
| t.condActions = new ButtonCondAction[0]; |
| } |
| while (r.getOffset() < endpos) |
| { |
| int b = r.readUI8(); |
| if (b != 0) |
| { |
| throw new SwfFormatException("nonzero data past end of DefineButton2"); |
| } |
| } |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private ButtonCondAction decodeButtonCondAction(int length) throws IOException |
| { |
| ButtonCondAction a = new ButtonCondAction(); |
| r.syncBits(); |
| a.keyPress = r.readUBits(7); |
| a.overDownToIdle = r.readBit(); |
| |
| a.idleToOverDown = r.readBit(); |
| a.outDownToIdle = r.readBit(); |
| a.outDownToOverDown = r.readBit(); |
| a.overDownToOutDown = r.readBit(); |
| a.overDownToOverUp = r.readBit(); |
| a.overUpToOverDown = r.readBit(); |
| a.overUpToIdle = r.readBit(); |
| a.idleToOverUp = r.readBit(); |
| |
| ActionDecoder actionDecoder = new ActionDecoder(r,swd); |
| actionDecoder.setKeepOffsets(keepOffsets); |
| a.actionList = actionDecoder.decode(length-2); |
| |
| return a; |
| } |
| |
| private Tag decodePlaceObject23(int type, int length) throws IOException |
| { |
| PlaceObject t = new PlaceObject(type); |
| int pos = r.getOffset(); |
| t.flags = r.readUI8(); |
| if (type == stagPlaceObject3) |
| { |
| t.flags2 = r.readUI8(); |
| } |
| t.depth = r.readUI16(); |
| if (t.hasClassName()) |
| { |
| t.className = r.readString(); |
| } |
| if (t.hasCharID()) |
| { |
| int idref = r.readUI16(); |
| t.setRef(dict.getTag(idref)); |
| } |
| if (t.hasMatrix()) |
| { |
| t.matrix = decodeMatrix(); |
| } |
| if (t.hasCxform()) |
| { |
| t.setCxform(decodeCxforma()); |
| } |
| if (t.hasRatio()) |
| { |
| t.ratio = r.readUI16(); |
| } |
| if (t.hasName()) |
| { |
| t.name = r.readString(); |
| } |
| if (t.hasClipDepth()) |
| { |
| t.clipDepth = r.readUI16(); |
| } |
| if (type == stagPlaceObject3) |
| { |
| if (t.hasFilterList()) |
| { |
| t.filters = decodeFilterList(); |
| } |
| if (t.hasBlendMode()) |
| { |
| t.blendMode = r.readUI8(); |
| } |
| } |
| if (t.hasClipAction()) |
| { |
| ActionDecoder actionDecoder = new ActionDecoder(r,swd); |
| actionDecoder.setKeepOffsets(keepOffsets); |
| t.clipActions = actionDecoder.decodeClipActions(length - (r.getOffset() - pos)); |
| } |
| return t; |
| } |
| |
| private List<Filter> decodeFilterList() throws IOException |
| { |
| LinkedList<Filter> filters = new LinkedList<Filter>(); |
| int count = r.readUI8(); |
| for (int i = 0; i < count; ++i) |
| { |
| int filterID = r.readUI8(); |
| switch( filterID ) |
| { |
| // NOTE: the filter decoding is pretty much just "save enough bits to regenerate", and ignores |
| // the real formatting of the filters (i.e. fixed 8.8 types, etc.) If you need the actual |
| // values rather than just acting as a passthrough, you will need to enhance the types. |
| |
| case DropShadowFilter.ID: filters.add( decodeDropShadowFilter() ); break; |
| case BlurFilter.ID: filters.add( decodeBlurFilter() ); break; |
| case GlowFilter.ID: filters.add( decodeGlowFilter() ); break; |
| case BevelFilter.ID: filters.add( decodeBevelFilter() ); break; |
| case GradientGlowFilter.ID: filters.add( decodeGradientGlowFilter() ); break; |
| case ConvolutionFilter.ID: filters.add( decodeConvolutionFilter() ); break; |
| case ColorMatrixFilter.ID: filters.add( decodeColorMatrixFilter() ); break; |
| case GradientBevelFilter.ID: filters.add( decodeGradientBevelFilter() ); break; |
| } |
| } |
| return filters; |
| } |
| |
| private DropShadowFilter decodeDropShadowFilter() throws IOException |
| { |
| DropShadowFilter f = new DropShadowFilter(); |
| f.color = decodeRGBA( r ); |
| f.blurX = r.readSI32(); |
| f.blurY = r.readSI32(); |
| f.angle = r.readSI32(); |
| f.distance = r.readSI32(); |
| f.strength = r.readUI16(); // really fixed8 |
| f.flags = r.readUI8(); |
| return f; |
| } |
| private BlurFilter decodeBlurFilter() throws IOException |
| { |
| BlurFilter f = new BlurFilter(); |
| f.blurX = r.readSI32(); // FIXED |
| f.blurY = r.readSI32(); |
| f.passes = r.readUI8(); |
| return f; |
| } |
| |
| private GlowFilter decodeGlowFilter() throws IOException |
| { |
| GlowFilter f = new GlowFilter(); |
| f.color = decodeRGBA( r ); |
| f.blurX = r.readSI32(); |
| f.blurY = r.readSI32(); |
| f.strength = r.readUI16(); // fixed 8.8 |
| f.flags = r.readUI8(); // bunch of fields |
| return f; |
| } |
| private BevelFilter decodeBevelFilter() throws IOException |
| { |
| BevelFilter f = new BevelFilter(); |
| f.highlightColor = decodeRGBA(r); |
| f.shadowColor = decodeRGBA(r); |
| f.blurX = r.readSI32(); |
| f.blurY = r.readSI32(); |
| f.angle = r.readSI32(); |
| f.distance = r.readSI32(); |
| f.strength = r.readUI16(); // fixed 8.8 |
| f.flags = r.readUI8(); // bunch of fields |
| return f; |
| } |
| private GradientGlowFilter decodeGradientGlowFilter() throws IOException |
| { |
| GradientGlowFilter f = new GradientGlowFilter(); |
| f.numcolors = r.readUI8(); |
| f.gradientColors = new int[f.numcolors]; |
| for (int i = 0; i < f.numcolors; ++i) |
| f.gradientColors[i] = decodeRGBA( r ); |
| f.gradientRatio = new int[f.numcolors]; |
| for (int i = 0; i < f.numcolors; ++i) |
| f.gradientRatio[i] = r.readUI8(); |
| // f.color = decodeRGBA( r ); |
| f.blurX = r.readSI32(); |
| f.blurY = r.readSI32(); |
| f.angle = r.readSI32(); |
| f.distance = r.readSI32(); |
| f.strength = r.readUI16(); // fixed 8.8 |
| f.flags = r.readUI8(); // bunch of fields |
| |
| return f; |
| } |
| private ConvolutionFilter decodeConvolutionFilter() throws IOException |
| { |
| ConvolutionFilter f = new ConvolutionFilter(); |
| f.matrixX = r.readUI8(); |
| f.matrixY = r.readUI8(); |
| f.divisor = r.readFloat(); |
| f.bias = r.readFloat(); |
| f.matrix = new float[f.matrixX*f.matrixY]; |
| for (int i = 0; i <f.matrixX*f.matrixY; ++i) |
| f.matrix[i] = r.readFloat(); |
| f.color = decodeRGBA( r ); |
| f.flags = r.readUI8(); |
| return f; |
| } |
| |
| private ColorMatrixFilter decodeColorMatrixFilter() throws IOException |
| { |
| ColorMatrixFilter f = new ColorMatrixFilter(); |
| |
| for (int i = 0; i < 20; ++i) |
| { |
| f.values[i] = r.readFloat(); |
| } |
| return f; |
| } |
| |
| private GradientBevelFilter decodeGradientBevelFilter() throws IOException |
| { |
| GradientBevelFilter f = new GradientBevelFilter(); |
| f.numcolors = r.readUI8(); |
| f.gradientColors = new int[f.numcolors]; |
| for (int i = 0; i < f.numcolors; ++i) |
| f.gradientColors[i] = decodeRGBA( r ); |
| f.gradientRatio = new int[f.numcolors]; |
| for (int i = 0; i < f.numcolors; ++i) |
| f.gradientRatio[i] = r.readUI8(); |
| // f.shadowColor = decodeRGBA( r ); |
| // f.highlightColor = decodeRGBA( r ); |
| f.blurX = r.readSI32(); |
| f.blurY = r.readSI32(); |
| f.angle = r.readSI32(); |
| f.distance = r.readSI32(); |
| f.strength = r.readUI16(); |
| f.flags = r.readUI8(); |
| |
| return f; |
| |
| } |
| |
| |
| private Tag decodePlaceObject(int length) throws IOException |
| { |
| PlaceObject t = new PlaceObject(stagPlaceObject); |
| int pos = r.getOffset(); |
| int idref = r.readUI16(); |
| t.depth = r.readUI16(); |
| t.setMatrix(decodeMatrix()); |
| if (length - r.getOffset() + pos != 0) |
| { |
| t.setCxform(decodeCxform()); |
| } |
| t.setRef(dict.getTag(idref)); |
| return t; |
| } |
| |
| // private Tag decodePlaceObject2(int length) throws IOException |
| // { |
| // PlaceObject t; |
| // t = new PlaceObject(stagPlaceObject2); |
| // r.syncBits(); |
| |
| // int pos = r.getOffset(); |
| |
| // t.flags = r.readUI8(); |
| // t.depth = r.readUI16(); |
| |
| // if (t.hasCharID()) |
| // { |
| // int idref = r.readUI16(); |
| // t.ref = dict.getTag(idref); |
| // } |
| // if (t.hasMatrix()) |
| // { |
| // t.matrix = decodeMatrix(); |
| // } |
| // if (t.hasCxform()) |
| // { |
| // // ed 5/22/03 the SWF 6 file format spec says this will be a CXFORM, but |
| // // the spec is wrong. the player expects a CXFORMA. |
| // t.colorTransform = decodeCxforma(); |
| // } |
| // if (t.hasRatio()) |
| // { |
| // t.ratio = r.readUI16(); |
| // } |
| // if (t.hasName()) |
| // { |
| // t.name = r.readString(); |
| // } |
| // if (t.hasClipDepth()) |
| // { |
| // t.clipDepth = r.readUI16(); |
| // } |
| // if (t.hasClipAction()) |
| // { |
| // ActionDecoder actionDecoder = new ActionDecoder(r,swd); |
| // actionDecoder.setKeepOffsets(keepOffsets); |
| // t.clipActions = actionDecoder.decodeClipActions(length - (r.getOffset() - pos)); |
| // } |
| // return t; |
| // } |
| |
| |
| private CXFormWithAlpha decodeCxforma() throws IOException |
| { |
| CXFormWithAlpha c = new CXFormWithAlpha(); |
| r.syncBits(); |
| c.hasAdd = r.readBit(); |
| c.hasMult = r.readBit(); |
| int nbits = r.readUBits(4); |
| if (c.hasMult) |
| { |
| c.redMultTerm = r.readSBits(nbits); |
| c.greenMultTerm = r.readSBits(nbits); |
| c.blueMultTerm = r.readSBits(nbits); |
| c.alphaMultTerm = r.readSBits(nbits); |
| } |
| if (c.hasAdd) |
| { |
| c.redAddTerm = r.readSBits(nbits); |
| c.greenAddTerm = r.readSBits(nbits); |
| c.blueAddTerm = r.readSBits(nbits); |
| c.alphaAddTerm = r.readSBits(nbits); |
| } |
| return c; |
| } |
| |
| private Tag decodeProtect(int length) throws IOException |
| { |
| GenericTag t; |
| t = new GenericTag(stagProtect); |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| return t; |
| } |
| |
| private Tag decodeDefineButtonCxform() throws IOException |
| { |
| DefineButtonCxform t; |
| t = new DefineButtonCxform(); |
| int idref = r.readUI16(); |
| t.button = (DefineButton) dict.getTag(idref); |
| if (t.button.cxform != null) |
| { |
| handler.error("button " + dict.getId(t.button) + " cxform redefined"); |
| } |
| t.button.cxform = t; |
| t.colorTransform = decodeCxform(); |
| return t; |
| } |
| |
| private Tag decodeDefineJPEG2(int length) throws IOException |
| { |
| DefineBits t = new DefineBits(stagDefineBitsJPEG2); |
| int pos = r.getOffset(); |
| int id = r.readUI16(); |
| length -= r.getOffset() - pos; |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private Tag decodeDefineBitsLossless(int length) throws IOException |
| { |
| DefineBitsLossless t = new DefineBitsLossless(stagDefineBitsLossless); |
| SwfDecoder r1 = r; |
| |
| int pos = r1.getOffset(); |
| |
| int id = r1.readUI16(); |
| t.format = r1.readUI8(); |
| t.width = r1.readUI16(); |
| t.height = r1.readUI16(); |
| |
| byte[] data; |
| |
| switch (t.format) |
| { |
| case 3: |
| int tableSize = r1.readUI8() + 1; |
| length -= r1.getOffset() - pos; |
| data = new byte[length]; |
| r1.readFully(data); |
| r1 = new SwfDecoder(new InflaterInputStream(new ByteArrayInputStream(data)), getSwfVersion()); |
| decodeColorMapData(r1, t, tableSize); |
| break; |
| case 4: |
| case 5: |
| length -= r1.getOffset() - pos; |
| data = new byte[length]; |
| r1.readFully(data); |
| r1 = new SwfDecoder(new InflaterInputStream(new ByteArrayInputStream(data)), getSwfVersion()); |
| t.data = new byte[t.width * t.height * 4]; |
| r1.readFully(t.data); |
| break; |
| default: |
| throw new SwfFormatException("Illegal bitmap format " + t.format); |
| } |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private void decodeColorMapData(SwfDecoder r1, DefineBitsLossless tag, int tableSize) throws IOException |
| { |
| tag.colorData = new int[tableSize]; |
| |
| for (int i = 0; i < tableSize; i++) |
| { |
| tag.colorData[i] = decodeRGB(r1); |
| } |
| |
| int width = tag.width; |
| int height = tag.height; |
| |
| if (width % 4 != 0) |
| { |
| width = (width / 4 + 1) * 4; |
| } |
| |
| tag.data = new byte[width * height]; |
| |
| r1.readFully(tag.data); |
| } |
| |
| private Tag decodeSoundStreamBlock(int length) throws IOException |
| { |
| GenericTag t = new GenericTag(stagSoundStreamBlock); |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| return t; |
| } |
| |
| private Tag decodeSoundStreamHead(int code) throws IOException |
| { |
| SoundStreamHead t; |
| t = new SoundStreamHead(code); |
| r.syncBits(); |
| |
| // mixFormat |
| r.readUBits(4); // reserved |
| t.playbackRate = r.readUBits(2); |
| t.playbackSize = r.readUBits(1); |
| t.playbackType = r.readUBits(1); |
| |
| // format |
| t.compression = r.readUBits(4); |
| t.streamRate = r.readUBits(2); |
| t.streamSize = r.readUBits(1); |
| t.streamType = r.readUBits(1); |
| |
| t.streamSampleCount = r.readUI16(); |
| |
| if (t.compression == SoundStreamHead.sndCompressMP3) |
| { |
| t.latencySeek = r.readSI16(); |
| } |
| return t; |
| } |
| |
| private Tag decodeDefineButtonSound() throws IOException |
| { |
| DefineButtonSound t; |
| t = new DefineButtonSound(); |
| int idref = r.readUI16(); |
| t.button = (DefineButton) dict.getTag(idref); |
| if (t.button.sounds != null) |
| { |
| handler.error("button " + idref + " sound redefined"); |
| } |
| t.button.sounds = t; |
| |
| idref = r.readUI16(); |
| if (idref != 0) |
| { |
| t.sound0 = dict.getTag(idref); |
| t.info0 = decodeSoundInfo(); |
| } |
| idref = r.readUI16(); |
| if (idref != 0) |
| { |
| t.sound1 = dict.getTag(idref); |
| t.info1 = decodeSoundInfo(); |
| } |
| idref = r.readUI16(); |
| if (idref != 0) |
| { |
| t.sound2 = dict.getTag(idref); |
| t.info2 = decodeSoundInfo(); |
| } |
| idref = r.readUI16(); |
| if (idref != 0) |
| { |
| t.sound3 = dict.getTag(idref); |
| t.info3 = decodeSoundInfo(); |
| } |
| return t; |
| } |
| |
| private Tag decodeStartSound() throws IOException |
| { |
| StartSound t; |
| t = new StartSound(); |
| int idref = r.readUI16(); |
| t.sound = (DefineSound) dict.getTag(idref); |
| t.soundInfo = decodeSoundInfo(); |
| return t; |
| } |
| |
| private SoundInfo decodeSoundInfo() throws IOException |
| { |
| SoundInfo i = new SoundInfo(); |
| |
| r.syncBits(); |
| |
| r.readUBits(2); // reserved |
| i.syncStop = r.readBit(); |
| i.syncNoMultiple = r.readBit(); |
| |
| boolean hasEnvelope = r.readBit(); |
| boolean hasLoops = r.readBit(); |
| boolean hasOutPoint = r.readBit(); |
| boolean hasInPoint = r.readBit(); |
| |
| if (hasInPoint) |
| { |
| i.inPoint = r.readUI32(); |
| } |
| if (hasOutPoint) |
| { |
| i.outPoint = r.readUI32(); |
| } |
| if (hasLoops) |
| { |
| i.loopCount = r.readUI16(); |
| } |
| if (hasEnvelope) |
| { |
| int points = r.readUI8(); |
| i.records = new long[points]; |
| for (int k = 0; k < points; k++) |
| { |
| i.records[k] = r.read64(); |
| } |
| } |
| |
| return i; |
| } |
| |
| private Tag decodeDefineSound(int length) throws IOException |
| { |
| DefineSound t; |
| t = new DefineSound(); |
| int pos = r.getOffset(); |
| |
| int id = r.readUI16(); |
| |
| r.syncBits(); |
| |
| t.format = r.readUBits(4); |
| t.rate = r.readUBits(2); |
| t.size = r.readUBits(1); |
| t.type = r.readUBits(1); |
| t.sampleCount = r.readUI32(); |
| |
| length -= r.getOffset() - pos; |
| |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private Tag decodeDefineFontInfo(int code, int length) throws IOException |
| { |
| DefineFontInfo t; |
| t = new DefineFontInfo(code); |
| int pos = r.getOffset(); |
| |
| int idref = r.readUI16(); |
| t.font = (DefineFont1) dict.getTag(idref); |
| if (t.font.fontInfo != null) |
| { |
| handler.error("font " + idref + " info redefined"); |
| } |
| t.font.fontInfo = t; |
| t.name = r.readLengthString(); |
| |
| r.syncBits(); |
| |
| r.readUBits(3); // reserved |
| t.shiftJIS = r.readBit(); |
| t.ansi = r.readBit(); |
| t.italic = r.readBit(); |
| t.bold = r.readBit(); |
| t.wideCodes = r.readBit(); |
| |
| if (code == stagDefineFontInfo2) |
| { |
| if (!t.wideCodes) |
| handler.error("widecodes must be true in DefineFontInfo2"); |
| if (getSwfVersion() < 6) |
| handler.error("DefineFont2 not valid before SWF6"); |
| t.langCode = r.readUI8(); |
| } |
| |
| length -= r.getOffset() - pos; |
| |
| if (t.wideCodes) |
| { |
| length = length / 2; |
| t.codeTable = new char[length]; |
| for (int i = 0; i < length; i++) |
| { |
| t.codeTable[i] = (char)r.readUI16(); |
| } |
| } |
| else |
| { |
| t.codeTable = new char[length]; |
| for (int i = 0; i < length; i++) |
| { |
| t.codeTable[i] = (char)r.readUI8(); |
| } |
| } |
| return t; |
| } |
| |
| private Tag decodeDoAction(int length) throws IOException |
| { |
| DoAction t = new DoAction(); |
| ActionDecoder actionDecoder = new ActionDecoder(r,swd); |
| actionDecoder.setKeepOffsets(keepOffsets); |
| t.actionList = actionDecoder.decode(length); |
| return t; |
| } |
| |
| private Tag decodeDefineText(int type) throws IOException |
| { |
| DefineText t = new DefineText(type); |
| |
| int id = r.readUI16(); |
| t.bounds = decodeRect(); |
| t.matrix = decodeMatrix(); |
| |
| int glyphBits = r.readUI8(); |
| int advanceBits = r.readUI8(); |
| // todo range check - glyphBits and advanceBits must be <= 32 |
| ArrayList<TextRecord> list = new ArrayList<TextRecord>(2); |
| |
| int code; |
| while ((code = r.readUI8()) != 0) |
| { |
| list.add(decodeTextRecord(type, code, glyphBits, advanceBits)); |
| } |
| |
| t.records = list; |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private GlyphEntry[] decodeGlyphEntries(int glyphBits, int advanceBits, int count) throws IOException |
| { |
| GlyphEntry[] e = new GlyphEntry[count]; |
| |
| r.syncBits(); |
| for (int i = 0; i < count; i++) |
| { |
| GlyphEntry ge = new GlyphEntry(); |
| |
| ge.setIndex( r.readUBits(glyphBits) ); |
| ge.advance = r.readSBits(advanceBits); |
| |
| e[i] = ge; |
| } |
| |
| return e; |
| } |
| |
| private TextRecord decodeTextRecord(int defineText, int flags, int glyphBits, int advanceBits) throws IOException |
| { |
| TextRecord t = new TextRecord(); |
| |
| t.flags = flags; |
| |
| if (t.hasFont()) |
| { |
| int idref = r.readUI16(); |
| t.font = (DefineFont) dict.getTag(idref); |
| } |
| |
| if (t.hasColor()) |
| { |
| switch (defineText) |
| { |
| case stagDefineText: |
| t.color = decodeRGB(r); |
| break; |
| case stagDefineText2: |
| t.color = decodeRGBA(r); |
| break; |
| default: |
| assert false; |
| } |
| } |
| |
| if (t.hasX()) |
| { |
| t.xOffset = r.readSI16(); |
| } |
| |
| if (t.hasY()) |
| { |
| t.yOffset = r.readSI16(); |
| } |
| |
| if (t.hasHeight()) |
| { |
| t.height = r.readUI16(); |
| } |
| |
| int count = r.readUI8(); |
| t.entries = decodeGlyphEntries(glyphBits, advanceBits, count); |
| |
| return t; |
| } |
| |
| private Tag decodeDefineFont() throws IOException |
| { |
| DefineFont1 t; |
| t = new DefineFont1(); |
| int id = r.readUI16(); |
| int offset = r.readUI16(); |
| int numGlyphs = offset/2; |
| |
| t.glyphShapeTable = new Shape[numGlyphs]; |
| |
| // skip the offset table |
| for (int i = 1; i < numGlyphs; i++) |
| { |
| r.readUI16(); |
| } |
| |
| for (int i = 0; i < numGlyphs; i++) |
| { |
| t.glyphShapeTable[i] = decodeShape(stagDefineShape3); |
| } |
| |
| dict.add(id, t); |
| dict.addFontFace( t ); |
| return t; |
| } |
| |
| private Tag decodeSetBackgroundColor() throws IOException |
| { |
| SetBackgroundColor t; |
| t = new SetBackgroundColor( decodeRGB(r)); |
| return t; |
| } |
| |
| /** |
| * decode jpeg tables. only one per movie. second and subsequent |
| * occurences of this tag are ignored by the player. |
| * @param length |
| * @return |
| * @throws IOException |
| */ |
| private GenericTag decodeJPEGTables(int length) throws IOException |
| { |
| GenericTag t; |
| t = new GenericTag(stagJPEGTables); |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| return t; |
| } |
| |
| private Tag decodeDefineButton(int length) throws IOException |
| { |
| int startPos = r.getOffset(); |
| DefineButton t; |
| t = new DefineButton(stagDefineButton); |
| int id = r.readUI16(); |
| |
| ArrayList<ButtonRecord> list = new ArrayList<ButtonRecord>(); |
| ButtonRecord record; |
| do |
| { |
| record = decodeButtonRecord(t.code); |
| if (record != null) |
| { |
| list.add(record); |
| } |
| } |
| while (record != null); |
| |
| t.buttonRecords = new ButtonRecord[list.size()]; |
| list.toArray(t.buttonRecords); |
| |
| // old school button actions only handle one possible transition |
| int consumed = r.getOffset()-startPos; |
| t.condActions = new ButtonCondAction[1]; |
| t.condActions[0].overDownToOverUp = true; |
| ActionDecoder actionDecoder = new ActionDecoder(r,swd); |
| actionDecoder.setKeepOffsets(keepOffsets); |
| t.condActions[0].actionList = actionDecoder.decode(length-consumed); |
| t.trackAsMenu = false; |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private ButtonRecord decodeButtonRecord(int type) throws IOException |
| { |
| boolean hasFilterList = false, hasBlendMode = false; |
| ButtonRecord b = new ButtonRecord(); |
| |
| r.syncBits(); |
| |
| int reserved; |
| if (type == stagDefineButton2) |
| { |
| reserved = r.readUBits(2); |
| hasBlendMode = r.readBit(); |
| hasFilterList = r.readBit(); |
| } |
| else |
| { |
| reserved = r.readUBits(4); |
| } |
| b.hitTest = r.readBit(); |
| b.down = r.readBit(); |
| b.over = r.readBit(); |
| b.up = r.readBit(); |
| |
| if (reserved == 0 && !b.hitTest && !b.down && !b.over && !b.up) |
| { |
| return null; |
| } |
| |
| int idref = r.readUI16(); |
| b.characterRef = dict.getTag(idref); |
| b.placeDepth = r.readUI16(); |
| b.placeMatrix = decodeMatrix(); |
| |
| if (type == stagDefineButton2) |
| { |
| b.colorTransform = decodeCxforma(); |
| if (hasFilterList) |
| { |
| b.filters = decodeFilterList(); |
| } |
| if (hasBlendMode) |
| { |
| b.blendMode = r.readUI8(); |
| } |
| } |
| |
| return b; |
| } |
| |
| private Tag decodeDefineBinaryData(int length) throws IOException |
| { |
| DefineBinaryData t = new DefineBinaryData(); |
| int pos = r.getOffset(); |
| int id = r.readUI16(); |
| t.reserved = r.readSI32(); |
| length -= r.getOffset() - pos; |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| dict.add(id, t); |
| return t; |
| } |
| |
| private Tag decodeDefineBits(int length) throws IOException |
| { |
| DefineBits t; |
| t = new DefineBits(stagDefineBits); |
| int pos = r.getOffset(); |
| int id = r.readUI16(); |
| length -= r.getOffset() - pos; |
| t.data = new byte[length]; |
| r.readFully(t.data); |
| t.jpegTables = jpegTables; |
| dict.add(id, t); |
| return t; |
| } |
| |
| private Tag decodeRemoveObject(int code) throws IOException |
| { |
| RemoveObject t; |
| t = new RemoveObject(code); |
| if (code == stagRemoveObject) |
| { |
| int idref = r.readUI16(); |
| t.ref = dict.getTag(idref); |
| } |
| t.depth = r.readUI16(); |
| return t; |
| } |
| |
| private CXForm decodeCxform() throws IOException |
| { |
| CXForm c = new CXForm(); |
| r.syncBits(); |
| |
| c.hasAdd = r.readBit(); |
| c.hasMult = r.readBit(); |
| int nbits = r.readUBits(4); |
| if (c.hasMult) |
| { |
| c.redMultTerm = r.readSBits(nbits); |
| c.greenMultTerm = r.readSBits(nbits); |
| c.blueMultTerm = r.readSBits(nbits); |
| } |
| if (c.hasAdd) |
| { |
| c.redAddTerm = r.readSBits(nbits); |
| c.greenAddTerm = r.readSBits(nbits); |
| c.blueAddTerm = r.readSBits(nbits); |
| } |
| return c; |
| } |
| |
| private Tag decodeMetadata() throws IOException |
| { |
| Metadata t = new Metadata(); |
| t.xml = r.readString(); |
| return t; |
| } |
| |
| private Tag decodeDefineShape(int shape) throws IOException |
| { |
| DefineShape t = new DefineShape(shape); |
| |
| int id = r.readUI16(); |
| t.bounds = decodeRect(); |
| if (shape == stagDefineShape4) |
| { |
| t.edgeBounds = decodeRect(); |
| r.readUBits(5); |
| t.usesFillWindingRule = r.readBit(); |
| t.usesNonScalingStrokes = r.readBit(); |
| t.usesScalingStrokes = r.readBit(); |
| } |
| t.shapeWithStyle = decodeShapeWithStyle(shape); |
| |
| dict.add(id, t); |
| return t; |
| } |
| |
| private ShapeWithStyle decodeShapeWithStyle(int shape) throws IOException |
| { |
| ShapeWithStyle sw = new ShapeWithStyle(); |
| |
| r.syncBits(); |
| |
| sw.fillstyles = decodeFillstyles(shape); |
| sw.linestyles = decodeLinestyles(shape); |
| |
| Shape s = decodeShape(shape); |
| |
| sw.shapeRecords = s.shapeRecords; |
| |
| return sw; |
| } |
| |
| private ArrayList<LineStyle> decodeLinestyles(int shape) throws IOException |
| { |
| ArrayList<LineStyle> a = new ArrayList<LineStyle>(); |
| |
| int count = r.readUI8(); |
| if (count == 0xFF) |
| { |
| count = r.readUI16(); |
| } |
| |
| for (int i = 0; i < count; i++) |
| { |
| a.add(decodeLineStyle(shape)); |
| } |
| |
| return a; |
| } |
| |
| private LineStyle decodeLineStyle(int shape) throws IOException |
| { |
| LineStyle s = new LineStyle(); |
| s.width = r.readUI16(); |
| |
| if (shape == stagDefineShape4) |
| { |
| s.flags = r.readUI16(); |
| if (s.hasMiterJoint()) |
| s.miterLimit = r.readUI16(); // 8.8 fixedpoint |
| } |
| if ((shape == stagDefineShape4) && (s.hasFillStyle())) |
| { |
| s.fillStyle = decodeFillStyle(shape); |
| } |
| else if ((shape == stagDefineShape3) || (shape == stagDefineShape4)) |
| { |
| s.color = decodeRGBA(r); |
| } |
| else |
| { |
| s.color = decodeRGB(r); |
| } |
| |
| return s; |
| } |
| |
| private ArrayList<FillStyle> decodeFillstyles(int shape) throws IOException |
| { |
| ArrayList<FillStyle> a = new ArrayList<FillStyle>(); |
| |
| int count = r.readUI8(); |
| if (count == 0xFF) |
| { |
| count = r.readUI16(); |
| } |
| |
| for (int i = 0; i < count; i++) |
| { |
| a.add(decodeFillStyle(shape)); |
| } |
| |
| return a; |
| } |
| |
| private FillStyle decodeFillStyle(int shape) throws IOException |
| { |
| FillStyle s = new FillStyle(); |
| |
| s.type = r.readUI8(); |
| switch (s.type) |
| { |
| case FillStyle.FILL_SOLID: // 0x00 |
| if (shape == stagDefineShape3 || shape == stagDefineShape4) |
| s.color = decodeRGBA(r); |
| else if (shape == stagDefineShape2 || shape == stagDefineShape) |
| s.color = decodeRGB(r); |
| else |
| throw new SwfFormatException("bad shape code"); |
| break; |
| case FillStyle.FILL_GRADIENT: // 0x10 linear gradient fill |
| case FillStyle.FILL_RADIAL_GRADIENT: // 0x12 radial gradient fill |
| case FillStyle.FILL_FOCAL_RADIAL_GRADIENT: // 0x13 focal radial gradient fill |
| s.matrix = decodeMatrix(); |
| s.gradient = decodeGradient(shape, s.type); |
| break; |
| case FillStyle.FILL_BITS: // 0x40 tiled bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP): // 0x41 clipped bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_NOSMOOTH): // 0x42 tiled non-smoothed fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP | FillStyle.FILL_BITS_NOSMOOTH): // 0x43 clipped non-smoothed fill |
| int idref = r.readUI16(); |
| try |
| { |
| s.bitmap = dict.getTag(idref); |
| } |
| catch (IllegalArgumentException e) |
| { |
| s.bitmap = null; |
| handler.error(e.getMessage()); |
| } |
| s.matrix = decodeMatrix(); |
| break; |
| default: |
| throw new SwfFormatException("unrecognized fill style type: " + s.type); |
| } |
| |
| return s; |
| } |
| |
| private Gradient decodeGradient(int shape, int filltype) throws IOException |
| { |
| Gradient gradient = (filltype == FillStyle.FILL_FOCAL_RADIAL_GRADIENT)? new FocalGradient() : new Gradient(); |
| r.syncBits(); |
| gradient.spreadMode = r.readUBits( 2 ); |
| gradient.interpolationMode = r.readUBits( 2 ); |
| int count = r.readUBits( 4 ); |
| gradient.records = new GradRecord[count]; |
| |
| for (int i = 0; i < count; i++) |
| { |
| gradient.records[i] = decodeGradRecord(shape); |
| } |
| |
| if (filltype == FillStyle.FILL_FOCAL_RADIAL_GRADIENT) |
| { |
| ((FocalGradient)gradient).focalPoint = r.readFixed8(); |
| } |
| |
| return gradient; |
| } |
| |
| private GradRecord decodeGradRecord(int shape) throws IOException |
| { |
| GradRecord g = new GradRecord(); |
| g.ratio = r.readUI8(); |
| |
| switch (shape) |
| { |
| case stagDefineShape: |
| case stagDefineShape2: |
| g.color = decodeRGB(r); |
| break; |
| case stagDefineShape3: |
| case stagDefineShape4: |
| g.color = decodeRGBA(r); |
| break; |
| } |
| |
| return g; |
| } |
| |
| private Matrix decodeMatrix() throws IOException |
| { |
| Matrix m = new Matrix(); |
| |
| r.syncBits(); |
| m.hasScale = r.readBit(); |
| if (m.hasScale) |
| { |
| int nScaleBits = r.readUBits(5); |
| m.scaleX = r.readSBits(nScaleBits); |
| m.scaleY = r.readSBits(nScaleBits); |
| } |
| |
| m.hasRotate = r.readBit(); |
| if (m.hasRotate) |
| { |
| int nRotateBits = r.readUBits(5); |
| m.rotateSkew0 = r.readSBits(nRotateBits); |
| m.rotateSkew1 = r.readSBits(nRotateBits); |
| } |
| |
| int nTranslateBits = r.readUBits(5); |
| m.translateX = r.readSBits(nTranslateBits); |
| m.translateY = r.readSBits(nTranslateBits); |
| |
| return m; |
| } |
| |
| private int decodeRGBA(SwfDecoder r) throws IOException |
| { |
| int color = r.readUI8() << 16; // red |
| color |= r.readUI8() << 8; // green |
| color |= r.readUI8(); // blue |
| color |= r.readUI8() << 24; // alpha |
| |
| // resulting format is 0xAARRGGBB |
| return color; |
| } |
| |
| private int decodeRGB(SwfDecoder r) throws IOException |
| { |
| int color = r.readUI8() << 16; // red |
| color |= r.readUI8()<<8; // green |
| color |= r.readUI8(); // blue |
| |
| // resulting format is 0x00RRGGBB |
| return color; |
| } |
| |
| private Shape decodeGlyph(int shape, int count) throws IOException |
| { |
| Shape s1 = new Shape(); |
| |
| r.syncBits(); |
| |
| // SDK-18153 - Hack to work around third-party generated SWFs that |
| // do not include at least one shape record in glyph SHAPE. |
| if (count > 0) |
| { |
| // we use int[1] so we can pass numBits by reference |
| int[] numFillBits = new int[] { r.readUBits(4) }; |
| int[] numLineBits = new int[] { r.readUBits(4) }; |
| |
| if (count > 1) |
| { |
| s1.shapeRecords = decodeShapeRecords(shape, numFillBits, numLineBits); |
| } |
| } |
| |
| return s1; |
| } |
| |
| private Shape decodeShape(int shape) throws IOException |
| { |
| Shape s1 = new Shape(); |
| |
| r.syncBits(); |
| |
| // we use int[1] so we can pass numBits by reference |
| int[] numFillBits = new int[] { r.readUBits(4) }; |
| int[] numLineBits = new int[] { r.readUBits(4) }; |
| |
| s1.shapeRecords = decodeShapeRecords(shape, numFillBits, numLineBits); |
| |
| return s1; |
| } |
| |
| private List<ShapeRecord> decodeShapeRecords(int shape, int[] numFillBits, int[] numLineBits) throws IOException |
| { |
| ArrayList<ShapeRecord> list = new ArrayList<ShapeRecord>(); |
| boolean endShapeRecord = false; |
| do |
| { |
| if (r.readBit()) |
| { |
| // edge |
| if (r.readBit()) |
| { |
| // line |
| list.add(decodeStraightEdgeRecord()); |
| } |
| else |
| { |
| // curve |
| list.add(decodeCurvedEdgeRecord()); |
| } |
| } |
| else |
| { |
| // style change |
| boolean stateNewStyles = r.readBit(); |
| boolean stateLineStyle = r.readBit(); |
| boolean stateFillStyle1 = r.readBit(); |
| boolean stateFillStyle0 = r.readBit(); |
| boolean stateMoveTo = r.readBit(); |
| |
| if (stateNewStyles || stateLineStyle || stateFillStyle1 || |
| stateFillStyle0 || stateMoveTo) |
| { |
| StyleChangeRecord s = decodeStyleChangeRecord(stateNewStyles, stateLineStyle, |
| stateFillStyle1, stateFillStyle0, stateMoveTo, |
| shape, numFillBits, numLineBits); |
| |
| list.add(s); |
| } |
| else |
| { |
| endShapeRecord = true; |
| } |
| } |
| } |
| while (!endShapeRecord); |
| |
| return list; |
| } |
| |
| private CurvedEdgeRecord decodeCurvedEdgeRecord() throws IOException |
| { |
| CurvedEdgeRecord s = new CurvedEdgeRecord(); |
| int nbits = 2+r.readUBits(4); |
| s.controlDeltaX = r.readSBits(nbits); |
| s.controlDeltaY = r.readSBits(nbits); |
| s.anchorDeltaX = r.readSBits(nbits); |
| s.anchorDeltaY = r.readSBits(nbits); |
| return s; |
| } |
| |
| private StraightEdgeRecord decodeStraightEdgeRecord() throws IOException |
| { |
| int nbits = 2+r.readUBits(4); |
| if (r.readBit()) |
| { |
| // general line |
| int dx = r.readSBits(nbits); |
| int dy = r.readSBits(nbits); |
| return new StraightEdgeRecord(dx, dy); |
| } |
| else |
| { |
| if (r.readBit()) |
| { |
| // vertical |
| int dy = r.readSBits(nbits); |
| return new StraightEdgeRecord(0, dy); |
| } |
| else |
| { |
| // horizontal |
| int dx = r.readSBits(nbits); |
| return new StraightEdgeRecord(dx, 0); |
| } |
| } |
| } |
| |
| private StyleChangeRecord decodeStyleChangeRecord(boolean stateNewStyles, |
| boolean stateLineStyle, |
| boolean stateFillStyle1, |
| boolean stateFillStyle0, |
| boolean stateMoveTo, |
| int shape, |
| int[] numFillBits, |
| int[] numLineBits) throws IOException |
| { |
| StyleChangeRecord s = new StyleChangeRecord(); |
| |
| s.stateNewStyles = stateNewStyles; |
| s.stateLineStyle = stateLineStyle; |
| s.stateFillStyle1 = stateFillStyle1; |
| s.stateFillStyle0 = stateFillStyle0; |
| s.stateMoveTo = stateMoveTo; |
| |
| if (s.stateMoveTo) |
| { |
| int moveBits = r.readUBits(5); |
| s.moveDeltaX = r.readSBits(moveBits); |
| s.moveDeltaY = r.readSBits(moveBits); |
| } |
| |
| if (s.stateFillStyle0) |
| { |
| s.fillstyle0 = r.readUBits(numFillBits[0]); |
| } |
| |
| if (s.stateFillStyle1) |
| { |
| s.fillstyle1 = r.readUBits(numFillBits[0]); |
| } |
| |
| if (s.stateLineStyle) |
| { |
| s.linestyle = r.readUBits(numLineBits[0]); |
| } |
| |
| if (s.stateNewStyles) |
| { |
| s.fillstyles = decodeFillstyles(shape); |
| s.linestyles = decodeLinestyles(shape); |
| |
| r.syncBits(); |
| |
| numFillBits[0] = r.readUBits(4); |
| numLineBits[0] = r.readUBits(4); |
| } |
| return s; |
| } |
| |
| private Tag decodeDefineSprite(int endpos) throws IOException |
| { |
| DefineSprite t = new DefineSprite(); |
| t.header = header; |
| int id = r.readUI16(); |
| t.framecount = r.readUI16(); |
| decodeTags(t.tagList); |
| while (r.getOffset() < endpos) |
| { |
| // extra data at end of sprite. must be zero |
| int b = r.readUI8(); |
| if (b != 0) |
| { |
| throw new SwfFormatException("nonzero data past end of sprite"); |
| } |
| } |
| dict.add(id, t); |
| return t; |
| } |
| |
| public Tag decodeSerialNumber() throws IOException |
| { |
| int product = r.readSI32(); |
| int edition = r.readSI32(); |
| |
| byte[] version = new byte[2]; |
| r.read(version); |
| byte majorVersion = version[0]; |
| byte minorVersion = version[1]; |
| |
| long build = r.read64(); |
| long compileDate = r.read64(); |
| |
| return new ProductInfo(product, edition, majorVersion, minorVersion, build, compileDate); |
| } |
| |
| public Header decodeHeader() throws IOException, FatalParseException |
| { |
| Header header = new Header(); |
| byte[] sig = new byte[8]; |
| |
| new DataInputStream(swfIn).readFully(sig); |
| header.version = sig[3]; |
| header.length = sig[4]&0xFF | (sig[5]&0xFF)<<8 | (sig[6]&0xFF)<<16 | sig[7]<<24; |
| |
| if (sig[0] == 'C' && sig[1] == 'W' && sig[2] == 'S') |
| { |
| header.compressed = true; |
| r = new SwfDecoder(new InflaterInputStream(swfIn), header.version, 8); |
| } |
| else if (sig[0] == 'F' || sig[1] == 'W' || sig[2] == 'S') |
| { |
| r = new SwfDecoder(swfIn, header.version, 8); |
| } |
| else |
| { |
| handler.error("Invalid signature found. Not a SWF file"); |
| throw new FatalParseException(); |
| } |
| |
| header.size = decodeRect(); |
| header.rate = r.readUI8() << 8 | r.readUI8(); |
| header.framecount = r.readUI16(); |
| |
| return header; |
| } |
| |
| public Tag decodeFileAttributes() throws IOException |
| { |
| FileAttributes tag = new FileAttributes(); |
| r.syncBits(); |
| r.readUBits(1); //reserved |
| tag.useDirectBlit = r.readBit(); |
| tag.useGPU = r.readBit(); |
| tag.hasMetadata = r.readBit(); |
| tag.actionScript3 = r.readBit(); |
| tag.suppressCrossDomainCaching = r.readBit(); |
| tag.swfRelativeUrls = r.readBit(); |
| tag.useNetwork = r.readBit(); |
| r.readUBits(24); //reserved |
| return tag; |
| } |
| |
| public Tag decodeEnableTelemetry() throws IOException |
| { |
| EnableTelemetry tag = new EnableTelemetry(); |
| r.syncBits(); |
| r.readUBits(16); //reserved |
| tag.enabled = true; |
| return tag; |
| } |
| |
| public Tag decodeDefineFontAlignZones() throws IOException |
| { |
| DefineFontAlignZones zones = new DefineFontAlignZones(); |
| int fontID = r.readUI16(); |
| zones.font = (DefineFont3)dict.getTag(fontID); |
| zones.font.zones = zones; |
| zones.csmTableHint = r.readUBits(2); |
| r.readUBits(6); // reserved |
| zones.zoneTable = new ZoneRecord[zones.font.glyphShapeTable.length]; |
| for (int i = 0; i < zones.font.glyphShapeTable.length; i++) |
| { |
| ZoneRecord record = new ZoneRecord(); |
| zones.zoneTable[i] = record; |
| record.numZoneData = r.readUI8(); |
| record.zoneData = new long[record.numZoneData]; |
| for (int j = 0; j < record.numZoneData; j++) |
| { |
| record.zoneData[j] = r.readUI32(); |
| } |
| record.zoneMask = r.readUI8(); |
| } |
| return zones; |
| } |
| |
| public Tag decodeCSMTextSettings() throws IOException |
| { |
| CSMTextSettings tag = new CSMTextSettings(); |
| int textID = r.readUI16(); |
| if (textID != 0) |
| { |
| tag.textReference = dict.getTag(textID); |
| if (tag.textReference instanceof DefineText) |
| { |
| ((DefineText)tag.textReference).csmTextSettings = tag; |
| } |
| else if (tag.textReference instanceof DefineEditText) |
| { |
| ((DefineEditText)tag.textReference).csmTextSettings = tag; |
| } |
| else |
| { |
| handler.error("CSMTextSettings' textID must reference a valid DefineText or DefineEditText. References " + tag.textReference); |
| } |
| } |
| tag.styleFlagsUseSaffron = r.readUBits(2); |
| tag.gridFitType = r.readUBits(3); |
| r.readUBits(3); // reserved |
| // FIXME: thickness/sharpness should be read in as 32 bit IEEE Single Precision format in little Endian |
| tag.thickness = r.readUBits(32); |
| tag.sharpness = r.readUBits(32); |
| r.readUBits(8); // reserved |
| return tag; |
| } |
| |
| public Tag decodeDefineFontName() throws IOException |
| { |
| DefineFontName tag = new DefineFontName(); |
| int fontID = r.readUI16(); |
| tag.font = (DefineFont)dict.getTag(fontID); |
| tag.font.license = tag; |
| tag.fontName = r.readString(); |
| tag.copyright = r.readString(); |
| return tag; |
| } |
| |
| private Rect decodeRect() throws IOException |
| { |
| r.syncBits(); |
| |
| Rect rect = new Rect(); |
| |
| int nBits = r.readUBits(5); |
| rect.xMin = r.readSBits(nBits); |
| rect.xMax = r.readSBits(nBits); |
| rect.yMin = r.readSBits(nBits); |
| rect.yMax = r.readSBits(nBits); |
| |
| return rect; |
| } |
| } |