// https://github.com/horsicq/Detect-It-Easy signature file
// Authors:
// LinXP
// Kaens (TG@kaens)

init("archive", "");
includeScript("read");

function checkDeflate(start) {
    var br = new BitReader(start),
        f, bt, tight = 0,
        maxf = Math.min(File.getSize(), 0x100);
    do {
        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.init(br.offset);
            //_log("  len="+len+"="+Hex(len)+"  br.ofs="+Hex(br.offset))
            if (br.offset > File.getSize()) 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 0;
                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 0;
                //_log("after creating a table @"+br.offset+"/"+br.n)
                i = 0;
                while (i < hlit + hdist) {
                    //bitLengthDecoder.decode:
                    if (!_t.length) return 0;
                    j = 0;
                    while (_t[j][0] || _t[j][1]) { j = br.read(1) ? _t[j][1] : _t[j][0]; if (!j) return 0 }
                    //_log("@"+br.offset+"^"+br.n+" code = "+_t[j][2])
                    if (_t[j][2] < 16) { /*auto insert's relevant part*/
                        if (i >= hlit + hdist) return 0;
                        i++
                    } else switch (_t[j][2]) {
                        case 16:
                            if (i) br.read(2);
                            else return 0;
                            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;
                    }
                    if (br.offset > File.getSize()) return 0;
                }
                tight = 2;
                f = true
            }
        } else return 0; //block type 3 is out
        //if(!File.isDeepScan() && br.ofs() >= maxf) f = true; //to avoid slowing it down with, well, unpacking
    } while (!f);
    if (tight == 1) {
        if (br.offset >= File.getSize()) return 0;
        //for(;;) { //TODO
        //    if()
        //}
    }
    return tight
}

function checkZlib(p) { // there's a clearer version in db/zlib but that one won't catch "58.." for example
    if (p + 6 > File.getSize()) return false;
    var c = File.read_uint8(p++);
    if ((c & 0xF) != 8) return false;
    if ((c & 0xF0) > 0x70) return false;
    var start = 2,
        f = File.read_uint8(p++);
    if (f & 0x20) {
        if (p + 8 < File.getSize()) 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(bShowType, bShowVersion, bShowOptions) {
    bad = "";
    if (File.getSize() >= 10 && File.compare("1FA1") && checkDeflate(2)) {
        sName = "Gzip hack: Quasijarus Strong Compression (Z.)";
        bDetected = true;
    }
    if (!bDetected && File.compare("1F8B")) { //gzip
        method = File.read_uint8(2);
        switch (method) {
            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 = File.read_uint8(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 = File.read_int32(5, _LE);
        if (ts <= 0) return false;
        if (File.isVerbose()) {
            switch (File.read_uint8(8)) { //not really meaningful
                case 2:
                    sOption("best");
                    break;
                case 4:
                    sOption("fast");
            }
            switch (File.read_uint8(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:
                    "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 = "?"
            }
            sOption(s, "OS code:");
        }
        p = 10;
        if (f & 4) p += 4 + File.read_uint16(p + 2, _LE);
        if (f & 8) {
            s = File.read_ansiString(p, 1024);
            p += s.length + 1;
            if (File.isVerbose()) sOption('"' + s + '"', "filename:");
        }
        if (f & 0x10) {
            s = File.read_ansiString(p, 1024);
            p += s.length + 1;
            if (File.isVerbose()) sOptionT(s, "cmt:");
        }
        if (f & 0x2) p += 2;
        if (checkDeflate(p)) {
            sName = "GZIP (.gz)";
            bDetected = true;
            if (File.isVerbose()) sOption(File.read_int32(File.getSize() - 4, _LE), "unp.sz:")
        } else if (File.isHeuristicScan()) {
            sName = "GZIP (.gz)";
            bDetected = 1
        }
    }
    if (!bDetected && File.isDeepScan() && File.isHeuristicScan()) {
        for (s = 1; s < 0x40 && !bDetected; s++) {
            if (!bDetected && checkZlib(s) && File.calculateEntropy(s + 4, 0x100) > 7) {
                sName = " Zlib stream (.zlib)";
                sOption("@" + Hex(s));
                sOption(cmprlvl, "lv:");
                bDetected = 1
            }
            if (!bDetected && checkDeflate(s) > 1 && File.calculateEntropy(s + 4, 0x100) > 7) {
                sName = "Raw Deflate stream";
                sOptions = "@" + Hex(s);
                bDetected = 1
            }
        }
    }
    return result(bShowType, bShowVersion, bShowOptions);
}