JavaScript でSWFを分解してみる

今年は JavaScript に真面目に取り組もうと、大晦日からプログラミングしてました。
JavaScript で SWF を分解するのは実質3度目ですが、今までを反省しつつ、かつ今までのやり方を全てご破算にして、一から考えて作ってます。

とりあえずブロックの分解は出来たので、以下にプログラムを晒します。(ChromeSafari で動きます。他は自信無し)

JavaScript のコンソールには以下のように表示されます。

(このスクリーンショットは、Chrome の右メニュー>「要素の検証」>「Console」タブを辿って表示させました)

あ。僕は JavaScript 初心者なので参考にし過ぎないで下さい。むしろ、こんなコードふざけんなって所を見つけたらお叱り頂けると幸いです。

index.html

  <canvas id="mycanvas" width="240" height="240"> canvas is here </canvas>
  <script type="text/javascript"">
   // var url = 'saitama.swf';
   var swf_url = 'ffxi2.swf';
   var canvas_id = 'mycanvas'
   var swfpl = new SWFEditor(swf_url, canvas_id);
  </script>

Canvasデバッグにでも使おうかなと。具体的な事は後で考えます。

editor.js

  • http://diary.awm.jp/~yoya/data/2012/01/01/swfed/editor.js
  • parser を loader に渡して起動します。(通信中は SWFLoader に主導権を渡す感じで)
  • 最後に main に戻ってくるので、ここで何かやりたい処理を埋める予定。今は分解したデータを dump してます。
var SWFEditor = function(url, canvas_id) {
    var parser = new SWFParser(this);
    var loader = new SWFLoader(url, parser);
    this.main = function(swfheader, swftags) {
	this.swfheeader = swfheader;
	this.swftags = swftags;
	console.debug("SWFEditor::run");
	console.debug(swfheader);
	for (var i = 0, n = swftags.length ; i < n ; i++) {
	    swftag = swftags[i];
	    console.debug('code:'+swftag.code+' length:'+swftag.length+' data:');
	    if (swftag.data) {
		console.debug(swftag.data);
	    }
	}

loader.js

	req.onreadystatechange = function(hoge) {
	    if (req.readyState > 1) {
		if (req.status == 200) {
		    if (req.readyState < 4) {
			this.parser.input(req.responseText); // read partial
		    } else {
			this.parser.input(req.responseText); // read completed
			this.parser.finish();
		    }
		} else {

parser.js

  • http://diary.awm.jp/~yoya/data/2012/01/01/swfed/parser.js
  • 渡されたデータを SWF のデータフォーマットに従って分解します。(途中までしか無い可能性があるので、二度目以降は続きを処理)。処理が終わったら editor の main に処理を渡します。
    this.input = function(data) {
	bitstream.input(data);
	if (bitstream.byte_offset < 8) {
	    this.parseHeader(bitstream);
	}
	this.parseTags(bitstream);
  <略>
    this.finish = function() {
	this.editor.main(this.swfheader, this.swftags);
    }
    this.parseHeader = function(bs) {
	//	console.debug('parseHeader');
	this.swfheader = new SWFHeader(bs);
    }

object.js

/* Header */
var SWFHeader = function(bs) {
    if (bs) {
	this.Signature  = bs.getData(3),
	this.Version    = bs.getUI8(),
	this.FileLength = bs.getUI32LE(),
	this.FrameSize  = new SWFRECT(bs),
	this.FrameRate  = bs.getUI16LE(),
	this.FrameCount = bs.getUI16LE()
    }
}

bitstream.js

var Bitstream = function() {
    this.data = null;
    this.byte_offset = 0;
    this.bit_offset = 0;
    this.input = function(data) {
	this.data = data;
    }
    this.byteAlign = function(n) {
	if (this.bit_offset) {
	    this.byte_offset += ((this.bit_offset+7)/8) | 0;
	    this.bit_offset = 0;
	}
    }
    this.getData = function(n) {
	this.byteAlign();
	bo = this.byte_offset;
	ret = this.data.substr(bo, n);
	this.byte_offset = bo + n;
	return ret;
    }
    this.getUI8 = function() {
	this.byteAlign();
	return this.data.charCodeAt(this.byte_offset++) & 0xff;
    }