SWFバイナリ編集のススメ第八回 (Action - AS2 Bytecode編)

こんにちは。プラットフォーム開発のよやです。

今回は SWF 3 から導入された DoAction タグと、それに含まれる ActionScript2(*1) Bytecode イメージの編集について、お話します。
似たタグに DoInitAction がありますが、形式的には Sprite ID のフィールド追加以外は同じですので説明を省きます。尚、DoInitAction は SWF 6 以降のタグです。

編集の目的

Flash Lite1.1 の SWF を携帯端末で表示する場合、URL や HTML タグでパラメータを渡せない制約があるので、ActionScript のロジックにパラメータを渡すのに色々と工夫のしがいがあります。

例えば、

  • (a) DoAction 中の ActionScript2 Bytecode の先頭に代入イメージを付加する
  • (b) 〃 Bytecode 中の文字列を入れ替える
  • (c) 変数に bind した DefineEditText(*2) 中の文字列を書き換える

等、色々な方法があります。

このうち (a) と (b) の方法について必要な知識からその実装まで一通り解説します。

DoAction タグとは

DoAction は SWF 3 から導入されたタグで ActionScript2 の Bytecode(*3)を収納します。
以下のフォーマットです。

  • Actions(ACTIONRECORD [0 or more]) フィールドに ActionScript2 の Bytecode が入ります。
  • Bytecode の分解は簡単で、0x7F 以下だと ActionCode 単体で、0x80 以上の時は値が後ろに続きます。
  • 0x80 以上の時のバイナリ形式は TLC(Type,Length,Content)になっていて、バイナリ編集者に優しい(*4)形式です。

Bytecode 解析

  • Data Payload の中身を気にしなければ、Bytecode を分解するのに以下の処理で十分です。

もう少し頑張って ActionCode (の名前)や Data Payload の中身を解釈してみます。

  • まず、ActionCode 一覧をテーブルで定義。

  • ActionCode に応じて Data Payload の中身を解釈。

  • ActionCode に応じて、後ろに続く Payload Data の形式が決まります。
  • SWF の文字列(STRING)は、\0 終端のフォーマットです。長さフィールドを持たないので処理が少し面倒です。又、PHP の String 型は途中に \0 を含められるので、PHP の API を用意する場合は、途中の \0 を除去する必要があります。

(a) Bytecode の頭に代入イメージを付加する

変数代入に相当する Bytecode を先頭に付加すれば変数パラメータを初期化出来ます。この方法だと SWF 中の Bytecode を弄らずに済むので実現するのが楽です。

どんな Bytecode を付加するか

試しに変数代入のスクリプトを 入れて Adobe Flash でパブリッシュしてみます。

以下のような Bytecode ができます。 (IO_SWF 付属の swfdump.php の表示です)

先頭の方を手作業で解釈していくと、以下のように分解できます。

実装サンプル

これらの実験から、以下の Bytecode を先頭に付加する事で、任意の変数に任意の文字列を代入出来る事が分かります。

  • 渡したい変数と代入値の文字列を、えんじ色の「....」のフィールドに当て嵌めます。

Bytecode 生成は以下のように実装できます。

(b) Bytecode 中の文字列を入れ替える

(a) 方式のように先頭に代入イメージを挿入する方式では、未初期化の変数を決める必要があります。しかし、ひな型 SWF 自体に決め打ちで値が入っていれば、その SWF 単体で動作テストが出来るので、決め打ちの値を必要に応じて書き換える方が開発が楽になります。
そこで、Byecode 中の文字列を入れ替える方法について紹介します。

編集したい文字列が入る可能性のある ActionCode としては、GetURL, ConstantPool, そして (a) でも扱った Push 命令が考えられます。

GetURL (0x83)

まず、GetURL です。

  • 後ろに文字列が2つ並ぶだけなので簡単に入れ替えられます。

ConstantPool (0x88)

お次は ConstantPool。SWF5 以降のActionCode なので、Flash Lite 1.1 では見かけません。

  • Count フィールドの数だけ並びますが、単純に \0 終端の文字列が並ぶだけです。これの処理も簡単です。

Push (0x96)

最後に Push。この命令は複数の値を一度に(スタックに) Push 出来るので少し面倒です。
しかし、変数代入は Push 命令を使う為、これの処理は避けられません。

  • この value は文字列以外に数値や(ConstantPool への)参照値を扱えます。

  • 各々の値に Type フィールドがある為、Push する複数の値に文字列や数値等、複数の Type が混在する事に注意して下さい。

実装サンプル

  • 長くなるのでコードの先頭の方だけ引用します。
  • parse (バイナリの分解)

  • build (バイナリの構築)

parse した後、変数の中身を書き換え、build して SWF バイナリに戻せば、編集が出来る事になります。

IO_SWF での実装

  • 上記の (a) と (b) の処理を openpear の IO_SWF 上に実装しました。

    • install 方法

  • 既に install されていてバージョンが古い場合

  • 素材SWFを以下のように作成します。

(a) Bytecode の頭に代入イメージを付加する

(a) の方式では、orange の文字列を書き換える事が出来ます。carrot の方は ActionScript の代入命令が既にある為、先頭に代入命令のイメージを差し込んでも、その後上書きされてしまうのでダメです。

  • 変換後 SWF (actiontext-mikan.swf)

(b) Bytecode 中の文字列を入れ替える

(b) の方法では carrot の文字列を書き換える事が出来ます。orange の文字列は (DoAction でなく) DefineEditText のタグの中に文字列が含まれる為、この方法では変更できません。

  • 変換後 SWF (actiontext-tomato.swf)

簡単ですね!

まとめ

  • ActionCode の分解は簡単。0x80 以上の時に (Length フィールド付きで) Data Payload が続く。
  • Data Payload の中身は ActionCode 毎に違うので少し手間。
  • Push 命令は複数の値を一度に Push 出来る上に色んな型が混在するので面倒。
  • 文字列は null(\0) 終端 (それ用の Lengthフィールドを持たない)
  • PHP の文字列型は \0 を途中に挟めるので注意。(null 終端では無い)
  • 文字列を編集して長さが変わると Action Payload Data Length, Tag Length, SWF Header Length の3つの Length を更新する必要がある。
  • つまり、文字列の長さを変えずに replace するならば、間に \0 を含まなければ、他に書き換える必要なし。

参考 URL

最後に

次回は、DefineShape の続きか、DefineEditText か DefineSprite のいずれかを取り上げる予定です。
それでは。

*1: Flash Lite 1.x は ActionScript1 ですが Bytecode 形式は互換ですので、ActionScript2 として話します。

*2: 要望があれば DefineEditText の編集も記事にしようと思います

*3: コンパイルされた結果のバイナリ、VirtualMachine (もしくは CPU)が解釈できる命令列が並ぶ

*4: Type 毎の Content 詳細を知らずに chunk 分解出来るので