C#でFlash Liteなswfをバイナリ編集して置換する
- 2013-01-10
Flash Liteに限定しませんが、そういうのをどうしてもしたい!というシチュエーションは少なからずごく一部であるようです。どーいうことかというと、ガラケーが積んでるFlash Lite、は、パラメータを受け取って、それをもとにどうこうする、というのが非常に弱い。ほぼほぼ出来ない。でも、違うメッセージを表示したい、画像を変えたい、などという需要があります。特に、ソーシャルゲームはまさにそうで。そこで各社がどういう手段を取っているかというと、.swfを開いてバイナリ編集して、直接、テキストだったり画像だったりを置換しています。
RubyやPHPには有名なライブラリがあって実例豊富だけれど、ドトネトにはない。というのが弱点の一つでした。ん?あれ?gloopsではどうしていたの?というとそこのところは内緒(辞めた人間なのであまり言えません)
で、SWF仕様読みながら自前で解析してやるしかないかなあ、画像置換ぐらいしかやらないからフルセットの再現はしなくていいので、手間でもないだろう、でも手間だなあ、嫌だなあ、と思ったら、ライブラリ、あったじゃないですか!それがSwf2XNA。to XNAということでFlashをXNAで使えるようにする(GreeのLWF、よりもスクリプトも再現するから高級版ですなー)、他にXAMLに書きだしたりとか色々できるよう。中々高機能で良さそう!
といっても、そもそも目的がテキスト/画像置換しかしないので高機能である必要はないのですが、高機能を実現するために、SWFの解析回りはバッチリ。
残念ながらドキュメントはXNA周り中心で(当たり前か)さっぱり、コンパイル済みバイナリも用意されてないでソースからのみ。と、使うには微妙にハードですが、一から仕様読み解いてぽちぽち作るよりも百億倍楽なので、喜んで使わせて頂きます。とはいえ、swfの仕様については、ある程度読んで頭に入れておいたほうがいいです、というかそうでないと、どう操作すればいいのか全くピンと来ないので。
SWFの詳しい話はSWFバイナリ編集のススメが親切丁寧で非常に詳しい、分かりやすい。ので、それと照らし合わせながら進めていきましょう。
SWFをSwfFormatで読み込む
Swf2XNAのソリューションを開くといっぱいあって何が何やら。しかもコンパイル通らないし(XNA周りが未インストールだから)。で、困るのですが、今回はXNA周りは不要でコアのSWF解析さえできればいいので、そのためのプロジェクトはSwfFormat。これはXNAなどなどを入れなくても単体でビルド通るので、ビルドしてDLLを作りましょう。
さて、ビルドが通ったら、まずはSWFバイナリ編集のススメ第一回に従って、orz.swfをサンプルとしていただいて、解析してみましょう。
// SwfReaderはちょっと高級なBinaryReader的なもの
// swfはbit単位での処理しなきゃならない部分があるのでBinaryReaderだけだと不便
var reader = new SwfReader(File.ReadAllBytes("orz.swf"));
// SwfCompilationUnitがSwfの構造を表す
// コンストラクタの時点で生成出来てる(XElement.Loadみたいなもの)
var swf = new SwfCompilationUnit(reader);
メインとなるクラスはSwfCompilationUnitです。これが全て。中身がどうなってるか、というと、Visual Studioで見るのが速いですね。いやほんと、皆IDE使うべきだと思いますよ、ほんと(最近ぺちぱーなので愚痴る、いや、私自身はPHPはPHPStormで書いてるのですが)
ばっちり解析済みのようです。Headerを見ても、問題なく作れてる。
Tagを置き換えてみる
SWFの中身の実態はTagです。↑で見ると、orz.swfにはTagが87個ありますね。どういうのが並んでいるのか、というと、これもVisual Studioで見るのが手っ取り早い。
ふむふむ。なんとなくわかるような感じ。フレームがあってオブジェクトがあって、みたいな。では、SWFバイナリ編集のススメ第二回に進んで、背景色を変更しましょう。背景色は↑で開いている、BackgroundColorTagを弄ります。具体的な作業手順、コードは以下のような感じ。
// 要素はTagの中に詰まってるので、それを探してパラメータを置きかえ
var tagIndex = swf.Tags.FindIndex(x => x.TagType == TagType.BackgroundColor);
var tag = (BackgroundColorTag)swf.Tags[tagIndex];
tag.Color.R = 255; // 背景色を真っ赤に
tag.Color.G = 0;
tag.Color.B = 0;
// Tagはstructなため、代入しないと反映されない
swf.Tags[tagIndex] = tag;
using (var ms = new MemoryStream())
{
// SwfWriterはMemoryStreamしか受け付けない(Lengthを最後に書き換えたりするから、その必要があるみたい)
var sw = new SwfWriter(ms);
swf.ToSwf(sw); // メモリストリームに書きだされた
// ファイルに置換後のSWFを出力
File.WriteAllBytes("replaced.swf", ms.ToArray());
}
Tagを置き換えて(classじゃなくてstructなので、実際にListに再代入しないと変更が反映されないことに注意)、あとはSwfCompilationUnitのToSwfを使ってMemoryStreamに吐き出してやれば、それだけでTagの置換は完了です。無事、背景色が真っ赤なswfが生成されました!すっごく簡単だわー。
画像置換
SWFバイナリ編集のススメ第三回 (JPEG)に従って、画像置換もやってみましょう。
var reader = new SwfReader(File.ReadAllBytes("orz.swf"));
var swf = new SwfCompilationUnit(reader);
// DefineBitsTagはCharacterIdを持つので、実際はそれを参照して置換するTagを探すと良い
var tagIndex = swf.Tags.FindIndex(x => x.TagType == TagType.DefineBitsJPEG2);
var tag = (DefineBitsTag)swf.Tags[tagIndex];
tag.JpegData = File.ReadAllBytes("ethnyan.jpg"); // jpegデータを直接置き換え
swf.Tags[tagIndex] = tag;
using (var ms = new MemoryStream())
{
// Tagの画像を置き換えたことでHeaderのFileLengthも変わらなければなりませんが
// ↑でTagに代入しただけでは、そこは変わっていないままです
// が、ToSwfの際に、Headerも再計算された値に置き換えてくれるので、手動で変える必要はなし
var sw = new SwfWriter(ms);
swf.ToSwf(sw);
// ファイルに置換後のSWFを出力
File.WriteAllBytes("replaced.swf", ms.ToArray());
}
やってることは当然ながら同じで、置き換えたいTagを探す、置き換える、ToSwfで吐き出す。それだけです。簡単~。
置き換えによってFileLengthが変わる、などといったことはSwfCompilationUnitが面倒を見てくれるので、考えなくても大丈夫です。素晴らしい。
まとめ
RubyやPHPにはライブラリあるけれど、ドトネトにはないというのが弱点の一つでした(多分)。これで解決しましたね!さあ、C#で是非とも参入しましょう。
置換にあたって元swfファイルって変わらないから、SwfCompilationUnitをキャッシュすれば、ファイルオープンや解析のコストがなくなり、バイナリ編集のコストが純粋なバイト書き出しだけに抑えられますね。KlabのFlamixerは、初回パース時に構造を変えてMessagePackでシリアライズしておくので、というけれど、それだって読み込みやデシリアライズのコストありますものね。ASP.NETならゼロシリアライゼーションコストでキャッシュ出来るから、それ以上に期待持てそうだし、実際、軽くテストして見た限りだと、相当速くて、かなりイケテルと思いますですね。
というわけで謎社ではC#でほげもげしたい人をそのうち募集しますので暫しお待ちを。