// Detect It Easy: detection rule file
// Author: horsicq <horsicq@gmail.com>
// Authors:
// LinXP
// Kaens (TG@kaens)

meta("archive");

function checkDeflate(start) {
    var br = new BitReader(start),
        tight = 0,
        maxf = Math.min(X.Sz(), 0x100);
    do {
        var f = br.read(1), bt = br.read(2);
        //_log("@"+br.offset+"^"+br.n+" blockType:"+bt+(f?" (final)":""));
        var code = 0,
            _t = [];
        if (!bt) { // copy stored block data
            br.init(start);
            var len = br.read(8);
            len |= br.read(8) << 8;
            var nlen = br.read(8);
            nlen |= br.read(8) << 8;
            if (len != (nlen ^ 0xFFFF)) return 0;
            br.consume(len); // br.meta(br.offset);
            //_log("  len="+len+"="+Hex(len)+"  br.ofs="+Hex(br.offset))
            if (br.offset > X.Sz()) return false;
            if (f) tight = 1
        } else if (bt == 1 || bt == 2) {
            if (bt == 1) { // fixed block
                //for(i=0; i < 24; i++) {}
                tight = 1;
            } else { //dynamic block
                var hlit = br.read(5) + 257;
                if (hlit > 286) return false;
                var hdist = br.read(5) + 1,
                    hclen = br.read(4) + 4;
                //_log("hlit:"+hlit+" hdist:"+hdist+" hclen:"+hclen)
                var lent = [];
                const lentord = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
                for (i = 0; i < hclen && br.offset < maxf; i++)
                    lent[lentord[i]] = br.read(3);
                _t = createOrderlyHuffmanTable(lent, 19, br);
                if (!_t) return false;
                //_log("after creating a table @"+br.offset+"/"+br.n)
                i = 0;
                while (i < hlit + hdist) {
                    //bitLengthDecoder.decode:
                    if (!_t.length) return false;
                    j = 0;
                    while (_t[j][0] || _t[j][1]) { j = br.read(1) ? _t[j][1] : _t[j][0]; if (!j) return false }
                    //_log("@"+br.offset+"^"+br.n+" code = "+_t[j][2])
                    if (_t[j][2] < 16) { /*auto insert's relevant part*/
                        if (i >= hlit + hdist) return false;
                        i++
                    } else switch (_t[j][2]) { /* beautify preserve:start */
                        case 16: if (i) br.read(2); else return false; break;
                        case 17: for (t = br.read(3) + 3; t; t--); break;
                        case 18: for (t = br.read(7) + 11; t; t--); break;
                        default: return false;
                    } /* beautify preserve:end */
                    if (br.offset > X.Sz()) return false;
                }
                tight = 2;
                f = true
            }
        } else return false; //block type 3 is out
        //if(!X.isDeepScan() && br.ofs() >= maxf) f = true; //to avoid slowing it down with, well, unpacking
    } while (!f);
    if (tight == 1) {
        if (br.offset >= X.Sz()) return false;
        //for(;;) { //TODO
        //    if()
        //}
    }
    return tight
}

function checkZlib(p) {
    if (p + 6 > X.Sz()) return false;
    var c = X.U8(p++);
    if ((c & 0xF) != 8 || (c & 0xF0) > 0x70) return false;
    var start = 2,
        f = X.U8(p++);
    if (f & 0x20) {
        if (p + 8 < X.Sz()) return false;
        start += 4
    }
    if (((c << 8) | f) % 31) return false;
    //_log("zlib, data begins @"+Hex(start))
    if (!checkDeflate(start)) return false;
    cmprlvl = ["fastest", "fast", "default", "max"][f >> 6];
    return true
}

function detect() {
    bad = "";
    if (X.Sz() >= 10 && X.c("1FA1") && checkDeflate(2)) {
        sName = "Gzip hack: Quasijarus Strong Compression (Z.)";
        bDetected = true;
    }
    if (!bDetected && X.c("1F8B")) { //gzip
        method = X.U8(2);
        switch (method) { /* beautify preserve:start */
            case 0: sVersion = "store"; bad = bad.addIfNone("!badalgo"); break;
            case 1: sVersion = "compress"; bad = bad.addIfNone("!badalgo"); break;
            case 2: sVersion = "pack"; bad = bad.addIfNone("!badalgo"); break;
            case 3: sVersion = "lz"; bad = bad.addIfNone("!badalgo"); break;
            case 8: sVersion = "deflate"; break; //the only format gzip won't throw an error about
            default: return false;
        }
        f = X.U8(3);
        fs = [];
        if (f & 1) fs.push("ASCII_FLAG");
        if (f & 2) fs.push("HEADER_CRC");
        if (f & 4) fs.push("EXTRA_FIELD");
        if (f & 8) fs.push("ORIG_NAME");
        if (f & 0x10) fs.push("COMMENT");
        if (f & 0x20) fs.push("ENCRYPTED");
        if (f & 0xC0) fs.push("RESERVED");
        ts = X.I32(5, _LE);
        if (ts <= 0) return false;
        if (X.isVerbose()) {
            switch (X.U8(8)) { //not really meaningful
                case 2: sOption("best"); break;
                case 4: sOption("fast");
            }
            switch (X.U8(9)) {
                case 0: s = "FAT FS (MS-DOS, OS/2, NT/Win32"; break;
                case 1: s = "Amiga"; break;
                case 2: s = "(Open)VMS"; break;
                case 3: s = "Unix"; break;
                case 4: s = "VM/CMS"; break;
                case 5: s = "Atari TOS"; break;
                case 6: s = "HPFS (OS/2, NT)"; break;
                case 7: s = "Macintosh"; break;
                case 8: s = "Z-System"; break;
                case 9: s = "CP/M"; break;
                case 10: s = "TOPS-20"; break;
                case 11: s = "NTFS (NT)"; break;
                case 12: s = "QDOS"; break;
                case 13: s = "Acorn RISCOS"; break;
                case 255: s = "unknown OS"; break;
                default: s = "?"
            } /* beautify preserve:end */
            sOption(s, "OS code:");
        }
        p = 10;
        if (f & 4) p += 4 + X.U16(p + 2, _LE);
        if (f & 8) {
            s = X.SA(p, 1024);
            p += s.length + 1;
            if (X.isVerbose()) sOption(s, 'filename:"', '"');
        }
        if (f & 0x10) {
            s = X.SA(p, 1024);
            p += s.length + 1;
            if (X.isVerbose()) sOptionT(s, "cmt:");
        }
        if (f & 0x2) p += 2;
        if (checkDeflate(p)) {
            sName = "GZIP (.gz)";
            bDetected = true;
            if (X.isVerbose()) sOption(X.U32(X.Sz() - 4, _LE), "unp.sz:")
        } else if (X.isHeuristicScan()) {
            sName = "GZIP (.gz)";
            bDetected = true
        }
    }
    if (!bDetected && X.isDeepScan() && X.isHeuristicScan() && X.calculateEntropy(0x40, 0x100) > 7) {
        for (s = 1; s < 0x40 && !bDetected; s++) {
            if (!bDetected && checkZlib(s)) {
                sName = "Zlib stream (.zlib)";
                sOption("@" + Hex(s));
                sOption(cmprlvl, "lv:");
                bDetected = true
            }
            if (!bDetected && checkDeflate(s) > 1) {
                sName = "Raw Deflate stream";
                sOptions = "@" + Hex(s);
                bDetected = true
            }
        }
    }
    return result();
}