Flash (.swf) File Format - (Basics)
by bkenwright@xbdev.net
There's a lot of cool things you'll come to love and hate about the flash swf
format....I mean its data is stored using chunks or tokens...so as you read in
each token descriptor, if you don't know what it is, or havn't wrote that bit of
code yet, you can just skip over it :) So as you continue to add
additional functionality to your parser, well you can continue to add in the
parts that you need :)
But it does have a few downsides as well, it has a lot of different token
types...and I mean a lot...each new version they seem to add a dozen more
types...and some of the tokens are recursive!...so some tokens store more tokens
within themselves!....criky!...it can soon get whacky complicated!
But hey!..don't worry...I'll hold your hand through it....once you get past
most of the quirks, you'll be okay...I mean I found it okay, but its can soon
get very time consuming....might need to put the kettle on and make a strong
black coffee :D
To start the exploration of the swf file format, where going
to use a very simple swf file called simple.swf...and you can see a
screenshot of it on the right. Its not compressed, and it only has a
single frame, so it doesn't animate...I suppose we could make it even more
simple...but I suppose this will do for now. |
|
The demo code is compiled using visual studio 2k5, and I tend to use sprintf(..)
and other standard c/c++ librarys, because I'm writing a lot of the information
to a debug output file, and adding lots of debug information, so you have to add
in '_CRT_SECURE_NO_DEPRECATE' to the project define....as Microsoft doesn't like
us using them anymore!
What we'll do first!....is read in the first 4 bytes! Yup, nothing to
extreme, we'll read them in, and write them out to a text file...and what you'll
find is that the first 3 bytes will tell us what where dealing with. I
just read the 4th one in to show that its not a null terminated string ;)
Source - Download Source Code |
////////////////////////////////////////////////////////////////////////////////////////
// //
// File: main.cpp //
// Date: 20-12-06 (xmas) //
// Author: bkenwright@xbdev.net //
// URL: www.xbdev.net //
// //
////////////////////////////////////////////////////////////////////////////////////////
/*
Introduction to the flash swf file format
*/
////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h> // sprintf(..), fopen(..)
#include <stdarg.h> // So we can use ... (in dprintf)
////////////////////////////////////////////////////////////////////////////////////////
// //
// dprintf(..) //
// Debug function, so we can write various output information to a log file as we //
// read in new information - I usually prefair to write it to a txt file, so I can //
// later open it in notepad or something and take a careful look at it, instead //
// of dumping it to the command window. //
// //
////////////////////////////////////////////////////////////////////////////////////////
//Saving debug information to a log file
void dprintf(const char *fmt, ...)
{
va_list parms;
char buf[256];
// Try to print in the allocated space.
va_start(parms, fmt);
vsprintf (buf, fmt, parms);
va_end(parms);
// Write the information out to a txt file
FILE *fp = fopen("output.txt", "a+");
fprintf(fp, "%s", buf);
fclose(fp);
}// End dprintf(..)
////////////////////////////////////////////////////////////////////////////////////////
// //
// main(..) //
// Program entry point - its where we start and end //
// //
////////////////////////////////////////////////////////////////////////////////////////
void main()
{
dprintf("Opening File: simple.swf\n");
FILE * fp = fopen("simple.swf", "rb");
char buf[4];
fread ( buf, // void * buffer,
3 * sizeof(char), // size
1, // count,
fp ); // FILE * stream
dprintf("Sig: '%c' '%c' '%c' '%c'\n", buf[0], buf[1], buf[2], buf[3]);
fclose(fp);
dprintf("Closing File.\n Done\n");
}// End main(..)
/*
Opening File: simple.swf
Sig: 'F' 'W' 'S' '̧
Closing File.
Done
*/
|
Not to bad eh? Most of the code is just setup code, and things...so as
long as your familiar with standard c/c++ its not to bad. So what you'll
see from the output file is we have 'FWS' which means where dealing with a bog
standard uncompressed .swf file. If we'd have 'CWS' then it would be a
compressed flash .swf file, and we'd have to uncompress it using zlib, which
we'll go over later :)
So lets take a look at what the header file format looks like:
Field |
Type |
Comment |
Signature |
UI8[3] |
Signature byte 'F' 'W' 'S' or 'C' 'W' 'S' for compressed
with zlib |
Version |
UI8 |
Single byte file version |
File Length |
UI32 |
Length of entire file in bytes |
Frame Size |
RECT RECT format is:
UI5 - nBits,
nBits - xMin
nBits - xMax
nBits - yMin
nBits - yMax |
Frame size in TWIPS |
Frame Rate |
UI16 |
Frame delay in 8.8 fixed number of frames per second |
Frame Count |
UI16 |
Total number of frames in movie |
Now the first thing that just shouted out at me when I first seen that header
format was ' RECT'! You just want to go, "Whats that all about"...well as
I mentioned earlier, lots of the information in this file format is bitwise
packed...so a lot of times you have to read in a few bits, to determine how many
bits to read in next!...be clear I'm saying bits, not bytes!.
Seems clear though, we have a Signature, Version, FileLength, all seems nice
and clear to me, so I think we can create a nice tidy header structure and read
in all our header data :)
What your about to see might look like a lot! And if your shaky on bit
shifting, and all that boolean logic of ANDing and ORing data, well your in for
fun now :) As all where doing below is reading in the header file and
storing it in a structure. The biggest chunk is because of the RECT bounds
part....which is messy I think...I did it all in the main so you could see how
you do it....later on I create a ReadRect(..) function where you can just pass
the start pointer to the data, and it returns how many bytes to increment along
by to the next data you want.
Source - Download Source Code |
////////////////////////////////////////////////////////////////////////////////////////
// //
// File: main.cpp //
// Date: 20-12-06 (xmas) //
// Author: bkenwright@xbdev.net //
// URL: www.xbdev.net //
// //
////////////////////////////////////////////////////////////////////////////////////////
/*
Introduction to the flash swf file format
*/
////////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h> // sprintf(..), fopen(..)
#include <stdarg.h> // So we can use ... (in dprintf)
////////////////////////////////////////////////////////////////////////////////////////
// //
// SWF Header Container Structures //
// //
////////////////////////////////////////////////////////////////////////////////////////
struct stRect
{
int m_Nbits; // nBits = UB[5] Bits in each rect value field
int m_Xmin; // SB[nBits] X minimum position for rect
int m_Xmax; // SB[nBits] X maximum position for rect
int m_Ymin; // SB[nBits] Y minimum position for rect
int m_Ymax; // SB[nBits] Y maximum position for rect
};
struct stSWFHeader
{
char m_Sig[3]; // UI8[3] Signature byte 1 - 'FWS' or 'CWS'
unsigned int m_Version; // UI8 Single byte file version
unsigned int m_FileLength; // UI32 Length of entire file in bytes
stRect m_FrameSize; // RECT Frame size in TWIPS
unsigned short m_FrameRate; // UI16 Frame delay in 8.8 fixed number of fps
unsigned short m_FrameCount; // UI16 Total number of frames in movie
};
//Saving debug information to a log file
void dprintf(const char *fmt, ...)
{
.... (as above)
}// End dprintf(..)
////////////////////////////////////////////////////////////////////////////////////////
// //
// main(..) //
// Program entry point - its where we start and end //
// //
////////////////////////////////////////////////////////////////////////////////////////
void main()
{
dprintf("Reading in simple.swf\n");
// Open the file, and read in all its contents int a large buffer
FILE * fp = fopen("simple.swf", "rb");
// Large temporary buffer which we'll use to read in the file to
char fileData[20000];
int size = 0;
while (true)
{
int done = (int)fread(fileData + size, 1, 1, fp);
if (done)
{
size += 1;
}
else
{
break;
}
}// End while(..)
// Close file
fclose(fp);
// Pointer to the start of the file data, which we can increment as
// we go along and determine what each byte is for
char* data = fileData;
// At this point, we have read in the whole file, and its stored
// in our temporary buffer.
dprintf("Sig: '%c' '%c' '%c' \n", data[0], data[1], data[2]);
// Create a temp instance of our header structure, and read data
// into it
stSWFHeader head;
head.m_Sig[0] = data[0];
head.m_Sig[1] = data[1];
head.m_Sig[2] = data[2];
// Increment data offset by 3 bytes
data += 3;
head.m_Version = data[0];
dprintf("Version: %d\n", head.m_Version);
// Increment data offset by 1 byte
data += 1;
head.m_FileLength = ((unsigned int*)data)[0];
dprintf("FileLength: %d\n", head.m_FileLength);
// Increment data offset by 4 bytes (unsigned int is 4 bytes)
data += 4;
// Get a pointer to the rect from the header, easier to reference then
stRect* rect = &head.m_FrameSize;
// Need to get the first 5 bits!
int nBits = data[0] >> 3;
rect->m_Nbits = nBits;
dprintf("Rect: nBits: %d\n", rect->m_Nbits);
// Now this is where it gets tricky, as we have some of our data
// in this byte, and some in the next bytes..so we have
// to do lots of bit manipulation
// Temp variable which we will use to store the value in as we
// get each bit, as we go from byte to byte
int buf = 0;
// Current number of bits left in the byte where working with, as
// remember the first 5 bytes where use to hold the number of bits
// for the other size
int numBits = 3;
// Byte data, which we change as we go from byte to byte
unsigned char byte = data[0] & 0x7;
// Read 4 rect values into here, i.e. xMin, xMax etc
int val[4];
// Loop around the 4 values
for (int i=0; i<4; i++)
{
buf = 0;
for (int j=0; j<nBits; j++) // Go bit by bit along the byte
{
if (byte & 0x80)
{
buf = buf | 1; // We have a 1 at this bit
}
buf = buf << 1; // Defaults to 0, so we have 0 at this bit
byte = byte<<1;
numBits--;
if (numBits==0) // Reached the last bit, we get the next byte
{ // along
data++;
byte = data[0];
numBits=8; // Just got a new byte, so we now have 8
// bits to work with
}
}
buf = buf>>1; // Took me a while to track this down, but we need
// to go back one as we have found our value
val[i] = buf;
}
// Just to note, we always start on a fresh aligned byte after readin
// in a rect! So there could be padding/unused bits on the end :)
if (numBits>0)
{
data++;
}
// Store the values and write them out to our debug log
rect->m_Xmin = val[0];
rect->m_Xmax = val[1];
rect->m_Ymin = val[2];
rect->m_Ymax = val[3];
dprintf("Rect Xmin: %d\n", rect->m_Xmin);
dprintf("Rect Xmax: %d\n", rect->m_Xmax);
dprintf("Rect Ymin: %d\n", rect->m_Ymin);
dprintf("Rect Ymax: %d\n", rect->m_Ymax);
head.m_FrameRate = ((unsigned short*)data)[0];
dprintf("FrameRate: %d\n", head.m_FrameRate);
data+=2;
head.m_FrameCount = ((unsigned short*)data)[0];
dprintf("FrameCount: %d\n", head.m_FrameCount);
data+=2;
}// End main(..)
/*
Reading in simple.swf
Sig: 'F' 'W' 'S'
Version: 3
FileLength: 151
Rect: nBits: 15
Rect Xmin: 0
Rect Xmax: 12000
Rect Ymin: 0
Rect Ymax: 8000
FrameRate: 3072
FrameCount: 1
*/
|
Doesn't look like much? Well its pretty good I think...as after the
header, its just tags! Well some people call them chunks...basically, you
have 6 bytes which say what type and how long...and you just keep going like
that...and any chunks we arn't interested in, we can just get its type, and skip
over its contents :)
|