/*Darron Schall (darron@darronschall.com)
July 7th, 2003
File: SWFHeaderInfo.cs
Note: Some ideas for this code were used from the flasm
source code. Seehttp://flasm.sourceforge.netfor more information on flasm.
Requires: This file requires "SharpZipLib", found athttp://www.icsharpcode.net/OpenSource/SharpZipLib/for
decompressing compressed .swf files with the ZLib algorithm.
Description:
See the summary for the SWFHeaderInfo class*/
using System;
using System.IO;
namespace FlashToImage
{
//SWFHeaderInfo is a class that exposes as properties the contents of///a .swf file header. The path to the .swf file is passed into///the constructor. The Status property is useful for///determining if the input was valid or not. It is either "OK" if///the swf header was successfully read, or an error message otherwise.//SWFHeaderInfo works for both compressed and uncompressed .swf files./// public class SWFHeaderInfo
{
private byte bitPos; //bitPos is our "position" in out bitBuffer "array", valid values are 0-8 (8 means get a new buffer) private byte bitBuffer; //this is a byte that we'll be treating like an array uint nBits; //the number of bit used to store the data for the RECT type storing//the movies xMin, xMax, yMin, and yMax values private ICSharpCode.SharpZipLib.Zip.Compression.Inflater zipInflator; //used to decompressed swf Stream swf; //either a FileStream or MemoryStream//private internal data values private byte mVersion;
private uint mLength;
private uint mxMin;
private uint mxMax;
private uint myMin;
private uint myMax;
private ushort mFrameRate;
private ushort mFrameCount;
private bool mCompressed;
private string mStatus;
//public read-only (because no "set" defined) properties public byte Version
{
get
{
return mVersion;
}
}
public uint Length
{
get
{
return mLength;
}
}
public uint xMin
{
get
{
return mxMin;
}
}
public uint xMax
{
get
{
return mxMax;
}
}
public uint yMin
{
get
{
return myMin;
}
}
public uint yMax
{
get
{
return myMax;
}
}
public ushort FrameRate
{
get
{
return mFrameRate;
}
}
public ushort FrameCount
{
get
{
return mFrameCount;
}
}
public bool Compressed
{
get
{
return mCompressed;
}
}
public string Status
{
get
{
return mStatus;
}
}
public SWFHeaderInfo(string path)
{
char first; //C if compressed, F otherwise//initialize mVersion = 0;
mLength = 0;
mxMin = 0;
mxMax = 0;
myMin = 0;
mxMax = 0;
mFrameRate = 0;
mFrameCount = 0;
mCompressed = false;
mStatus = "No input specified";
//attempt to open the swf file try
{
swf = new FileStream(path, FileMode.Open);
}
catch (Exception e)
{
mStatus = "Error opening swf file";
//We don't need to close the file here because there//was a problem opening it.//swf.Close(); return;
}
bitPos = 8; //set out bitPos to be "out of bounds" so the first call to getBits reads in//a new byte from the file (the first byte)//try to read the first byte of the file try
{
first = (char)getBits(swf, 8);
}
catch (Exception e)
{
mStatus = "Error reading first byte file";
//close the swf file on error - 08/04/03 swf.Close();
return;
}
if (first == 'C')
{
//compressed swf file mCompressed = true;
swf = decompressSwf(swf);
if (mStatus == "Error decompressing swf file")
{
//trouble decompressing.. bail out! - 08/04/03 swf.Close();
return;
}
//reset our "array" index so the first call to getBits reads a new byte bitPos = 8;
//attempt to get the first byte of the file try
{
first = (char)getBits(swf, 8);
}
catch (Exception e)
{
mStatus = "Error reading first byte file";
//close the swf file on error - 08/04/03 swf.Close();
return;
}
}
//wrapped everything in a try/catch block "just in case" for//a little better error handling try
{
//make sure the first 3 bytes are "FWS" if (first != 'F' || (char)getBits(swf, 8) != 'W' || (char)getBits(swf, 8) != 'S')
{
//not a swf file! mStatus = "File specified does not seem to be a valid .swf file";
//close the swf file on error - 08/04/03 swf.Close();
return;
}
//start collecting informatin mVersion = (byte)getBits(swf, 8);
mLength = getDoubleWord(swf);
nBits = getBits(swf, 5);
mxMin = getBits(swf, nBits);
mxMax = getBits(swf, nBits) / 20;
myMin = getBits(swf, nBits);
myMax = getBits(swf, nBits) / 20;
mFrameRate = (ushort)(swf.ReadByte() <
mFrameCount = (ushort)(getWord(swf));
mStatus = "OK";
}
catch (Exception e)
{
mStatus = "Error reading .swf header information";
return;
}
swf.Close();
}
//corrections for flash storing the byte values in little-endian format//ported from the flasm source code private uint getWord(Stream f)
{
return (uint)(f.ReadByte() | f.ReadByte() <
}
//corrections for flash storing the byte values in little-endian format//ported from the flasm source code private uint getDoubleWord(Stream f)
{
return getWord(f) | (getWord(f) <
}
//idea for this function borrowed from the flasm source code, but//the function code is my creation private uint getBits(Stream f, uint n)
{
uint returnbits = 0;
while (true)
{
//do we have more bits to read? if (n >= 1)
{
//yes, more bits to read.... do we have unread bits in our buffer? if (bitPos == 8)
{
//no more bits to read in our buffer, lets get a new//buffer from f and reset the bitPos bitPos = 0;
bitBuffer = (byte)f.ReadByte(); //read 8 bits at a time }
//if we're here, we have more bits to read and//we have unread bits in the buffer
returnbits <<= 1; //shift the returnbit value left 1 place
byte bitMask = (byte)(0x80 >> bitPos);
//determine if the next bit we add to return bits should//be a 1 or a 0, based on the value of applying//the bitMask to the bitBuffer. A quick example://bitBuffer = 01011010//bitmask = 00001000//~~~~~~~~~~~~~~~~~~~~ anding the bits together yeilds://00001000//and because the result is equal to the bitmask, we add//a 1 to the returnbits value.
returnbits |= (bitBuffer & bitMask) == bitMask ? (uint)1 : (uint)0;
n -= 1; //one less bit to read bitPos += 1; //advance our "array" index }
else
{
//no more bits to read, return what we read from f return returnbits;
}
}
}
private Stream decompressSwf(Stream swf)
{
//seek to after the 4th byte and read in 4 bytes to determine the//size that the buffer needs to be to uncompressed the swf file swf.Seek(4, SeekOrigin.Begin);
uint bufferSize = getDoubleWord(swf);
//the uncompressedSwf byte array will be used as a MemoryStream byte[] uncompressedSwf = new byte[bufferSize];
//only after the 8th bit is the ZLib compresseion applied, so just//copy the first 8 bits from the compressed swf to the uncompressed swf swf.Seek(0, SeekOrigin.Begin);
swf.Read(uncompressedSwf, 0, 8);
//set the first byte to be 'F' instead of 'C' uncompressedSwf[0] = (byte)0x46;
zipInflator = new ICSharpCode.SharpZipLib.Zip.Compression.Inflater();
//because the zipInflator takes a byte array, not a FileStream,//we need to declare a temporary byte array to use as the//input for inflation byte[] tmpSwf = new byte[bufferSize - 8];
//read the rest of the swf into our temporary byte array swf.Read(tmpSwf, 0, (int)(bufferSize - 8));
//close the swf on disk because we have enough information in memory now swf.Close();
zipInflator.SetInput(tmpSwf);
if (zipInflator.Inflate(uncompressedSwf, 8, (int)(bufferSize - 8)) == 0)
{
mStatus = "Error decompressing swf file";
}
//change our swf from a FileStream to a MemoryStream now, using the uncompressed swf data return new MemoryStream(uncompressedSwf);
}
/*[STAThread]
public static void Main()
{
SWFHeaderInfo s = new SWFHeaderInfo("C:\\Development\\SWFHeaderInfo\\test.swf");
Console.WriteLine(s.Status);
if (s.Status == "OK")
{
Console.WriteLine("Version: " + s.Version);
Console.WriteLine("Length: " + s.Length);
Console.WriteLine("xMin: " + s.xMin);
Console.WriteLine("xMax: " + s.xMax);
Console.WriteLine("yMin: " + s.xMin);
Console.WriteLine("yMax: " + s.yMax);
Console.WriteLine("FrameRate: " + s.FrameRate);
Console.WriteLine("FrameCount: " + s.FrameCount);
Console.WriteLine("Compressed: " + s.Compressed);
}
// keep the input on the screen
Console.Read();
}*/
}
}