// Detect It Easy: detection rule file
// Authors: hypn0 <hypn0@mail.ru>, Kaens (TG@kaens)

meta("format", "");

/* beautify ignore:start */
function detect() {
    if (Binary.compare("00010000'Standard Jet DB'00")) {
        sName = "Microsoft Access database (.MDB)";
        bDetected = true;
    }

    function isDBF() {
        // from https://independent-software.com/dbase-dbf-dbt-file-format.html
        // & https://www.clicketyclick.dk/databases/xbase/format/dbf.html
        // & https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
        nv = X.U8(0); v5 = false;
        switch (nv) {
            case 0x02: sv = 'FoxBase 1.0'; break;
            case 0x03: sv = 'FoxBase 2.x / dBASE III, no memo file'; break;
            case 0x04: sv = 'dBASE IV, no memo file'; break;
            case 0x05: sv = 'dBASE V, no memo file'; break;
            case 0x07: sv = "VISUAL OBJECTS v1.x for dBase III, no memo file"; break;
            case 0x30: sv = 'Visual FoxPro (possibly with DBC)'; break;
            case 0x31: sv = 'Visual FoxPro with auto increment'; break;
            case 0x32: sv = 'Visual FoxPro with varchar/varbinary'; break;
            case 0x43: sv = 'dBASE IV SQL Table, no memo file / Flagship .dbv memo var size'; break;
            case 0x64: sv = 'dBASE IV SQL System, no memo file'; break;
            case 0x83: sv = 'FoxBase 2.x / dBASE III+ with memo file'; break;
            case 0x87: sv = 'VisualObjects 1.x with memo file'; break;
            case 0x8B: sv = 'dBASE IV with memo file'; break;
            case 0x8C: sv = 'dBASE V with memo file'; break;
            case 0x8E: sv = 'dBASE IV with SQL table'; break;
            case 0xB3: sv = '.dbv with memo table'; break;
            case 0xCB: sv = 'dBASE IV SQL Table with memo file'; break;
            case 0xE5: sv = 'Clipper SIX with memo file'; break;
            case 0xF5: sv = 'FoxPro 2 with memo file'; break;
            case 0xFB: sv = 'FoxPro 2, no memo file'; break;
            default: return false;
        }
        if (nv == 2) {
            recc = X.U16(1); if (!recc) return false; recsz = X.U16(6); if (!recsz) return false;
            hdrsz = 0x209; rectp = 8; enc = 'IBM437';
        } else {
            y = X.U8(1); if (y < 80) y += 2000; else y += 1900; m = X.U8(2); d = X.U8(3);
            if (X.c('000000', 1)) { upd = 'never' } else {
                if (!isWithin(y, 1970, 2100) || !isWithin(m, 1, 12) || !isWithin(d, 1, 31)) return false;
                upd = y + '-' + m.padStart(2, '0') + d.padStart(2, '0')
            }
            recc = X.U32(4); if (!recc) return false;
            hdrsz = X.U16(8); recsz = X.U16(0xA); if (hdrsz < 0x20 || !recsz) return false;
            res0 = X.U24(0xC); res1 = X.readBytes(0xF, 13); res2 = X.U32(0x1C);
            if ([4, 0x8C].indexOf(nv) >= 0) {
                rectp = 0x44; enc = 'CP' + X.SA(0x22, 3); if (isWithin((t = X.SA(0x25, 1)), '0', '9')) enc += t;
                if (enc == 'CPKOI8') enc += X.SA(0x26, 1); v5 = true
            } else {
                rectp = 0x20;
                switch (X.U8(0x1D)) {
                    case 0x02: enc = 'CP850'; break; //DOS Multilingual
                    case 0x03: enc = 'CP1252'; break; //Windows ANSI
                    case 0x04: enc = 'CP10000'; break; //aka. 'macintosh', MAC Roman/W.European Mac
                    case 0x64: enc = 'CP852'; break; //Eastern-European MS-DOS
                    case 0x65: enc = 'CP865'; break; //Nordic MS-DOS
                    case 0x66: enc = 'CP866'; break; //Russian MS-DOS
                    case 0x67: enc = 'IBM861'; break; //Icelandic MS-DOS
                    //case 0x68: enc = 'Kamenicky (Czech) MS-DOS'; break;
                    //case 0x69: enc = 'Mazovia (Polish) MS-DOS'; break;
                    case 0x6A: enc = 'IBM737'; break; //Greek MS-DOS (aka. 437G)
                    case 0x6B: enc = 'IBM857'; break; //Turkish MS-DOS
                    case 0x96: enc = 'CP10007'; break; // aka. 'x-mac-cyrillic', Russian Mac
                    case 0x97: enc = 'CP10029'; break; // aka. 'x-mac-ce', MAC Latin 2/C.European Mac
                    case 0x98: enc = 'CP10006'; break; //aka. 'x-mac-greek', Greek Mac
                    case 0xC8: enc = 'CP1250'; break; //C.European Windows
                    case 0xC9: enc = 'CP1251'; break; //Russian Windows
                    case 0xCA: enc = 'CP1254'; break; //Turkish Windows
                    case 0xCB: enc = 'CP1253'; break; //Greek Windows
                    case 0x01: default: enc = 'IBM437' //OEM US
                }
            }
            //TODO check res* depending on version
        }
        validC = ['C', 'N', 'L', 'D', 'M', 'F', 'B', 'G', 'P', 'Y', 'T', 'I', 'V', 'X', '@', 'O', '+', '0'];
        fldc = 0; totalfldsz = 1 /* we begin with a deletion flag */; p = rectp; old = [0, 1];
        while (X.U8(p) != 0xD && p < hdrsz) {
            var name = X.readBytes(p, v5 ? 0x1F : 11); ns = charStat(name, 1);
            // _log('fld#'+fldc+' @'+Hex(p)+': '+decEncoding(name,CP866)+' (ns=['+ns+'])')
            if (!name[0] || (ns.indexOf('allxsc') < 0 && ns.indexOf('allforeign') < 0)) return false;
            tp = X.SA(v5 ? p + 0x20 : p + 0xB, 1); if (validC.indexOf(tp) < 0) return false;
            if (nv == 2) fldst = X.U16(p + 0xD); else if (v5) fldst = 0; else fldst = X.U32(p + 0xC);
            fldsz = X.U8(nv == 2 ? p + 0xC : v5 ? p + 0x21 : p + 0x10); if (!fldsz) return false;
            // _log('  sz:'+fldsz+', old: '+old+'; '+old[0]+'+'+Hex(old[1])+' = '+Hex(old[0]+old[1])+' ?? '+Hex(fldst))
            if (old[0] && fldst && old[0] + old[1] != fldst) return false; old = [fldsz, fldst];
            totalfldsz += fldsz;
            decn = X.U8(nv == 2 ? p + 0xF : p + 0x11);
            if (tp === 'N' && decn > fldsz) return false;
            fldc++; p += nv == 2 ? 0x10 : v5 ? 0x30 : 0x20;
            if (charStat(name.slice(0, name.indexOf(0)), 1).indexOf('foreign') >= 0
                && enc == "IBM437") enc = "CP1251"; // pesky Russians?
        }
        // _log('@'+Hex(p)+': '+fldc+' fields total size '+totalfldsz+' vs. '+recsz)
        if (totalfldsz - recsz != 0) return false;
        del = 0; if (X.isDeepScan()) {
            p = hdrsz; for (i = 0; i < recc; i++, p += recc) if (X.U8(p) == 0x2A) del++
        }
        sz = hdrsz + recc * recsz; if (X.U8(sz) == 0x1A) sz++;
        return sz <= X.Sz() || X.isHeuristicScan()
    }
    if (!bDetected && isDBF()) {
        sName = "dBase Database (.DBF)"; sVersion = sv; bDetected = true;
        if (X.isVerbose()) {
            sOption('fld:' + fldc + ' rec: ' + recc + (del ? '(* ' + del + ')' : '') + ' enc: ' + enc + ' sz: ' + outSz(sz))
        }
    }

    return result();
}
/* beautify ignore:end */
