swf2jsでswf内でzlib形式で圧縮されたデータを解凍する

# swfのバージョン6に対応したい。

手始めに圧縮されたswfをJSで解凍する。

# 課題

* 画像データは基本zlib圧縮されている
* バージョン6からはswf本体をzlib圧縮して出力ができる


## 完成品

[ienaga/swf2js](https://github.com/ienaga/swf2js)


## 参考にしたサイト

* http://www.onicos.com/staff/iz/release/zlib-js/zlib-js.html

* http://www.futomi.com/lecture/japanese/rfc1951.html


## 分解する時の注意点

* 先頭の2bytesはzlibヘッダなので無視する。
* swfの解凍時は2bytesがヘッダーで8bytesがswfのヘッダー情報。（ここで結構時間がかかったorz）


メタデータに関する符号
型付き配列(Typed Array)に対応している端末ならTyped Arrayを使用する。

~~~

var ORDER = 
    [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];

var LEXT = [
    0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
    3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99
];

 var LENS = [
    3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
    35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
 ];

var DEXT = [
    0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
    7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
];

var DISTS = [
    1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
    257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
    8193, 12289, 16385, 24577
];

if (isArrayBuffer) {
    ORDER = new Uint8Array(ORDER);
    CODE_EXT = new Uint8Array(CODE_EXT);
    CODE_LENGTH = new Uint16Array(CODE_LENGTH);
    DIST_EXT = new Uint8Array(DIST_EXT);
    DIST_CODE = new Uint16Array(DIST_CODE);
}

~~~


# BitIOを拡張

※yoyaさんの「bitio.js」を使わせていただきました。
[yoya/swfpl](https://github.com/yoya/swfpl)


~~~
/**
 * readUB
 * @param length
 * @returns {number}
 */
BitIO.prototype.readUB = function(length)
{
    var _this = this;
    var value = 0;
    for (var i = 0; i < length; i++) {
        if (_this.bit_offset == 8) {
            _this.bit_buffer = _this.readNumber(1);
            _this.bit_offset = 0;
        }
        value |= (_this.bit_buffer & (0x01 << _this.bit_offset++) ? 1 : 0) << i;
    }
    return value;
};

/**
 * readNumber
 * @returns {number}
 */
BitIO.prototype.readNumber = function(n)
{
    var _this = this;
    var value = 0;
    var o = _this.byte_offset;
    var i = o + n;
    for(; i > o; ){
        value = (value << 8) | _this.data[--i];
    }
    _this.byte_offset += n;
    return value;
};
~~~


# 解凍

~~~
    for (; !done; ) {
        var done = zBitio.readUB(1);
        var type = zBitio.readUB(2);

        var distTable = {};
        var litTable = {};
        var fixedDistTable = false;
        var fixedLitTable = false;

        if (type) {
            if (type == 1) {
                distTable = fixedDistTable;
                litTable = fixedLitTable;

                if (!distTable) {
                    bitLengths = [];
                    for(i = 32; i--;){
                        bitLengths[bitLengths.length] = 5;
                    }
                    distTable = fixedDistTable = _buildHuffTable(bitLengths);
                }

                if (!litTable) {
                    bitLengths = [];
                    i = 0;

                    for(; i < 144; i++){
                        bitLengths[bitLengths.length] = 8;
                    }

                    for(; i < 256; i++){
                        bitLengths[bitLengths.length] = 9;
                    }

                    for(; i < 280; i++){
                        bitLengths[bitLengths.length] = 7;
                    }

                    for(; i < 288; i++){
                        bitLengths[bitLengths.length] = 8;
                    }

                    litTable = fixedLitTable = _buildHuffTable(bitLengths);
                }
            } else {
                var numLitLengths = zBitio.readUB(5) + 257;
                var numDistLengths = zBitio.readUB(5) + 1;
                var numCodeLengths = zBitio.readUB(4) + 4;
                var codeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
                if (isArrayBuffer) {
                    codeLengths = new Uint8Array(codeLengths);
                }

                for(i = 0; i < numCodeLengths; i++){
                    codeLengths[ORDER[i]] = zBitio.readUB(3);
                }

                var codeTable = _buildHuffTable(codeLengths);
                var litLengths = [];
                var prevCodeLen = 0;
                var maxLengths = numLitLengths + numDistLengths;
                for (; litLengths.length < maxLengths; ) {
                    sym = _decodeSymbol(zBitio, codeTable);
                    switch (sym) {
                        case 16:
                            i = zBitio.readUB(2) + 3;
                            for (; i--; ) {
                                litLengths[litLengths.length] = prevCodeLen;
                            }
                            break;
                        case 17:
                            i = zBitio.readUB(3) + 3;
                            for (; i--; ) {
                                litLengths[litLengths.length] = 0;
                            }
                            break;
                        case 18:
                            i = zBitio.readUB(7) + 11;
                            for (; i--; ) {
                                litLengths[litLengths.length] = 0;
                            }
                            break;
                        default:
                            if(sym <= 15){
                                litLengths[litLengths.length] = sym;
                                prevCodeLen = sym;
                            }
                            break;
                    }
                }
                distTable = _buildHuffTable(
                    litLengths.splice(numLitLengths, numDistLengths)
                );
                litTable = _buildHuffTable(litLengths);
            }

            sym = 0;
            for (; sym != 256; ) {
                sym = _decodeSymbol(zBitio, litTable);


                if (sym < 256) {
                    buff[buff.length] = sym;
                    test++;
                } else if(sym > 256){
                    var mapIdx = sym - 257;
                    var len = LENS[mapIdx] + zBitio.readUB(LEXT[mapIdx]);
                    var distMap = _decodeSymbol(zBitio, distTable);
                    var dist = DISTS[distMap] + zBitio.readUB(DEXT[distMap]);
                    i = buff.length - dist;
                    for (; len--; ) {
                        buff[buff.length] = buff[i++];
                        test++;
                    }
                }
            }
        } else {
            zBitio.bit_offset = 8;
            zBitio.bit_buffer = null;

            var len = zBitio.readNumber(2);
            var nlen = zBitio.readNumber(2);
            for (; len--; ) {
                buff[buff.length] = zBitio.readNumber(1);
                test++;
            }

        }
    }

    return buff;
}





~~~

~~~
    /**
     * buildHuffTable
     * @param bitLengths
     * @returns {{}}
     */
    function buildHuffTable(bitLengths)
    {
        var numLengths = bitLengths.length;
        var blCount = [];
        var maxBits = _max.apply(Math, bitLengths) + 1;
        var nextCode = [];
        var code = 0;
        var table = {};
        var i = numLengths;
        var len = 0;

        for (; i--; ) {
            len = bitLengths[i];
            blCount[len] = (blCount[len] || 0) + (len > 0);
        }

        for (i = 1; i < maxBits; i++) {
            len = i - 1;
            if (!(len in blCount)) {
                blCount[len] = 0;
            }

            code = (code + blCount[len]) << 1;
            nextCode[i] = code;
        }

        for (i = 0; i < numLengths; i++) {
            len = bitLengths[i];
            if (len) {
                table[nextCode[len]] = {
                    length: len,
                    symbol: i
                };
                nextCode[len]++;
            }
        }
        return table;
    }

    /**
     * decodeSymbol
     * @param bitio
     * @param table
     * @returns {*}
     */
    function decodeSymbol(bitio, table)
    {
        var code = 0;
        var len = 0;
        for (;;) {
            code = (code << 1) | bitio.readUB(1);
            len++;
            if (!(code in table)) {
                continue;
            }

            var entry = table[code];
            if (entry.length == len) {
                return entry.symbol;
            }
        }
    }
~~~