SWFバイナリ編集のススメ第一回
こんにちは。メディア開発部のよやと申します。バイナリ編集エンジニアです。
はじめに
GREE では携帯向けコンテンツに Flash Lite を利用していますが、Lite には様々な制限(*1)があり、SWF(スウィフ) の動的生成技術を活用しています。
ツール(ming, swfmill, swftools, etc...)を用いた SWF 生成の記事は世間に溢れてますので、SWFバイナリの生編集をテーマに記事を何回かに分けて進めようと思います。
ツールを使う場合でも何かしら問題に遭遇した際の一助になるかもしれません。
第一回の当記事は、SWF仕様書の読み方ガイドです。
SWF仕様
Adobe公式の SWF仕様書は一般公開されています。 英語です。
- http://www.adobe.com/devnet/swf/ (swf_file_format_spec_v10.pdf)
理解の助けになる日本語のサイトを列挙します。
- http://hkpr.info/flash/swf/
- http://www.be-interactive.org/works/20080126/swfassist.pdf
- http://developer.cybozu.co.jp/takesako/2007/05/swf_binary_golf.html
以下の SWFファイル(orz.swf)をサンプルとして、公式仕様書を読み進めます。
SWFヘッダ
仕様書の SWF Structure Summary - The SWF header で以下のように定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 |
SWF File Header Field Type Comment Signature UI8 Signature byte: “F” indicates uncompressed “C” indicates compressed (SWF 6 and later only) Signature UI8 Signature byte always “W” Signature UI8 Signature byte always “S” Version UI8 Single byte file version (for example, 0x06 for SWF 6) FileLength UI32 Length of entire file in bytes FrameSize RECT Frame size in twips FrameRate UI16 Frame delay in 8.8 fixed number of frames per second FrameCount UI16 Total number of frames in file |
hexdump コマンドで実際の SWFファイルの先頭を16進数で表示して仕様書と比較してみます。
1 2 |
% hexdump -C orz.swf | head -1 00000000 46 57 53 04 77 1b 00 00 70 00 09 60 00 00 96 00 |FWS.w...p..`....| |
初めの8bytes
- 先頭3bytesの FWS は Signature。一文字目が F なので圧縮なし。
- 4byte目は 0x04 で、SWF の version
- 5~8byte目の 77 1b 00 00 は FileLength。SWF は基本 LittleEndian なのでbyte列を逆方向に読んで、0x1b77 = 7031 これが SWFファイルのファイル長に相当します。
1 2 |
% ls -l orz.swf -rwxr--r-- 1 yoya yoya 7031 2010-08-04 16:41 orz.swf |
合ってますね。
といった具合に読み解けます。
RECT
9byte目以降は少々やっかいです。
ここで出てくる RECT(Rectangle Record)は仕様書の Basic Data Types で定義されます。
1 2 3 4 5 6 7 |
RECT Field Type Comment Nbits UB[5] Bits used for each subsequent field Xmin SB[Nbits] x minimum position for rectangle in twips Xmax SB[Nbits] x maximum position for rectangle in twips Ymin SB[Nbits] y minimum position for rectangle in twips Ymax SB[Nbits] y maximum position for rectangle in twips |
初めの5bitにフィールド長が入っていて、その長さのフィールドが4つ後ろに続きます。
具体的には、
1 2 |
% hexdump -C orz.swf | head -1 00000000 46 57 53 04 77 1b 00 00 70 00 09 60 00 00 96 00 |FWS.w...p..`....| |
この中の
1 |
70 00 09 60 00 00 96 00 |
のbyte列が対応します。bit で分けると以下のようになります。
1 2 3 4 |
7 0 0 0 0 9 6 0 0 0 0 0 9 6 0 0 01110000 00000000 00001001 01100000 00000000 00000000 10010110 00000000 <-5-><-- 14 bits --><-- 14 bits --><-- 14 bits --><-- 14 bits --> NBits Xmin Xmax Ymin Ymax |
初めの 5 bits を読むと 14 という値が入っているので、その後ろに 14bits ずつ Xmin, Xmax, Ymin, Ymax の4つの値が並ぶという訳です。
各々の値は次のようになります。
1 2 3 4 5 |
NBits = 01110 = 14 Xmin = 00000000000000 = 0 twips = 0 pixel Xmax = 01001011000000 = 4800 twips = 240 pixel Ymin = 00000000000000 = 0 twips = 0 pixel Ymax = 01001011000000 = 4800 twips = 240 pixel |
ここで出てくる twips は長さの単位で(本来は DPI 依存ですが)、SWF では logical pixel(100% 表示での pixel) の 1/20 の長さと定められています。つまり twips の値を 20 で割ると丁度 pixel 値になります。
ヘッダの残り
1 2 3 |
% hexdump -C orz.swf | head -2 00000000 46 57 53 04 77 1b 00 00 70 00 09 60 00 00 96 00 |FWS.w...p..`....| 00000010 00 0a 28 00 43 02 ff ff ff 7f 05 e4 08 00 00 01 |..(.C...........| |
丁度二行目(offset 0x10)から続いています。
- FrameRate の 00 0a は LittleEndian なのでひっくり返して 0a 00 。8.8 fixed number (8bit整数.8bit少数 の固定小数点)なので、10.00 [frames/秒] (*2)
- FrameCount の 28 00 も LittleEndian なので 0x0028 => 40 frames (=コマ数)
以上で、SWF ヘッダが一通り読めました。
- イメージ図
SWFタグ (short形式)
SWFヘッダの後ろに SWFタグのリストが続きます。(図は仕様書から抜粋)
仕様書の SWF Structure Summary - SWF file structure に以下の定義があります。
1 2 3 4 |
RECORDHEADER (short) Field Type Comment TagCodeAndLength UI16 Upper 10 bits: tag type Lower 6 bits: tag length |
SWFタグはSWFヘッダの後ろに続くので、先程の FrameCount の続きから読んでいきます。
1 2 3 |
% hexdump -C orz.swf | head -2 00000000 46 57 53 04 77 1b 00 00 70 00 09 60 00 00 96 00 |FWS.w...p..`....| 00000010 00 0a 28 00 43 02 ff ff ff 7f 05 e4 08 00 00 01 |..(.C...........| |
1 |
43 02 ff ff ff 7f 05 e4 08 00 00 01 |
タグの先頭2bytes にタグの種類とタグの長さが入っていますが、LittleEndian と bit packing の合わせ技なので、読むのに少しコツがいります。
まず。43 02 を逆方向に読んで 0243 として、これを bit に分解します。
1 2 3 4 |
0 2 4 3 00000010 01000011 <-10 bits-><-6 -> tag type length |
初めの10bits がタグの種類(type)で、続く 6bits が(TagCodeAndLengthを除いた)タグの長さです。
1 2 |
tag type = 0000001001 = 9 = SetBackgroundColor length = 000011 = 3 = (RGBの) 3 byte |
tag type の 9 と SetBackgroundColor の対応は仕様書(swf_file_format_spec_v10.pdf)をテキスト検索して確認してください。
以上から、SetBackgroundColor のタグは以下の byte列になる事が分かります。
1 |
43 02 ff ff ff |
背景が (R,G,B) = (0xff, 0xff, 0xff)= 白色となります。
- イメージ図
SWFタグ (long形式)
6 bits では111111 = 0x3f = 63 byte までしか表現できないので、この 6 bits に 111111 = 0x3f が入っている時は、long 形式が適用されます。
画像や動画等のマルチメディアコンテンツを含むタグはこの形式を利用します。
1 2 3 4 5 |
RECORDHEADER (long) Field Type Comment TagCodeAndLength UI16 Tag type and length of 0x3F Packed together as in short header Length SI32 Length of tag |
先程のタグ(SetBackgroundColor)の後ろに丁度、JPEG画像のタグが並んでいるので、それを例にします。
1 2 3 |
% hexdump -C orz.swf | head -2 00000000 46 57 53 04 77 1b 00 00 70 00 09 60 00 00 96 00 |FWS.w...p..`....| 00000010 00 0a 28 00 43 02 ff ff ff 7f 05 e4 08 00 00 01 |..(.C...........| |
先程の続きなので以下のbyte列が相当します。
1 |
7f 05 e4 08 00 00 |
まず TagCodeAndLength の 2 byte を読みだします。
1 2 3 4 5 |
7f 05 => 0x057f => 00000101 01111111 <---------><----> tag type length tag type = 10101 = 0x15 = 21 = DefineBitsJPEG2 length = 0x3f = long形式 |
Length が 0x3f なので、その後ろに続く 4bytes を本当の Length として読み出します。
1 |
e4 08 00 00 = 0x8e4 = 2276 byte |
- イメージ図
PHPで読みだし
以上で SWF のヘッダとタグの読み方を一通り説明しました。
参考までに今まで説明した処理を行う PHP プログラムを以下に付けます。
swfdump
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<?php $swfdata = file_get_contents($argv[1]); $reader = new BitReader(); $reader->input($swfdata); /* SWF Header */ echo 'Signature: '.$reader->getData(3).PHP_EOL; echo 'Version: '.$reader->getUI8().PHP_EOL; echo 'FileLength: '.$reader->getUI32LE().PHP_EOL; echo 'FrameSize: '.PHP_EOL; $NBits = $reader->getUIBits(5); echo "\tXmin: ".($reader->getUIBits($NBits) / 20).PHP_EOL; echo "\tXmax: ".($reader->getUIBits($NBits) / 20).PHP_EOL; echo "\tYmin: ".($reader->getUIBits($NBits) / 20).PHP_EOL; echo "\tYmax: ".($reader->getUIBits($NBits) / 20).PHP_EOL; $reader->byteAlign(); echo 'FrameRate: '.($reader->getUI16LE() / 0x100).PHP_EOL; echo 'FrameCount: '.$reader->getUI16LE().PHP_EOL; /* SWF Tags */ echo 'Tag:'.PHP_EOL; while (true) { $tagAndLength = $reader->getUI16LE(); $type = $tagAndLength >> 6; $length = $tagAndLength & 0x3f; if ($length == 0x3f) { // long format $length = $reader->getUI32LE(); } echo "\ttype: $type length: $length".PHP_EOL; $contents = $reader->getData($length); if ($type == 0) { // END Tag break; } } exit(0); |
BitReader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
class BitReader { var $_data; // input_data var $_byte_offset; var $_bit_offset; function input($data) { $this->_data = $data; $this->_byte_offset = 0; $this->_bit_offset = 0; } function byteAlign() { if ($this->_bit_offset > 0) { $this->_byte_offset ++; $this->_bit_offset = 0; } } function getData($length) { $this->byteAlign(); $data = substr($this->_data, $this->_byte_offset, $length); $data_len = strlen($data); $this->_byte_offset += $data_len; return $data; } function getUI8() { $this->byteAlign(); $value = ord($this->_data{$this->_byte_offset}); $this->_byte_offset += 1; return $value; } function getUI16LE() { $this->byteAlign(); $ret = unpack('v', substr($this->_data, $this->_byte_offset, 2)); $this->_byte_offset += 2; return $ret[1]; } function getUI32LE() { $this->byteAlign(); $ret = unpack('V', substr($this->_data, $this->_byte_offset, 4)); $this->_byte_offset += 4; return $ret[1]; } function getUIBit() { $value = ord($this->_data{$this->_byte_offset}); $value = 1 & ($value >> (7 - $this->_bit_offset)); $this->_bit_offset ++; if (8 <= $this->_bit_offset) { $this->_byte_offset++; $this->_bit_offset = 0; } return $value; } function getUIBits($width) { $value = 0; for ($i = 0 ; $i < $width ; $i++) { $value <<= 1; $value |= $this->getUIBit(); } return $value; } } |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
% php swfdump.php orz.swf Signature: FWS Version: 4 FileLength: 7031 FrameSize: Xmin: 0 Xmax: 240 Ymin: 0 Ymax: 240 FrameRate: 10 FrameCount: 40 Tag: type: 9 length: 3 type: 21 length: 2276 <略> type: 0 length: 0 |
次回予告
次回は SWFバイナリの編集まで話しを進める予定です。
それでは失礼いたします。
*1: Lite の制限については、こちらのURLが分かり易いです => http://d.hatena.ne.jp/hanageman/20080922
*2: 2 byte まとめて一つの値として読んだ上で 0x100 で割ると楽です。