// https://github.com/horsicq/Detect-It-Easy signature file
// Authors:
// Kaens TG@kaens (for all the https://en.wikipedia.org/wiki/Music_tracker stuff)
// hypn0 <hypn0@mail.ru>

init("audio","");

includeScript("read");

function detect(bShowType,bShowVersion,bShowOptions) {
// Due to the extreme script file size, many good code practices were sacrificed to ensure it loads

    if(File.compare("'[1tracker module]'0D0A")) {
        sName = "Shiru's 1tracker module (.1TM)"; bDetected = true;
        l = File.findString(1,0x40,"Engine="); if(l >= 0) {
            r = File.findString(l+7,0x40,".");
            sVersion = "for "+File.read_ansiString(l+7,r-l-7)
        }
        if(File.isVerbose()) {
            l = File.findString(r,0x80,"Title="); if(l >= 0) {
                r = File.findSignature(l+6,0x40,"0D0A");
                sOption(File.read_ansiString(l+6,r-l-6)) }
            l = File.findString(r,0x80,"Author="); if(l >= 0) {
                r = File.findSignature(l+7,0x40,"0D0A");
                sOption(File.read_ansiString(l+7,r-l-7),"by: ") }
            l = File.findString(r,0x80,"Speed="); if(l >=0 ) {
                r = File.findSignature(l+7,0x40,"0D0A");
                sOption(File.read_ansiString(l+7,r-l-7),"spd:") }
        }
    }
    else if( (File.compare("'if'") || File.compare("'JN'")) && File.getSize()>0x1F1+0x600
      && File.read_uint8(0x6E)<=0x40 && File.read_uint8(0x6F)<=0x80 && File.read_uint8(0xF0)==0xFF) {
        sName = "Composer 669 / UNIS 669 module (.669)"; bDetected = true;
        smp = File.read_uint8(0x6E);
        c = 0; for(i = 0; i < smp; i++) c += File.read_uint32(0x01F1+i*0x19+13,_LE);
        c += 0x1F1+(smp*0x19)+smp*0x600;
        if(File.isVerbose()) {
            if(File.read_ansiString(0,2) == "JN") sVersion = "extended";
            //if(File.getSize() != c) sVersion += "malformed";
            sOptionT(File.read_codePageString(0x02,36,"CP850")); //the seemingly most-used encoding for'em
            sOptionT(File.read_codePageString(0x26,36,"CP850"));
            sOptionT(File.read_codePageString(0x4A,36,"CP850"));
            sOption("ptn:"+File.read_uint8(0x6F)+" smp:"+smp+" loop:"+File.read_uint8(0x70))
        }
    }
    else if(File.compare("'_A2module_'")) {
        bDetected = true; bad = false;
        sName = "AdLib Tracker II module (.A2M)";
        nV = File.read_uint8(0x0E); sVersion = "/┤DLiB TR/┤CK3R ][ v"+nV;
        ptn = File.read_uint8(0x0F); if(!ptn || ptn>64) bad = "!badptn";
        if(bad) sVersion += "/malformed"+bad;
        else if(File.isVerbose()) sOption("ptn:"+ptn);
    }
    else if(File.compare("'EXITgB'2018201820182F18201024C94AA80004660A558006A80000000200042200D28924C1D0A8000424C0D1DF2018D1D82018D1D82018201812D804800000000166F660B4610000'H|'017E01610000'Z|'027E02610000'R|'037E03610000'JL'DF7FFF'NuH'E7FFFE7E01610000AC7E02610000A67E03610000A04CDF7FFF'Nu`'0A602260000092600000'~A'FA....4BF900DFF000'p0;|'000000AA0440001064F4'NuK'F900DFF0003007C0FC",0x1A) //check D0h bytes
        ) {
        bDetected = true; sName = "Art and Magic module (.AAM)";
        if(File.isVerbose()) {
            t = File.findSignature(0x8000,Math.min(0xFFFF,File.getSize()),"'EXIT'");
            if(t >= 0) sOption(outSz(t+4),"sz:");
            else sVersion = "malformed!short"
        }
    }
    else if(File.compare("'ADLIB'01")) {
        sName = "Martin Fernandez's Adlib module (.ADLIB)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_ansiString(6,0x100);
            g = File.read_ansiString(6+t.length+1,0x100);
            sOption(t); sOption(g,"for: ")
        }
    }
    else if(File.compare("'AERO'")) {
        sName = "Aero Tracker module (.AERO)"; bDetected = true;
        sVersion = "v"+File.read_uint8(0x0E);
    }
    else if(File.compare("'AM01'000000") && File.compare("'ASD1'00", 0x38)) {
        sName = "Ace Tracker module (.AM)"; bDetected = true;
    }
    else if(File.compare("'ZXAYAMAD'")) {
        //the format seems similar to .EMUL
        sName = "AY Amadeus module (.AMAD)"; bDetected = true;
        if(File.isVerbose()) {
            ptitle = 0x14 + File.read_uint16(0x14,_LE);
            sOptionT(File.read_ansiString(ptitle))
        }
    }
    else if(File.compare("'AMC V'......' REPLAY!'")) {
        sName = "A.M. Composer module (.AMC)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(5,3);
    }
    else if(File.compare("'<o'EF'QU'EE'RoR'", 0x426) || File.compare("'MaDoKaN96'", 0x426)) {
        //ref http://modland.com/pub/documents/format_documentation/AMusic%20(.amd).txt
        sName = "AMusic Adlib-MOD module (.AMD)"; bDetected = true;
        if(File.read_ansiString(0x426,1) != "<") sVersion = "xms rip-off";
        if(File.read_uint8(0x42F) == 0x11) sVersion = sVersion.append("packed");
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0,0x18));
            sOptionT(File.read_ansiString(0x18,0x18),"by: ");
            sOption("ord:"+File.read_uint8(0x3A4)+" ptn:"+(File.read_uint8(0x3A5)+1));
        }
    }
    else if(File.compare("'ASYLUM Music Format'")) {
        sName = "ASYLUM Music Format (.AMF)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(0x15,3);
    }
    else if(File.compare("'AMF'0.")) {
        //some say this format is identical to ASYLUM... /presses (X)/ --Kae
        sName = "Digital Sound and Music Interface Advanced Music Format module (.AMF)";
        sVersion = "v0"+Hex(File.read_uint8(3)); bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(4,32));
    }
    else if(File.compare("'AMShdr'1A") && (File.read_uint8(7) <= 30)) {
        //ref http://modland.com/pub/documents/format_documentation/Velvet%20Studio%20v2.2,%20Advanced%20Module%20System%20(.ams).txt
        bDetected = true; t_ = File.read_uint8(7);
        sName = "Velvet Studio Advanced Module System tune (.AMS)";
        sVersion = "v"+File.read_uint8(9+t_)+"."+File.read_uint8(8+t_);
        bad = 0;
        ptn = File.read_uint16(8+t_+3); if(!ptn || ptn>1024) bad++;
        ord = File.read_uint16(8+t_+5); if(!ord) bad++;
        trk = File.read_uint8(8+t_+10); //if(!trk || trk>32) bad++;
        cmd = File.read_uint8(8+t_+11); //if(!cmd || cmd>7) bad+;
        if(bad) sVersion += "/malformed"+bad;
        if(File.isVerbose()) {
            ins = File.read_uint8(8+t_+2);
            bpm0 = File.read_uint8(8+t_+8)+"."+File.read_uint8(8+t_+7);
            spd0 = File.read_uint8(8+t_+9);
            rows = File.read_uint8(8+t_+12);
            flg =  File.read_uint16(8+t_+13);
            ch = (flg >> 6)&1?"2":"1";
            linfreqtbl = (flg >> 7)&1?" linfreqtbl":"";
            midiused = (flg >> 8)&1?" midi used":"";
            sOptionT(File.read_codePageString(8,t_,"CP850"));
            sOption("ch:"+ch+" ins:"+ins+" ptn:"+ptn+" ord:"+ord+
                " bpm0:"+bpm0+" spd0:"+spd0+" trk:"+trk+" cmd0:"+cmd+" rows0:"+rows+
                linfreqtbl+midiused)
        }
    }
    else if(File.compare("'Extreme0'01")) {
        //ref http://modland.com/pub/documents/format_documentation/Extreme's%20Tracker%20v1.3%20(.ams).txt
        sName = "Extreme Tracker module (.AMS)"; bDetected = true;
        //song tech info
        sVersion = "v"+File.read_uint8(7)+"."+File.read_uint8(8);
        if(File.isVerbose()) {
            cccsssss = File.read_uint8(9);  cmd=cccsssss>>5; sch=cccsssss&0x1F+1;
            smp = File.read_uint8(10); ptn = File.read_uint16(11,_LE); pos = File.read_uint16(13,_LE);
            vmch = File.read_uint8(15); xtra = File.read_uint16(16,_LE);
            //title
            pts = smp*17 + xtra + 18; ts = File.read_uint8(pts);
            sOptionT(File.read_ansiString(pts+1,ts));
            sOption("cmd:"+cmd+" strk:"+sch+" smp:"+smp
              +" ptn:"+ptn+" pos:"+pos+" mtrk:"+(vmch+1))
        }
    }
    else if(File.compare("'AMX ")) {
        sName = "AND Xynth module (.AMX)"; bDetected = 1
    }
    else if(File.compare("'AON4'") || File.compare("'AON8'")) {
        sName = "ArtOfNoise/Chorus module (.AON)"; bDetected = true;
        sVersion = File.read_ansiString(3,1)+"ch";
        if(File.isVerbose()) {
            id = File.read_ansiString(4,0x2A);
            p = 0x2E; //header skipped
            t = ''; a = ''; d = ''; c = ''; ord = 0;
            while (p < File.getSize()) {
                hkhd = File.read_ansiString(p,4);
                hksz = File.read_uint32(p+4,_BE);
                p+=0x08; //chunk header = 4xfourcc + 4xsize
                switch (hkhd) {
                    case "NAME": t = File.read_codePageString(p,hksz,"CP1252"); break;
                    case "AUTH": a = File.read_codePageString(p,hksz,"CP1252"); break;
                    case "DATE": d = File.read_codePageString(p,hksz,"CP1252"); break;
                    case "RMRK": c = File.read_codePageString(p,hksz,"CP1252"); break; //apparently intended to be 78ch per line, maybe TODO auto-wrap (without left trimming!)
                    case "PLST": ord = hksz; break;
                }
                p += hksz;
            }
            sOptionT(t); sOptionT(a,"by: "); sOptionT(d,"on: ");
            sOption(c); sOption(id,"with: "); sOption(ord,"ord:");
        }
    }
    else if(File.compare("'ADRVPACK'")) {
        sName = "AProSys module (.APS)"; bDetected = 1
    }
    else if(File.compare("'ARP.'")) {
        sName = "Arpeggiator module (.ARP)"; bDetected = 1
    }
    else if(File.compare("'ACTIONAMICS SOUND TOOL'",0x3E)) {
        sName = "Actionamics Sound Tool module (.AST)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(0x56,3);
    }
    else if(File.compare("08'AST '")) {
        sName = "All Sound Tracker module (.AST)"; bDetected = true;
        sVersion = "v" + File.read_ansiString(5,4);
        if(File.isVerbose()) {
            L = File.read_uint16(0x0A,_BE);
            info = File.read_codePageString(12,L+1,"CP850").trim();
            var s = ""; var q=0;
            for (var i = 0; q<2; i+=0x26) {
                a = info.slice(i,i+0x26);
                if(a.trim() != "") {q++; s += " "+a.trim();} //skipping the empty lines
            }
            sOption(s);
        }
    }
    else if(File.compare("'AudioSculpture'")) {
        sName = "Audio Sculpture module (.AS)"; bDetected = 1
    }
    else if(File.compare("'##synth'0D0A")) {
        sName = "Athtune module (.ATHTUNE)"; bDetected = 1
    }
    else if(File.compare("'FORM'.... ....'AXSFUSER'")) {
        sName = "AXS module (.AXS)"; bDetected = 1
    }
    else if(File.compare("'CBMF'")) {
        sName = "Bob's Adlib Music module (.BAM)"; bDetected = 1
    }
    else if(File.compare("'BBSONG'00")) {
        sName = "Beepola module (.BBSONG)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(7,5)+":"+File.read_ansiString(File.findString(10,1000,"Engine=")+7);
        if(File.isVerbose()) {
            title = File.read_ansiString(0x18,100);
            sOptionT(title);
            auth = File.read_ansiString(0x18+title.length+8,100);
            sOptionT(auth,"by: ")
        }
    }
    else if(File.compare("000003F3") && File.read_uint8(0x14)
      && File.compare("70FF4E75'DAGLISH!'",0x20)
      && File.read_uint32(0x2C,_BE) && File.read_uint32(0x30,_BE)
      && File.read_uint32(0x34,_BE) && File.read_uint32(0x38,_BE)) {
        bDetected = true;
        sName = "Ben Daglish's SID (.BDS)"; sVersion = "v1.1";
        if(File.isVerbose()) {
            title = File.read_ansiString(0x20+File.read_uint32(0x3C,_BE),0x100);
            auth = File.read_ansiString(0x20+File.read_uint32(0x40,_BE),0x100);
            misc = File.read_ansiString(0x20+File.read_uint32(0x44,_BE),0x100);
            sOptionT(title);
            x = File.read_uint32(0x38,_BE); if(x > 1) sOption(x,"×");
            sOptionT(auth,"by: ");
            sOptionT(misc);
        }
    }
    else if(File.compare("'Buzz'")) {
        sName = "Jeskola Buzz module (.BMX)";  bDetected = true;
        // Do NOT waste time trying to extract anything else...
        // You'll find the format dox lie and formats differ
        // For example, the no. of machines overlaps with some beta's version string
        // For example, that beta has no float x/y coordinates
        // And there is no signature difference.
    }
    else if(File.compare("'buz2'02")) {
        sName = "Buzzic module (.BUZ2)"; bDetected = true;
        sVersion = "v2";
    }
    else if(File.compare("'BRTF'")) {
        sName = "BeRoTracker module (.BRT)"; bDetected = true;
        if(File.isVerbose()) {
            p = 8; t = ''; c = ''; q = ''; instmsg = []; sainmsg = [];
            ins = 0; smp = 0; ord = 0; //TODO figure out how to get ptn
            bpm0 = 0; spd0 = 0; st0 = 0; mvol = 0; rowsperbeat = 0; hltu = 0; hltd = 0;
            while (p < File.getSize()) {
                hkhd = File.read_ansiString(p,4);
                hksz = File.read_uint32(p+4,_LE);
                p += 8;
                switch (hkhd) {
                    case "NAME": t = File.read_ucsdString(p); break;
                    case "MESS": c = File.read_codePageString(p+2,File.read_uint32(p,_LE),"CP1252"); break;
                    case "BPMI": rpb = File.read_uint8(p); break;
                    case "INFO":
                        spd0 = File.read_uint8(p+4); bpm0 = File.read_uint8(p+5);
                        st0 = File.read_uint8(p+6); mvol = File.read_uint8(p+10);
                        break;
                    case "PORD": ord = File.read_uint8(p); break;
                    case "PAIN": hltu = File.read_uint8(p+1); hltd = File.read_uint8(p+2); break;
                    case "SAIN":
                        smp++;
                        q = File.read_ucsdString(p+2); if(q.trim().length) sainmsg.push(q);
                        break;
                    case "INST":
                        if(File.read_uint8(p)) ins++;
                        q = File.read_ucsdString(p+1); if(q.trim().length) instmsg.push(q);
                        break;
                    case "DONE": p = File.getSize(); break;
                }
                p += hksz;
            }
            sOption(t); sOption(c);
            sOption("ord:"+ord+" ins:"+ins+" smp:"+smp+" hlt:"+hltu+"/"+hltd+
                " RPB:"+rpb+" spd0:"+spd0+" bpm0:"+bpm0+" st.sep0:"+st0+" mixvol:"+mvol);
            sOption(instmsg.join("\n"),"ins.text:\n"); sOption(sainmsg.join("\n"),"smp.text:\n");
        }
    }
    else if(File.compare("FFFFFFFF",0x34) && File.compare("1027",0x15C)
      && File.compare("1027",0x164)) {
        sName = "BoyScout module (.BSF)"; bDetected = 1
    }
    else if(File.compare("'NuBEATHOVEN'",0x22)) {
        bDetected = true;
        sName = "Beathoven Synthesizer (.BSS)";
        sVersion = "v"+File.read_ansiString(0x2D,3);
        if(File.isVerbose()) {
            title = File.read_ansiString(0x6C,0x100);
            auth = File.read_ansiString(0x6C+title.length+1,0x100);
            misc = File.read_ansiString(0x6C+title.length+auth.length+2,0x100);
            sOptionT(title);
            sOptionT(auth,"by: ");
            sOptionT(misc);
        }
    }
    else if(File.compare("'CBA'F9")) {
        sName = "CBA module (.CBA)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(4,0x20))
    }
    else if(File.compare("'<CUD-FM-File>'1ADEE0")) {
        //ref https://github.com/adplug/adplug/blob/master/src/cff.cpp
        sName = "Boom Tracker module (.CFF)"; bDetected = true;
        sVersion = "v"+File.read_uint8(0x10);
        if(File.read_uint8(0x13)) sVersion += "/packed";
        if(File.getSize() != File.read_uint16(0x11,_LE)+0x20)
            sVersion += "/malformed"
    }
    else if(File.compare("'CHIPv'3.")) {
        //ref "chip" from the release
        sName = "Chip Tracker module (.CHI)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(5,1)+".x"; if(File.getSize() <= 0x100) sVersion += "/malformed";
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(8,0x20));
            tempo = File.read_uint8(0x28); ord = File.read_uint8(0x29); loop =  File.read_uint8(0x30);
            ptn=0; for(i=0; i < ord; i++) { p =File.read_uint8(0x100+i); if(p > ptn) ptn = p }
            sOption("tempo:"+tempo+" ord:"+ord+" ptn:"+ptn+" loop:"+loop)
        }
    }
    else if(File.compare("'CTMF'")) {
        sName = "Creative Music File (.CMF)"; bDetected = true;
        //ref https://github.com/adplug/adplug/blob/master/src/cmf.cpp
        nV = File.read_uint16(4,_LE);
        sVersion = "v"+(nV>>8)+"."+(nV&0xFF); bad = 0;
        pins = File.read_uint16(6,_LE); //instruments ptr
        tp = File.read_uint16(0x0E,_LE); if(tp >= pins) { tp = 0; bad = 1 }
        ap = File.read_uint16(0x10,_LE); if(ap >= pins) { ap = 0; bad = 2 }
        cp = File.read_uint16(0x12,_LE); if(cp >= pins) { cp = 0; bad = 3 }
        if(bad) sVersion += "/malformed"+bad;
        if(File.isVerbose()) {
            if(tp) sOptionT(File.read_ansiString(tp,0x100));
            if(ap) sOptionT(File.read_ansiString(ap,0x100),"by: ");
            if(cp) sOptionT(File.read_ansiString(cp,0x100));
        }
    }
    else if(File.compare("000003F3") && File.compare("70FF4E75'S.PHIPPS'",0x20) &&
      0 < File.read_int32(0x40,_BE) < File.getSize() && 0 < File.read_int32(0x44,_BE) < File.getSize() ) {
        sName = "Code Design module (.CORE)"; bDetected = true;
        if(File.isVerbose()) {
            title = File.read_ansiString(0x68,0x100);
            auth = File.read_ansiString(0x68+title.length+1,0x100);
            misc = File.read_ansiString(0x68+title.length+auth.length+2,0x100);
            sOptionT(title);
            sOptionT(auth,"by: ");
            sOptionT(misc);
        }
    }
    else if(File.compare("'CAT '................'FORM'")) {
        sName = "Cybertracker (not C64) module (.CT)"; bDetected = true;
    }
    else if(File.compare("0004'NNTRKMZX'")) {
        sName = "Cybertracker C64 module (.CT)"; bDetected = true;
        //ref https://noname.c64.org/download.php/ctmisc/ctfileformat-1_01.html
        sVersion = "v"+File.read_uint8(0x0B)+"."+File.read_uint8(0x0A);
        if(File.isVerbose()) {
            for(sng=0; sng < File.read_uint16(0x4B9,_LE); sng++)
                if(File.read_uint8(0x4BB+sng-1) == 0) break;
            if(sng) {
                sng0ord = File.read_uint8(0x4BB);
                loop0 = File.read_uint8(0x6BD);
                if(sng > 1) sOption("×"+sng+" ord0:"+sng0ord+" loop0:"+loop0);
                else sOption(" ord:"+sng0ord+" loop:"+loop0)
            }
            else sOption("empty");
        }
    }
    else if(File.compare("0004'NNTRKINS'")) {
        sName = "Cybertracker C64 instrument (.CI)"; bDetected = true;
        sVersion = "v"+File.read_uint8(0x0B)+"."+File.read_uint8(0x0A);
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x1A,0x10))
    }
    else if(File.compare("000003F3") && (File.findString(0,0x100,"NuDELIRIUM")>0)) {
        sName = "DeliTracker player addon or Amiga Custom Module (.CUST)"; bDetected = true;
        sVersion = "CustomPlay";
        if(File.isVerbose())
            sOptionT(File.read_ansiString(File.findString(0,0x100,"NuDELIRIUM")+20,0x100))
    }

    else if(File.compare("'JCH'26026601")) {
        sName = "Edlib Tracker module samples (.S01)"; bDetected = 1
    }
    else if((File.compare("'JCH'26 026600") && File.compare("FFFF",0x75))
      || (File.compare("'JCH'26 026680") && File.compare("FFFF",0x77))) { //is this one a one-off?
        //ref http://ftp.modland.com/pub/documents/format_documentation/Edlib%20Tracker%20(.edl,%20.d00,%20.d01).txt
        bDetected = true; bad = 0;
        sName = "Edlib Tracker module (.D00,.D01)";
        sVersion = "v"+File.read_uint8(7);
        //if(File.read_uint8(6) == 4) bad = 1;
        if(File.read_uint8(0xA) > 1) bad = 1;
        if(bad) sVersion += "/malformed"+bad;
        if(File.isVerbose()) {
            x = File.read_uint8(9);
            sOptionT(File.read_codePageString(0x0B,0x20,"CP850"));
            if(x > 1) sOption(x,"×");
            sOptionT(File.read_codePageString(0x2B,0x20,"CP850"),"by: ");
            p = File.read_uint16(0x71,_LE);
            sOptionT(File.read_codePageString(p,File.getSize()-p-2,"CP850"));
            sOption(outSz(File.read_uint16(0x71,_LE)+2),"sz:")
        }
    }
    else if(File.compare("'DBM0'........'NAME'") && File.compare("'INFO'",0x3C)
      && File.compare("'SONG'",0x4E) && File.read_uint8(4) < 4) {
        sName = "Digi Booster Pro module (.DBM)"; bDetected = true;
        sVersion = "v"+File.read_uint8(4)+"."+File.read_uint8(5).padStart(2,'0');
        p = 8; done = false; if(File.isVerbose()) ord = []; titles = [];
        while(!done && p < File.getSize()) {
            hkhd = File.read_ansiString(p,4); hksz = File.read_uint32(p+4,_BE); p += 8;
            switch(hkhd) {
            case "INFO": if(!File.isVerbose()) break; x = File.read_uint16(p+4,_BE);
                info = "ch:"+File.read_uint16(p+8,_BE)+" ptn:"+File.read_uint16(p+6,_BE)
                +" ins:"+File.read_uint16(p,_BE)+" smp:"+File.read_uint16(p+2,_BE);
                break;
            case "SONG": if(!File.isVerbose()) break; q = 0;
                while(q+44 < hksz) { t = File.read_codePageString(p+q,44,"CP1250").trim(); titles.push(t);
                    q += 44; ord.push(File.read_uint16(p+q,_BE)); q += 2+ord[ord.length-1]*2 }
                break;
            case "SMPL": done = true
            }
            p += hksz;
        }
        if(!done || p > File.getSize()) sVersion += "/malformed!short";
        if(File.isVerbose()) {
            title = File.read_ansiString(0x10,42).trim(); sOption(title);
            sOption(titles.join("; "),(!title && title.length == 1 ? "songs:" : ""));
            if(title == "" && !titles.length) sOptionT(File.read_ansiString(0xD8,28));
            if(x > 1) sOption(x,"×");
            info = "ord:"+ord.join("+")+" "+info; sOption(info+" sz:"+outSz(p))
        }
    }
    else if(File.compare("'DFM'1A")) {
        sName = "Digital FM module (.DFM)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(7,32))
    }
    else if(File.compare("'DSNGSEQU'00")) {
        sName = "David Hanney's module (.DH)"; bDetected = 1
    }
    else if(File.compare("'DIGI Booster module'")) {
        sName = "DIGI Booster module (.DIGI)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(0x15,3);
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x262,0x20));
            sOptionT(File.read_ansiString(0x282,0x20),"by: ");
        }
    }
    else if(File.compare("000003F3") && File.compare("70FF'NuUNCLEART'",0x20)
      && File.read_uint8(0x14) && File.read_uint32(0x2C,_BE)
      && File.read_uint32(0x30,_BE) && File.read_uint32(0x38,_BE)) {
        sName = "Dave 'Uncle Tom' Lowe module (.DL)"; bDetected = true;
        if(File.isVerbose()) {
            smpip = 0x20+File.read_uint32(0x3C,_BE);
            smpiep = 0x20+File.read_uint32(0x40,_BE);
            if(!smpiep) smp = 0;
            else smp = Math.floor((smpiep-smpip)/14);
            title = File.read_ansiString(0x20+File.read_uint32(0x50,_BE),0x100);
            auth = File.read_ansiString(0x20+File.read_uint32(0x54,_BE),0x100);
            cmt = File.read_ansiString(0x20+File.read_uint32(0x58,_BE),0x100);
            loadsz = 0x20+File.read_uint32(0x5C,_BE);
            sz = 0x20+File.read_uint32(0x60,_BE);
            sza = Hex(File.getSize()-sz); //TODO value different, why? Where does it start?
            smpsz = 0x20+File.read_uint32(0x64,_BE);
            songsz = 0x20+File.read_uint32(0x68,_BE);
            sfx = 0x20+File.read_uint32(0x6C,_BE);
            x = 1;
            d1 = 0x20+File.read_uint32(0x4C,_BE);
            if(d1) {
                a1 = d1;
                while(1) {
                    a1 += 16;
                    d1 = File.read_uint32(a1,_BE);
                    //_log("read(0x20+a1:"+Hex(a1)+") = "+Hex(d1));
                    if(!d1) break;
                    d1 -= File.read_uint8(a1+3);
                    if(!d1) break; else x++
                }
            }
            sOptionT(title);
            if(x > 1) sOption(x,"×");
            sOptionT(auth,"by: ");
            sOptionT(cmt);
            sOption("smp:"+smp+" sfx:"+sfx)//+" sz:"+outSz(sz)+" a:"+sza+" smpsz:"+smpsz+" songsz:"+songsz+" loadsz:"+loadsz);
        }
    }
    else if(File.compare("'ALL '00")) {
        sName = "Delta Music module (.DM)"; bDetected = 1
    }
    else if(File.compare("670003B40C000001670001EC0C",6)
      && (File.findString(0x146,181,"DELTA MUSIC") > 0)) {
        sName = "Delta Music 2 module (.DM2)"; bDetected = 1
    }
    else if(File.compare("'DMF'0E")) {
        sName = "Digital Sound and Music Interface Advanced Music Format hack (.DMF)";
        sOption("delta samples & no text"); bDetected = 1
    }
    else if(File.compare("'DDMF'") && File.read_uint8(4) && File.read_uint8(4) <= 10
        && File.read_uint16(0x3F) && File.compare("'CMSG'", 0x42)) {
        //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_dmf.cpp
        sName = "X-Tracker module (.DMF)"; bDetected = true;
        nV = File.read_uint8(4); sVersion = "v"+nV;
        p = 0x42; hkhd = hksz = end = lpst = ch = ord = ptn = smp = sz = ptntotallen = 0; lped = 0xFFFF;
        while(hkhd != "ENDE" && p < File.getSize()) {
            hkhd = File.read_ansiString(p,4); hksz = File.read_uint32(p+4); p += 8;
//_log("@"+Hex(p-4)+" "+hkhd+" ["+Hex(p)+" -> "+Hex(p+hksz)+"]");
            switch(hkhd) {
            case "SEQU": ord = 0;
                if(nV == 3) p += 2; else if(nV == 4) p += 4;
                u = p;
                if(nV >= 3) { lpst = File.read_uint16(u); u += 2 }
                if(nV >= 4) { lpst = File.read_uint16(u); u += 2 }
                if(nV === 4 & !lped) lped = 0xFFFF;
                ord = (p+hksz-u)>>1; break;
            case "PATT":
                var ptnhdsz = nV < 3 ? 9 : 8; u = p;
                ptn = File.read_uint16(u); trk = File.read_uint8(u+2); u += 3;
                ch = trk; if(ch < 1) ch = 1; if(ch > 32) ch = 32;
                for(i = 0; i < ptn; i++) {
                    u += ptnhdsz-4;
                    var ptnlen = File.read_uint32(u); ptntotallen += ptnlen; u += ptnlen;
                }
                break;
            case "SMPI": u = p; smp = File.read_uint8(u++); var smptotallen = 0;
                for(i = 0; i < smp; i++) {
                    var snamelen = nV < 2 ? 30 : File.read_uint8(u++);
                    var sname = File.read_codePageString(u,snamelen,"CP437").trim(); u += snamelen;
                    var slen = File.read_uint32(u); smptotallen += slen;
//_log("slen @"+Hex(u)+" = "+Hex(slen));
                    u += 4+4+4+2;
                    var svol = File.read_uint8(u++), sf = File.read_uint8(u++);
//_log("smp["+i+"] "+sname+" length:"+Hex(slen)+" total:"+Hex(smptotallen)+(svol?"":" ignored"));
                    if(nV >= 8) { var libname = File.read_codePageString(u,8,"CP437"); u += 8 }
                    u += nV > 1 ? 6 : 2;
                }
                break;
            case "SMPD":
                if(!File.compare("'ENDE'",p+hksz)) {
                    // Simply search for it. It's unique enough.
                    // The multiplier of 3 is chosen for stereo + white noise "compression"
                    t = File.findSignature(p,Math.min(File.getSize()-p, smptotallen*3),"'ENDE'");
                    if(t < 0) { sVersion += "/malformed!noeof"; hkhd = "ENDE"; break }
                    else {
                        hksz = t-p;
//_log("SMPD length should be "+Hex(hksz));
                    }
                }
                break;
            case "ENDE": p -= 4+hksz; break;
            }
            sz = p;
            p += hksz;
        }
        if(hkhd != "ENDE") sVersion += "/malformed!short"; else sz = p;
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(0xD,30,"CP437"));
            sOptionT(File.read_codePageString(0x2B,20,"CP437"),"by: ");
            yy = File.read_uint8(0x41);
            if(yy < 80) yyyy = "20"; else yyyy = "19"; yyyy += yy.padStart(2,'0');
            sOptionT(yyyy+"-"+File.read_uint8(0x40).padStart(2,'0')+
                "-"+File.read_uint8(0x3F).padStart(2,'0'),"on: ");
            sOptionT(File.read_ansiString(5,8),"in: ");
            cmt = ""; cnt = File.read_uint32(0x46)-1; var linelen = 40, readpos = 0x4B;
            while(cnt) {
                var readlen = Math.min(cnt,linelen,0x200);
                var curline = File.read_codePageString(readpos,readlen,"CP437");
                if(curline != "") cmt = cmt.appendS(curline,'\n');
                cnt -= readlen; readpos += readlen;
            }
            sOption(addEllipsis(cmt,0x100,0x80));
            sOption("ch:"+ch+"+1 ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
        }
    }
    else if(File.compare("'.DelekDefleMask.'")) {
        //ref https://web.archive.org/web/20150226185703if_/http://www.delek.com.ar:80/soft/deflemask/DMF_SPECS.txt
        // It's a pretty damn old document though...
        sName = "DeFleMask module (.DMF)"; bDetected = true;
        sVersion = "v"+File.read_uint8(0x10);
        if(File.isVerbose()) {
            switch(File.read_uint8(0x11)) { //TODO add more new chip versions
            case 0x01: sVersion += "#YMU759"; ch = 17; break;
            case 0x02: sVersion += "#Genesis (10ch)"; ch = 10; break;
            case 0x03: sVersion += "#SMS (4ch)"; ch = 4; break;
            case 0x04: sVersion += "#GameBoy (4ch)"; ch = 4; break;
            case 0x05: sVersion += "#PCEngine (6ch)"; ch = 6; break;
            case 0x06: sVersion += "#NES (5ch)"; ch = 5; break;
            case 0x07: case 0x47: sVersion += "#C64 (3ch)"; ch = 3; break;
            case 0x08: sVersion += "#YM2151 (13ch)"; ch = 13; break;
            default: ch = 4 //just 'cause
            }
            sOption(File.read_ucsdString(0x12));
            l = File.read_uint8(0x12); p = 0x12+l+1;
            sOption(File.read_ucsdString(p),"by: ");
            l = File.read_uint8(p); p += l+1+2;
            tbase = File.read_uint8(p++);
            tick1 = File.read_uint8(p++); tick2 = File.read_uint8(p++);
            Hz = File.read_uint8(p++)?"60(NTSC)":"50(PAL)";
            if(File.read_uint8(p++)) Hz = File.read_uint8(p++)+":"+File.read_uint8(p++)+":"+File.read_uint8(p++);
            else p+=3;
            //These have changed over the years it seems, TODO: RE it
            //p++; _ripm = File.read_uint8(p++);
            //arptick = File.read_uint8(p++); p += ch*_ripm;
            //ins = File.read_uint8(p++);
            sOption("tbase:"+tbase+" tck:"+tick1+":"+tick2+" freq:"+Hz); //+" ins:"+ins+" arp.tick:");
        }
    }
    else if(File.compare("' MUGICIAN/SOFTEYES 1990 '")) {
        bDetected = true; //TODO change to prowizard's method for the tampered-with tracks
        sName = "Digital Mugician module (.DMU)";
        sVersion = "v1";
        if(File.isVerbose())
            for (var i = 0x50; i<0xC0; i+=0x10)
                sOptionT(File.read_ansiString(i,12))
    }
    else if(File.compare("' MUGICIAN2/SOFTEYES 1990'")) {
        sName = "Digital Mugician 2 module (.MUG)"; bDetected = true;
        sVersion = "v2";
        var c = 0;
        for (var i = 0x50; i<0xC0; i+=0x10) {
            t = File.read_ansiString(i,12).trim();
            if(t!="") {
                c++; sOption(t);
            }
        }
    }
    else if(File.compare("'DBRAWOPL'")) {
        sName = "DOSBox Raw OPL chiptune (.DRO)"; bDetected = 1
    }
    else if(File.compare("'DSM'10")) {
        sName = "Digital Audio Sound Interface Kit module (.DSM)"; bDetected = true;
        sOptionT(File.read_ansiString(4,32));
    }
    else if(File.compare("'RIFF'........'DSMFSONG'")) {
        sName = "Digital Sound Interface Kit module (.DSM)"; bDetected = true;
        sVersion = "RIFF";
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x14,32));
    }
    else if(File.compare("02011313 1412010B 0.")) {
        //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_dsym.cpp
        // & https://sourceforge.net/p/xmp/libxmp/ci/master/tree/src/loaders/sym_load.c
        sName = "Digital Symphony module (.DSYM)"; bDetected = true;
        sVersion = "v"+File.read_uint8(8);
        if(File.isVerbose()) {
            var infolenlo = File.read_uint16(0x0E,_LE); var infolenhi = File.read_uint8(0x10);
            infolen = (infolenhi<<16) + infolenlo;
            p = 0x11;
            for(i=0; i < 63; i++)
                if((File.read_uint8(p) & 0x80)) p++;
                else p += 3;
            sOption(File.read_ucsdString(p));
            sOption("ch:"+File.read_uint8(9)+" ord:"+File.read_uint16(0x0A,_LE)
              +" trk:"+File.read_uint16(0x0C,_LE));
        }
    }
    else if(File.compare("'DSm'1A20")) {
        sName = "Dynamic Studio Pro module (.DSM)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(5,20));
            sOptionT(File.read_ansiString(25,20),"by: ");
        }
    }
    else if(File.compare("02647373")) {
        sName = "Digital Speech Standard audio (.DSS)"; bDetected = true;
        sVersion = "v2";
    }
    else if(File.compare("03647373")) {
        sName = "Digital Speech Standard audio (.DSS)"; bDetected = true;
        sVersion = "v3";
    }
    else if(File.compare("'DSFmt1'0D0A")) {
        sName = "DreamStation module (.DSS)"; bDetected = true;
        sVersion = "v1.0";
        if(File.isVerbose()) {
            pt = File.findSignature(0,TOEOF,"F0E40001")+4; //the 00/01 at the end is "show info at track load"
            if(pt < 4) pt = File.findSignature(0,TOEOF,"F0E40000")+4;
            if(pt >= 4) {
                pa = File.findSignature(pt,TOEOF,"0D0A"); t = File.read_ansiString(pt,pa-pt); pa += 2;
                pc = File.findSignature(pa,TOEOF,"0D0A"); a = File.read_ansiString(pa,pc-pa); pc += 2;
                c = File.read_ansiString(pc,File.getSize()-pc);
                sOption(t); sOption(a,"by: "); sOption(c)
            }
        }
    }
    else if(File.compare("'DS2F0'....'Default'")) {
        sName = "DreamStation II module (.DS2)"; bDetected = true;
        sVersion = "v2";
        if(File.isVerbose()) {
            ts=File.read_uint8(0x62); t=File.read_ansiString(0x63,ts);
            pa=0x62+ts+1; as=File.read_uint8(pa); a=File.read_ansiString(pa+1,as);
            pc=pa+as+1;  cs=File.read_uint8(pc); c=File.read_ansiString(pc+1,cs);
            sOption(t);
            sOption(a,"by: ");
            sOption(c);
        }
    }
    else if(File.compare("'MMU2'00")) {
        sName = "Digital Sound Studio module (.DSS)"; bDetected = true;
        sVersion = "v1-3.0";
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(10,32));
            sOption("ord:"+File.read_int16(0x59C,_BE));
        }
    }
    else if(File.compare("'DTL'00")) {
        sName = "Drum Traker module (.DTL)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(4,20))
    }
    else if(File.compare("'D.T.'00")) {
        sv = ["S.Q.","VERS"].indexOf(File.read_ansiString(0x2A,4));
        if(sv >= 0) {
            sName = "Digital Tracker module (.DTM)"; bDetected = true;
            if(File.isVerbose()) {
                if(sv==0) sOptionT(File.read_ansiString(0x16,20))
                //let's play along with Bummtschak here
                else sOptionT(File.read_ansiString(0x16,24))
            }
        }
    }
    else if(File.compare("'DeFy DTM'")) {
        sName = "DeFy AdLib Tracker module (.DTM)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(9,3);
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0xD,20));
            sOptionT(File.read_ansiString(0x21,20));
        }
    }
    else if(File.compare("'SONG'........'NAME'")) {
        sName = "DigiTrekker module (.DTM)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x10,20));
    }
    else if(File.compare("48E7F1FE610000964CDF7F8F'NuH'E70010610000'$L'DF0800'NuH'E7F1FE610001844CDF7F8F'NuH'E70010610000'NL'DF0800'NuG'FAFFC651EB05',Q'EB05'PQ'EB05'tQ'EB059833FC000F00DFF09633FC00FF00DFF09E33FC000000DFF0A833FC000000DFF0B833FC000000DFF0C833FC000000DFF0D8'NuG'FAFF80177C00010524177C00010548177C0001056C177C00010590'NuG'FAFF'bJ+'00BA670E'S+'00B96608177C000600B9'Nu`'0003180006")) {
        sName = "David Whittaker's SFX module (.DW)"; bDetected = 1
    }
    else if(File.compare("' PWD'03") && File.compare("'Master'",0x0E)) {
        sName = "DarkWave Studio module (.DWP)"; bDetected = 1
    }
    else if(File.compare("'EASO'")) {
        sName = "EarAche module (.EAS)"; bDetected = true;
    }
    else if(File.compare("'FORM'.... ....'EMODEMIC'")) {
        sName = "Quadra Composer module (.EMOD)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x16,0x28));
            sOptionT(File.read_ansiString(0x44,0x18),"by: ");
        }
    }
    else if(File.compare("'E.M.S. V6.'")) {
        sName = "Electronic Music System module (.EMS)"; bDetected = true;
        sVersion = "v6."+File.read_ansiString(10,2)
    }
    else if(File.compare("'ZXAYEMUL'")) {
        // ref https://vgmrips.net/wiki/AY_File_Format
        sName = "AY Emul module (.EMUL)"; bDetected = true;
        sVersion = "Rel "+File.read_uint8(8)+" Plr "+File.read_uint8(9);
        if(File.isVerbose()) {
            psongst = 0x12 + File.read_uint16(0x12,_BE);
            title0 = File.read_ansiString(psongst + File.read_uint16(psongst,_BE),0x100);
            auth = File.read_ansiString(0x0C + File.read_uint16(0x0C,_BE),0x100);
            misc = File.read_ansiString(0x0E + File.read_uint16(0x0E,_BE),0x100);
            more = File.read_uint8(0x10);
            sOptionT(title0);
            if(more > 0) sOption(more+1,"x ");
            sOptionT(auth);
            sOptionT(misc);
        }aaa
    }
    else if(File.compare("'ENF '....'scor'")) {
        sName = "Extended Notation Format sheet music (.ENF)"; bDetected = true;
        p = 6; staf = brln = 0; hkhd = "dumm";
        while(p < File.getSize()) {
            hkhd = File.read_ansiString(p,4);
            if(!/[a-z\[\]\-0-9]{4}/.test(hkhd)) break;
            hksz = File.read_uint16(p+4,_BE);
            if(hkhd === "staf") staf++;
            else if(hkhd === "brln") brln++;
            p += hksz //this format includes signatures into the chunk size
        }
        sOption("staves:"+staf+" bars:"+brln+" sz:"+outSz(p))
    }
    else if((File.getSize()>0x24 && File.compare("'ETracker (C) BY ESI.'",10)) ||
      (File.getSize()>0x4D4 && File.compare("21B384") &&
      File.compare("'ETracker (C) BY ESI.'",File.read_uint16(1,_LE)-0x8000+10))) {
        //kudos to horsicq for helping narrow down the bad samples in modland files
        sName = "SAM Coupe E-Tracker file (.ETC)"; bDetected = true;
        if(File.compare("21B384")) sVersion = "&player";
    }
    else if((File.getSize() > 2048+6+6)
      && File.compare("00000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x3E1)
      && File.compare("......00 ......00 ......00 ......00 ......00 ......00 ......00",0x4D4)
        ) {
        sName = "Euphony module (.EUP)"; bDetected = true;
        if(File.isVerbose()) {
            sOption(File.read_codePageString(0,0x20,"CP932"))
        }
    }
    else if(File.compare("'FAR'FE") && File.compare("0D0A1A",0x2C)) {
        //ref https://web.archive.org/web/20151027135146/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Format%20Specifications%20(FAR,%20FSM,%20USM,%20F2R)%20by%20Daniel%20Potter.cp437.txt.utf-8.txt
        bDetected = true; nV = File.read_uint8(0x31);
        sName = "Farandole Composer module (.FAR)";
        sVersion = "v"+ (nV>>4) +"."+ (nV&0x0F);
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(4,0x28,"CP850"));
            var stlen = File.read_uint16(0x60,_LE);
            sOption(File.read_codePageString(0x62,stlen,"CP850").trim().slice(0,0x100).trim()+"...");
            sOption("ord:"+File.read_uint8(stlen+0x163)
                +" ptn:"+File.read_uint8(stlen+0x162)
                +" lp:"+File.read_uint8(stlen+0x164))
        }
    }
    else if(File.compare("'FPT'FE") && File.compare("0D0A1A",0x24)) {
        //ref https://web.archive.org/web/20151027135146/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Format%20Specifications%20(FAR,%20FSM,%20USM,%20F2R)%20by%20Daniel%20Potter.cp437.txt.utf-8.txt
        sName = "Farandole Composer pattern (.FPT)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_codePageString(4,0x20,"CP850"));
    }
    else if(File.compare("'FSM'FE") && File.compare("0A0D1A",0x24)) {
        //ref https://web.archive.org/web/20151027135146/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Format%20Specifications%20(FAR,%20FSM,%20USM,%20F2R)%20by%20Daniel%20Potter.cp437.txt.utf-8.txt
        sName = "Farandole Composer sample (.FSM)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(4,0x20,"CP850"));
            if(File.read_uint8(0x35)) type = "16bit"; else type = "8bit";
            if(File.read_uint8(0x36)&4) looped = " looped"; else looped = "";
            sVersion = type+looped;
            sOption("len:"+File.read_uint32(0x27,_LE));
        }
    }
    else if(File.compare("'FC14'") && (File.getSize() > 192)) {
        //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/FutureComposer14.c
        sName = "Future Composer module (.FC)"; bDetected = true;
        sVersion = "v1.4";
        if (File.getSize() < File.read_uint32(8,_BE)+File.read_uint32(0x10,_BE)
            +File.read_uint32(0x20,_BE)+File.read_uint32(0x24,_BE)) sVersion += "/malformed"
    }
    else if(File.compare("'FUCO'C1") && File.compare("'DIGI'",0x4404)) {
        sName = "Future Composer module (.BSI)"; bDetected = true;
        sVersion = "ver.BSI";
    }
    else if(File.compare("'SMOD'") && (File.getSize()>112)) {
        //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/FutureComposer13.c
        sName = "Future Composer module (.SMOD)"; sVersion = "v1.0~3"; bDetected = true;
        if((File.getSize() < File.read_uint32(8,_BE)) || (File.getSize() < File.read_uint32(0x10,_BE))
          || (File.getSize() < File.read_uint32(0x18,_BE)) || (File.getSize() < File.read_uint32(0x20,_BE)))
            sVersion += "/malformed"
    }
    else if(File.compare("'FMK!'")) {
        sName = "FM-Kingtracker module (.FMK)"; bDetected = true;
        if(File.isVerbose()) {
            text = true; for(i=0; i < 0x38; i++) if(0x20 > File.read_uint8(i+4)) { text = false; break }
            if(text) {
                sOptionT(File.read_ansiString(0x04,0x1C));
                sOptionT(File.read_ansiString(0x20,0x1C),"by: ");
            }
        }
    }
    else if(File.compare("'FMTracker'....'The FM Tracker!'")) {
        sName = "FM Tracker module (.FMT)"; bDetected = true;
        sVersion = "v"+File.read_uint8(9)+"."+File.read_uint8(10);
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x1F,0x20));
    }
    else if(File.compare("'FMTRK'1A")) {
        sName = "FM Tracker (Adlib) module (.FMT)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x10,0x20));
    }
    else if(File.compare("'Funk'") && File.getSize() >= 0x8FF) {
        //ref https://sourceforge.net/p/xmp/libxmp/ci/master/tree/docs/formats/FORMAT.FunktrackerGOLD
        sName = "Funktracker module (.FNK,.Funk)"; bDetected = true;
        sV = File.read_ansiString(0x0C,4); t = File.read_uint32(4,_LE);
        if(/F2\d\d/.test(sV))
            sVersion = "R2 GOLD "+(1980+((t>>9)&0x7F))+"-"
              +((t>>5)&0xF).padStart(2,'0')+"-"+(t&0x1F).padStart(2,'0')+" ";
        else sVersion = "R1";
        if(/F[2vk]\d\d/.test(sV)) ch = File.read_ansiString(0x0E,2);
        else sVersion += "b";
        switch((t>>20)&0xF) {
        case 1: case 2: t = "IBM"; break; case 3: t = "Intel 386"; break;
        case 4: t = "Intel 486"; break; case 5: t = "Pentium"; break; case 6: t = "Linux"; break;
        case 7: t = "FreeBSD"; break; case 8: t = "N/A"; break; default: t = "unk.system" }
        sVersion += "#"+t;
        switch((t>>16)&0xF) {
        case 0: t = "SB 2.0"; break; case 1: t = "SB Pro"; break;
        case 2: t = "GUS+ch.pan"; break; case 3: t = "SB compatible"; break;
        case 4: t = "SB 16"; break; case 5: t = "GUS"; break; case 6: t = "conversion"; break;
        case 7: t = "Pro Audio Spectrum"; break; case 8: t = "Voxware /dev/dsp 8 bit"; break;
        case 9: t = "Voxware /dev/dsp 16 bit"; break; case 15: t = "unk.soundcard"; break;
        default: t = "soundcard N/A" }
        sVersion += ":"+t;
        sz = File.read_uint32(8,_LE); bad = "";
        if(File.getSize() < sz) bad += "!short";
        ord = 0; ptn = -1; for(i = 0; i < 256; i++) {
            t = File.read_uint8(0x11+i); if(t == 0xFF) break; ord++;
            if(t > 0x79)
                if(File.isHeuristicScan()) { bad = bad.addIfNone("!badord") }
                else { bDetected = false; break }
            else if(t > ptn) ptn = t;
        } ptn++;
        t = File.read_uint8(0x10);
        if(t == 0xFF) looped = " non-looped";
        else looped = " looped@"+t;
        if(t > ord) if(File.isHeuristicScan()) bad += "!badloop"; else bDetected = false;
        if(bDetected && File.isVerbose()) {
            bpm = t>>24; bits = bpm&1 ? 16 : 8; bpm >>= 1; var sine = bpm >> 6;
            bpm &= (1<<6)-1; if(sine) bpm = 125-bpm; else bpm = 125+bpm;
            smp = 0; for(i = 0x1A8; i < 0x8BF; i += 0x20) if(File.read_uint32(i,_LE)) smp++; //length
            sOptionT(File.read_ansiString(0x191,0x13));
            sOption("ch:"+ch+" "+bits+"bit ord:"+ord+" ptn:"+ptn+" smp:"+smp+" bpm:"+bpm+looped+" sz:"+outSz(sz));
        }
        if(bad != "") sVersion += "/malformed"+bad
    }
    else if(File.compare("000003F3") && File.read_uint8(0x14)
      && File.compare("70FF4E75'F.PLAYER'",0x20) && File.read_int32(0x40,_BE)) {
        sName = "Future Player module (.FP)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(File.read_int32(0x2C,_BE)+0x20,0x100));
            x = 1; p = 0x48;
            do { t = File.read_uint32(p,_BE); if(t) x++; p += 8 } while(t);
            if(x > 1) sOption(x,"×");
            sOptionT(File.read_ansiString(File.read_int32(0x30,_BE)+0x20,0x100),"by:");
            sOptionT(outSz(File.read_int32(0x38,_BE)+0x20),"sz:");
        }
    }
    else if(File.compare("'Module: '") && File.compare("';Fast Tracker v1.00'", 0x32)) {
        sName = "Fast Tracker module (.FTС)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(8,0x2A));
    }
    else if(File.compare("'FTMN'03")) {
        sName = "Face the Music module (.FTM)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(0x10,0x20,"CP1250"));
            sOptionT(File.read_codePageString(0x30,0x20,"CP1250"))
        }
    }
    else if(File.compare("'FMS!'..000000")) {
        sName = "FamiStudio module (.FMS)"; bDetected = true;
        sVersion = "v"+File.read_uint8(4)
    }
    else if(File.compare("'FamiTracker Module'")) {
        sName = "FamiTracker module (.FTM)"; bDetected = true;
        if(File.isVerbose()) {
            itag = File.findString(0x14,0x100,"INFO\x00");
            sOptionT(File.read_ansiString(itag+0x18,0x20));
            sOptionT(File.read_ansiString(itag+0x38,0x20),"by: ");
            sOptionT(File.read_ansiString(itag+0x58,0x20))
        }
    }
    else if(File.compare("'-Furnace '")) {
        // from https://github.com/tildearrow/furnace/blob/master/papers/format.md
        switch(File.read_ansiString(9,7)) {
            case 'module-':
                cV = 'm'; bDetected = true; sName = "Furnace Tracker module (.FUR)"; break;
            case 'instr.-':
                cV = 'i'; bDetected = true; sName = "Furnace Tracker instrument (.FUI)"; break;
            case 'waveta-':
                cV = 'w'; bDetected = true; sName = "Furnace Tracker wavetable (.FUW)"; break
            default:
                cv = "?"; bDetected = true; sName = "unknown Furnace Tracker file"
        }
        nV = File.read_uint16(0x10,_LE);
        //ref https://github.com/tildearrow/furnace/blob/master/papers/format.md
        //they update it every 0.1 femtosecond, why not help out since you're reading this?
        if(nV < 12) sVersion = "["+nV+"]"; else
        if(nV <= 14) sVersion = "v0.2.X"; else
        if(nV <= 16) sVersion = "v0.3.X"; else
        if(nV <= 27) sVersion = "v0.4.X"; else
        if(nV < 35) sVersion = "["+nV+"]"; else
        if(nV <= 54) sVersion = "v0.5.X"; else
        if(nV < 57) sVersion = "["+nV+"]"; else
        if(nV == 75) sVersion = "v.dev75/April Fools' 0.6pre0"; else
        if(nV <= 99) sVersion = "v.dev"+nV; else
        if(nV == 100) sVersion = "v0.6pre1"; else
        if(nV == 101) sVersion = "v0.6pre1 (dev101)"; else
        if(nV == 102) sVersion = "v0.6pre1 (dev102)"; else
        if(nV <= 115) sVersion = "v.dev"+nV; else
        if(nV == 116) sVersion = "v.0.6pre1.5"; else
        if(nV <= 131) sVersion = "v.dev"+nV; else
        if(nV == 132) sVersion = "v.0.6pre2"; else
        if(nV == 133) sVersion = "v.0.6pre3"; else
        if(nV <= 140) sVersion = "v.dev"+nV; else
        if(nV == 141) sVersion = "Tournament Edition"; else
        if(nV == 142) sVersion = "v.dev"+nV; else
        if(nV == 143) sVersion = "v.0.6pre4"; else
        if(nV <= 145) sVersion = "v.dev"+nV; else
        if(nV == 146) sVersion = "v.Pro (joke edition)"; else
        if(nV <= 157) sVersion = "v.dev"+nV; else
        if(nV == 158) sVersion = "v.0.6pre5"; else
        if(nV <= 160) sVersion = "v.dev"+nV; else
        if(nV == 161) sVersion = "v.0.6pre6"; else
        if(nV == 162) sVersion = "v.0.6pre7";
        else sVersion = "["+nV+"]";
        if(cV === 'i') {
            p = File.read_uint32(0x14,_LE);
            if(p>0 && File.compare("'INST'",p)) {
                ity = File.read_uint16(0x10,_LE);
                switch(ity) {
                    case 0: sVersion += "/std"; break;
                    case 1: sVersion += "/FM (OPM/OPN)"; break;
                    case 2: sVersion += "/Game Boy"; break;
                    case 3: sVersion += "/C64"; break;
                    case 4: sVersion += "/Amiga|smp"; break;
                    case 5: sVersion += "/PC Engine"; break;
                    case 6: sVersion += "/AY-3-8910"; break;
                    case 7: sVersion += "/AY8930"; break;
                    case 8: sVersion += "/TIA"; break;
                    case 9: sVersion += "/SAA1099"; break;
                    case 10: sVersion += "/VIC"; break;
                    case 11: sVersion += "/PET"; break;
                    case 12: sVersion += "/VRC6"; break;
                    case 13: sVersion += "/OPLL"; break;
                    case 14: sVersion += "/OPL"; break;
                    case 15: sVersion += "/FDS"; break;
                    case 16: sVersion += "/Virtual Boy"; break;
                    case 17: sVersion += "/Namco 163"; break;
                    case 18: sVersion += "/SCC"; break;
                    case 19: sVersion += "/OPZ"; break;
                    case 20: sVersion += "/POKEY"; break;
                    case 21: sVersion += "/PC Speaker"; break;
                    case 22: sVersion += "/WonderSwan"; break;
                    case 23: sVersion += "/Lynx"; break;
                    case 24: sVersion += "/VERA"; break;
                    case 25: sVersion += "/X1-010"; break;
                    case 26: sVersion += "/VRC6(saw)"; break;
                    case 27: sVersion += "/ESS5506"; break;
                    case 28: sVersion += "/MultiPCM"; break;
                    case 29: sVersion += "/SNES"; break;
                    case 30: sVersion += "/Sound Unit"; break;
                    case 31: sVersion += "/Namco WSG"; break;
                    default: sVersion += "/unk"
                }
                if(File.isVerbose()) {
                    wvt = File.read_uint16(p+0x18,_LE);
                    smp = File.read_uint16(p+0x1A,_LE);
                    sOptionT(File.read_codePageString(p+0x0C,0x200,"UTF8"));
                    sOption(" wvt:"+wvt+" smp:"+smp);
                }
            } //verbose inst
        } //inst
        if(cV === 'm' && File.isVerbose()) {
            p = File.read_uint32(0x14,_LE);
            if(p>0 && File.compare("'INFO'",p)) {
                t = File.read_codePageString(p+0x100,0x200,"UTF8");
                p1 = File.findSignature(p+0x100,0x200,"00")+1;
                a = File.read_codePageString(p1,0x200,"UTF8");
                p1 = File.findSignature(p1,0x200,"00")+1;
                ins = File.read_uint16(p+0x16,_LE);
                wvt = File.read_uint16(p+0x18,_LE);
                smp = File.read_uint16(p+0x1A,_LE);
                ptng = File.read_uint32(p+0x1C,_LE);
                freq = File.read_float(p+0x0C,_LE);
                A4freq = File.read_float(p1,_LE);
                s1spd = File.read_uint8(p+9)+"/"+File.read_uint8(p+0x0A);
                s1ptn = File.read_uint16(p+0x10);
                s1ord = File.read_uint16(p+0x12);
                sOptionT(t);
                sOptionT(a,"by: ");
                chips = []; el = false; i = 0;
                while(!el && (i<32)) {
                    switch(File.read_uint8(p+0x20+i)) {
                    case 0: el = true; break;
                    case 0x01: chips[i] = "YMU759 (17ch)"; break;
                    case 0x02: chips[i] = "Genesis (10ch comp.)"; break;
                    case 0x03: chips[i] = "SMS (SN76489) (4ch)"; break;
                    case 0x04: chips[i] = "Game Boy (4ch)"; break;
                    case 0x05: chips[i] = "PC Engine (6ch)"; break;
                    case 0x06: chips[i] = "NES (5ch)"; break;
                    case 0x07: chips[i] = "C64 (8580) (3ch)"; break;
                    case 0x08: chips[i] = "Arcade (YM2151+SegaPCM) (13ch)"; break;
                    case 0x09: chips[i] = "Neo Geo CD (YM2610) (13ch)"; break;
                    case 0x42: chips[i] = "Genesis extended (13ch)"; break;
                    case 0x43: chips[i] = "SMS (SN76489) + OPLL (YM2413) (13ch comp.)"; break;
                    case 0x46: chips[i] = "NES+VRC7 (11ch)"; break;
                    case 0x47: chips[i] = "C64 (6581) (3ch)"; break;
                    case 0x49: chips[i] = "Neo Geo CD extended (16ch)"; break;
                    case 0x80: chips[i] = "AY-3-8910 (3ch)"; break;
                    case 0x81: chips[i] = "Amiga (4ch)"; break;
                    case 0x82: chips[i] = "YM2151 (8ch)"; break;
                    case 0x83: chips[i] = "YM2612 (6ch)"; break;
                    case 0x84: chips[i] = "TIA (2ch)"; break;
                    case 0x85: chips[i] = "VIC-20 (4ch)"; break;
                    case 0x86: chips[i] = "PET (1ch)"; break;
                    case 0x87: chips[i] = "SNES (8ch)"; break;
                    case 0x88: chips[i] = "VRC6 (3ch)"; break;
                    case 0x89: chips[i] = "OPLL (YM2413) (9ch)"; break;
                    case 0x8A: chips[i] = "FDS (1ch)"; break;
                    case 0x8B: chips[i] = "MMC5 (3ch)"; break;
                    case 0x8C: chips[i] = "Namco 163 (8ch)"; break;
                    case 0x8D: chips[i] = "YM2203 (6ch)"; break;
                    case 0x8E: chips[i] = "YM2608 (16ch)"; break;
                    case 0x8F: chips[i] = "OPL (YM3526) (9ch)"; break;
                    case 0x90: chips[i] = "OPL2 (YM3812) (9ch)"; break;
                    case 0x91: chips[i] = "OPL3 (YMF262) (18ch)"; break;
                    case 0x92: chips[i] = "MultiPCM (28ch)"; break;
                    case 0x93: chips[i] = "Intel 8253 (beeper) (1ch)"; break;
                    case 0x94: chips[i] = "POKEY (4ch)"; break;
                    case 0x95: chips[i] = "RF5C68 (8ch)"; break;
                    case 0x96: chips[i] = "WonderSwan (4ch)"; break;
                    case 0x97: chips[i] = "Philips SAA1099 (6ch)"; break;
                    case 0x98: chips[i] = "OPZ (YM2414) (8ch)"; break;
                    case 0x99: chips[i] = "Pokémon Mini (1ch)"; break;
                    case 0x9A: chips[i] = "AY8930 (3ch)"; break;
                    case 0x9B: chips[i] = "SegaPCM (16ch)"; break;
                    case 0x9C: chips[i] = "Virtual Boy (6ch)"; break;
                    case 0x9D: chips[i] = "VRC7 (6ch)"; break;
                    case 0x9E: chips[i] = "YM2610B (16ch)"; break;
                    case 0x9F: chips[i] = "ZX Spectrum (beeper) (6ch)"; break;
                    case 0xA0: chips[i] = "YM2612 extended (9ch)"; break;
                    case 0xA1: chips[i] = "Konami SCC (5ch)"; break;
                    case 0xA2: chips[i] = "OPL drums (YM3526) (11ch)"; break;
                    case 0xA3: chips[i] = "OPL2 drums (YM3812) (11ch)"; break;
                    case 0xA4: chips[i] = "OPL3 drums (YMF262) (20ch)"; break;
                    case 0xA5: chips[i] = "Neo Geo (YM2610) (14ch)"; break;
                    case 0xA6: chips[i] = "Neo Geo extended (YM2610) (17ch)"; break;
                    case 0xA7: chips[i] = "OPLL drums (YM2413) (11ch)"; break;
                    case 0xA8: chips[i] = "Atari Lynx (4ch)"; break;
                    case 0xA9: chips[i] = "SegaPCM (DefleMask compat.) (5ch)"; break;
                    case 0xAA: chips[i] = "MSM6295 (4ch)"; break;
                    case 0xAB: chips[i] = "MSM6258 (1ch)"; break;
                    case 0xAC: chips[i] = "Commander X16 (VERA) (17ch)"; break;
                    case 0xAD: chips[i] = "Bubble System WSG (2ch)"; break;
                    case 0xAE: chips[i] = "OPL4 (YMF278B) (42ch)"; break;
                    case 0xAF: chips[i] = "OPL4 drums (YMF278B) (44ch)"; break;
                    case 0xB0: chips[i] = "Seta/Allumer X1-010 (16ch)"; break;
                    case 0xB1: chips[i] = "Ensoniq ES5506 (32ch)"; break;
                    case 0xB2: chips[i] = "Yamaha Y8950 (10ch)"; break;
                    case 0xB3: chips[i] = "Yamaha Y8950 drums (12ch)"; break;
                    case 0xB4: chips[i] = "Konami SCC+ (5ch)"; break;
                    case 0xB5: chips[i] = "tildearrow Sound Unit (8ch)"; break;
                    case 0xB6: chips[i] = "YM2203 extended (9ch)"; break;
                    case 0xB7: chips[i] = "YM2608 extended (19ch)"; break;
                    case 0xB8: chips[i] = "YMZ280B (8ch)"; break;
                    case 0xB9: chips[i] = "Namco WSG (3ch)"; break;
                    case 0xBA: chips[i] = "Namco 15xx (8ch)"; break;
                    case 0xBB: chips[i] = "Namco CUS30 (8ch)"; break;
                    case 0xBC: chips[i] = "MSM5232 (8ch)"; break;
                    case 0xBD: chips[i] = "YM2612 extra features extended (11ch)"; break;
                    case 0xBE: chips[i] = "YM2612 extra features (7ch)"; break;
                    case 0xBF: chips[i] = "T6W28 (4ch)"; break;
                    case 0xC0: chips[i] = "PCM DAC (1ch)"; break;
                    case 0xC1: chips[i] = "YM2612 CSM (10ch)"; break;
                    case 0xC2: chips[i] = "Neo Geo CSM (YM2610) (18ch)"; break;
                    case 0xC3: chips[i] = "YM2203 CSM (10ch)"; break;
                    case 0xC4: chips[i] = "YM2608 CSM (20ch)"; break;
                    case 0xC5: chips[i] = "YM2610B CSM (20ch)"; break;
                    case 0xC6: chips[i] = "K007232 (2ch)"; break;
                    case 0xC7: chips[i] = "GA20 (4ch)"; break;
                    case 0xDE: chips[i] = "YM2610B extended (19ch)"; break;
                    case 0xE0: chips[i] = "QSound (19ch)"; break;
                    case 0xFC: chips[i] = "Pong (1ch)"; break;
                    case 0xFD: chips[i] = "Dummy System (8ch)"; break;
                    case 0xFE: case 0xFF: chips[i] = "reserved for development"; break;
                    default: chips[i] = "unk.";
                    }
                    if(!el) i++;
                }
                sOption("chips: "+chips.join("+"));
                sOption("ticks="+freq+"Hz, A4="+A4freq+"Hz");
                sOption("ins:"+ins+" wvt:"+wvt+" smp:"+smp+" glob.ptn:"+ptng);
                sOption("1st song's spd:"+s1spd+" ptn:"+s1ptn+" ord:"+s1ord);
            } //verbose mod
        } //mod
    }
    else if(File.compare("'FWMP'00")) {
        sName = "Forgotten Worlds BGM module (.FW)"; bDetected = true;
    }
    else if(File.compare("'FXSM'")) {
        sName = "Fuxoft AY Language module (.FXM)"; bDetected = true;
    }
    else if(File.compare("'GBRF'")) {
        sName = "Gameboy Ripped Format module (.GBR)"; bDetected = true;
        t = File.read_ansiString(0x154,0x13);
        if(t != "") if(/^([a-zA-Z0-9_ -]{4,})/.test(t)) sOptions = sOptions.append(t);
    }
    else if(File.compare("'GBS'01")) {
        bDetected = true;
        sName = "Gameboy Sound System module (.GBS)";
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x10,0x20));
        tc = File.read_uint8(4); if(tc > 0) sOption("x "+tc);
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x30,0x20),"by: ");
            sOptionT(File.read_ansiString(0x50,0x20));
        }
    }
    else if(File.compare("'GDM'FE") && File.compare("0D0A1A'GMFS'",0x44)) {
        //ref http://modland.com/pub/documents/format_documentation/General%20Digital%20Music%20(.gdm).txt
        sName = "General Digital Music module (.GDM)"; bDetected = true;
        trkr = "";
        if(File.read_uint16(0x4D,_BE)==0) trkr = "2gdm";
        sVersion = "v"+ File.read_uint8(0x4B) +"."+ File.read_uint8(0x4C);
        if(trkr != "") sVersion += "/"+trkr+" v."+File.read_uint8(0x4F)+"."+File.read_uint8(0x50);
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x04,0x20));
            sOptionT(File.read_ansiString(0x24,0x20),"by: ");
            tof = File.read_uint16(0x74,_BE);
            switch(tof) {
                case 1: sOptionT("orig: MOD"); break;
                case 2: sOptionT("orig: MTM"); break;
                case 3: sOptionT("orig: S3M"); break;
                case 4: sOptionT("orig: 669"); break;
                case 5: sOptionT("orig: FAR"); break;
                case 6: sOptionT("orig: ULT"); break;
                case 7: sOptionT("orig: STM"); break;
                case 8: sOptionT("orig: MED")
            }
            sOptionT(File.read_ansiString(File.read_uint32(0x8A,_BE),File.read_uint32(0x8D,_BE)))
        }
    }
    else if(File.compare("'GLUE'B8B3AABA")) {
        //TODO reverse & find calcsize
        sName = "GlueMon module (.GLUE)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(8,8));
            ins = File.read_uint8(0x7F);
            ord = File.read_uint8(0x9E); ptn = 0;
            for(i=0; i < ord; i++) { t = File.read_uint8(0x9F+i); if(t != 0xFF && ptn < t) ptn = t }
            ptn++;
            sOption("ord:"+ord+" ptn:"+ptn+" ins:"+ins)
        }
    }
    else if(File.compare("'NuFREDGRAY'",0x22)) {
        sName = "Fred Gray's module (.GRAY)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_ansiString(0x50,0x100); pn = 0x50+t.length+1;
            a = File.read_ansiString(pn,0x100); pn += a.length+1;
            c = File.read_ansiString(pn,0x100);
            sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
        }
    }
    else if(File.compare("'+SNT'")) {
        bDetected = true;
        sName = "Beaver Sweeper module (.GTK)";
    }
    else if( /GT[2K]/.test(File.read_ansiString(0,3)) ) {
        //GTK from http://ldesoras.free.fr/src/gt2/dev_gtk.zip / formats.txt
        //GT2 from mod_gt2.cpp
        //+ input from the author Laurent "Dumbo" de Soras (http://ldesoras.free.fr)
        bDetected = true;
        sigv = 0;
        switch(File.read_uint8(2)){
            case 0x4B: sName = "Graoumf Tracker module (.GTK)"; sVersion = "Amiga "; sigv = 1; break;
            case 0x32: sName = "Graoumf Tracker 2 module (.GT2)"; sVersion = "PC " ; sigv = 2;
        }
        v = File.read_uint8(3); sVersion += "v"+v;
        if(v < 6) switch(v){
            case 1: sVersion += "/GT v0.7"; break;
            case 2: sVersion += "/GT v0.726"; break;
            case 3: sVersion += "/GT v0.731";break;
            //lost in time
        }
        else {
             if(v == 9) sVersion+="/GT r27";
        }

        if(File.isVerbose()) {
            hksz = File.read_uint32(4,_BE); pn=hksz;
        }
        if(File.isVerbose()) {
            if(sigv == 1) { //v1
                t = File.read_ansiString(4,0x20).trim(); d = xc = "";
                c = File.read_ansiString(0x24,0xA0).trim(); //the author says he never implemented this
                ins = File.read_uint16(0xC4,_BE); lpp = File.read_uint16(0xC6,_BE);
                songlen = File.read_uint16(0xC8,_BE);
                statln = "ins:"+File.read_uint16(0xC4,_BE)+" lpp:"+File.read_uint16(0xC6,_BE)+
                  " len:"+Hex(File.read_uint16(0xCA,_BE))+" trk:"+File.read_uint16(0xC8,_BE);
            }
            else { //v2
                t = File.read_ansiString(8,0x20).trim();
                d = File.read_uint16(0xCA,_BE)+"-"+File.read_uint8(0xC9)+"-"+File.read_uint8(0xC8);
                sVersion += "/"+File.read_ansiString(0xCC,0x18).trim();
                c = File.read_ansiString(0x28,0xA0).trim();
                if(v < 6) statln = "spd:"+File.read_uint16(0xE4,_BE)+" bpm:"+File.read_uint16(0xE6,_BE);
                else statln = "";
                xc = [];
                for(i=0; i < 10;i++) {
                    //traverse some hunks... for no good reason, the author never implemented XCOM
                    //but forks exist
                    if(pn > File.getSize()) break;
                    hkhd = File.read_ansiString(pn,4); hksz = File.read_uint32(pn+4,_BE);
                    //statln+=" "+hkhd;
                    switch (hkhd) {
                      case "XCOM": xcc = File.read_uint16(pn+8,_BE);
                        xc = xc.push(File.read_ansiString(pn+10,xcc).trim());
                        break;
                    }
                    pn+=hksz;
                }
            }
            sOption(t); sOption(d); sOption(c); sOption(statln);
            if(xc.length) sOption(xc,join("\n"));
        }
    }
    else if(File.compare("0.'GTR'")) {
        sName = "Global Tracker (.GTR)"; bDetected = true;
        sV = Hex(File.read_uint8(4)); sVersion = "v"+sV[0]+"."+sV[1];
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(7,0x20));
            sOption(File.read_uint8(0),"delay:");
        }
    }
    else if(File.compare("'GYMX'")) {
        sName = "Sega Genesis/Mega Drive YM2612 chiptune (.GYM)"; bDetected = true;
        e = File.read_ansiString(0x64,0x20).trim();
        if(e != "") sVersion = e; sVersion.append("unpacked");
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x4,0x20));
            sOptionT(File.read_ansiString(0x24,0x20),"for: ");
            sOptionT(File.read_ansiString(0x44,0x20),"at: ");
            sOptionT(File.read_ansiString(0x84,0x20),"by: ");
            sOptionT(File.read_ansiString(0xA4,0x100));
        }
    }
    else if(File.compare("'NuH.DAVIES'",0x22)) {
        sName = "Howie Davies's module (.HD)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_ansiString(0x64,0x100); pn = 0x64+t.length+1;
            a = File.read_ansiString(pn,0x100); pn += a.length+1;
            c = File.read_ansiString(pn,0x100);
            sOptionT(t);
            sOptionT(a,"by: ");
            sOptionT(c);
        }
    }
    else if(File.compare("'HESM'") && File.compare("'DATA'",0x10)) {
        sName = "HES module (.hes)"; bDetected = true;
        sVersion = "v"+File.read_uint8(4);
        /* TODO get what's going on! Most files are good but some just have codeseg up there...
        if(File.isVerbose()) {
            t = ""; a = ""; c = ""; pn=0x40; dataat = File.read_uint16(6,_BE);
            if(pn<dataat && 31<File.read_uint8(pn)<128) t = File.read_ansiString(pn,0x100);
            if(t.length+1>0x1F) pn=Math.round((pn+t.length)/0x10)*0x10;
            else pn += 0x20;
            if(pn<dataat && 31<File.read_uint8(pn)<128) a = File.read_ansiString(pn,0x100);
            if(a.length+1>0x1F) pn=Math.round((pn+a.length)/0x10)*0x10;
            else pn += 0x20;
            if(pn<dataat && 31<File.read_uint8(pn)<128) c = File.read_ansiString(pn,0x100);

            sOptionT(t); sOptionT(a,"by: "); OptionT(c);
        }
        */
    }
    else if(File.compare("'IM10'",0x3C) && File.read_uint8(0x30)
        && File.read_uint8(0x31) >= 0x20 && File.read_uint8(0x32) <= 0x40
        && File.read_uint8(0x33) >= 4 && File.read_uint8(0x33) < 0x80) {
        //ref https://sourceforge.net/p/xmp/libxmp/ci/master/tree/src/loaders/imf_load.c
        sName = "Imago Orpheus module (.IMF)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0,0x20));
            sOption("tempo0:"+File.read_uint8(0x30)+" bpm0:"+File.read_uint8(0x31)
                +" ord:"+File.read_uint16(0x20,_LE)+" ptn:"+File.read_uint16(0x22,_LE)
                +" ins:"+File.read_uint16(0x24,_LE));
        }
    }
    else if(File.compare("'GTI5'")) {
        sName = "GoatTracker 2 Instrument (.INS)"; bDetected = 1
    }
    else if(File.compare("'ISM!V1.2'")) {
        sName = "Sound Invasion Music System/In Stereo! module (.IS)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(5,3);
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x24,25))
    }
    else if(File.compare("'IS20DF10STBL'")) {
        sName = "Sound Invasion Music System/In Stereo! module (.IS20)"; bDetected = true;
        sVersion = "v2.0";
    }
    else if(File.compare("'IXS!'")) {
        sName = "Ixalance module (.IXS)"; bDetected = true;
        sVersion = "v1.2";
        if(File.isVerbose())
          if(File.read_uint8(0x17) == 0x40)
            sOptionT(File.read_ansiString(0x18,0x20))
    }
    else if(File.compare("'MUSE'DEADBEAF") || File.compare("'MUSE'DEADBABE")) {
        sName = "Jazz Jackrabbit 2 container (.J2B)/Galaxy Sound System module";
        bDetected = true;
        //TODO: confirm if zlib extraction available; if so, derive the module name too
    }
    else if(File.compare("'RIFF'........'AM  '")) {
        sName = "Galaxy Sound System module (.J2B)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x14,64))
    }
    else if(File.compare("'NuJ.FLOGEL'",0x22)) {
        sName = "Janko Mrsic-Flogel's module (.JMF)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_ansiString(0x54,0x100); pn = 0x54+t.length+1;
            a = File.read_ansiString(pn,0x100); pn += a.length+1;
            c = File.read_ansiString(pn,0x100);
            sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
        }
    }
    else if( File.compare("2B7C") && File.compare("2B7C",8)
      && File.compare("2B7C",0x10) && File.compare("2B7C",0x18)
      && File.compare("303C",0x20) ) {
        sName = "Jason Page's old module (.JPO)"; bDetected = 1
    }
    else if(File.compare("0239000100......6602'Nux'001839") && File.compare("660A13F9",0x22) ) {
        sName = "Jeroen Tel's module (.JT)"; bDetected = true;
    }
    else if(File.compare("0040....0040....0040....0040....0040....0040",0x19D1) ) {
        //unreliable for potential new modules (but there's not gonna be even one)
        sName = "JayTrax module (.JXS)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0xA6,0x40));
    }
    else if(File.compare("00000000000000FD..FFFE",0x04) && File.compare("F0F0F0F0F0F0F0",0x14)
      && File.compare("000000000000010101",0x25) && File.compare("'2222222'",0x44)) {
        sName = "Ken Silverman's Adlib module (.KSM)"; bDetected = 1
    }
    else if(File.compare("'cyd!song'") ) {
        //ref https://github.com/kometbomb/klystron/tree/master/src/snd/music.c , .h
        sName = "Klystrack module (.KT)"; bDetected = true;
        v = File.read_uint8(8); sVersion = "v"+v;
        if(File.isVerbose()) {
            pn = 9;
            if(v >= 6) trk = File.read_uint8(pn++); else if(v > 3) trk = 4; else trk = 3;
            timesig = File.read_uint8(pn++)+"/"+File.read_uint8(pn++);
            if(v >= 17) pn+=2; //seqstep;
            instc = File.read_uint8(pn++);  patc = File.read_uint16(pn,_LE); pn += 2;
            pn += trk*2; //seq cnt per track, ignore
            songlen = File.read_uint16(pn,_LE); pn += 2; /*loopat = File.read_uint16(pn,_LE);*/ pn += 2;
            if(v >= 12) pn++; //master_volume
            spd = File.read_uint8(pn++); spd2 = File.read_uint8(pn++);
            rate = File.read_uint8(pn++); //bpm?...
            if(v > 2) pn += 4; if(v >= 9) pn++; if(v >= 16) pn++; //flags, mulpd, ptcin

            tlen = 17; if(v >= 11) tlen = File.read_uint8(pn++);
            if(v >= 5) tlen = Math.min(tlen,65);
            sOptionT(File.read_ansiString(pn,tlen));
            sOption("trk:"+trk+" tsig:"+timesig+" bpm:"+rate+" ins:"+instc+
                " len:"+Hex(songlen)+" ptn:"+Hex(patc)+" spd:"+spd+"-"+spd2)
        }
    }
    else if(File.compare("'cyd!inst'") ) {
        sName = "Klystrack instrument (.KI)"; bDetected = 1
    }
    else if(File.compare("'cyd!efex'") ) {
        sName = "Klystrack effects (.KF?)"; bDetected = 1
    }
    else if(File.compare("'KSCC'") || File.compare("'KSSX'")) {
        sName = "KSS module (.KSS)"; bDetected = true;
        if(File.compare("'KSSX'")) sVersion = "extended";
        if(File.isVerbose())
          if(File.compare("'MBM'",0x20))
            sOptionT(File.read_ansiString(0x24,0x34))
    }
    else if( File.compare("'ADL '0000....'MDhd'00000008000080..00000080'MThd'00000006000200..01E0'MTrk'0000....00FF03")
      || ( File.read_uint32(0,_LE)==File.getSize() && File.compare("'AD'",4) ) ) {
        sName = "Lucas Arts Adlib chiptune (.LAA)"; bDetected = true;
        if(File.read_ansiString(0,4)==="ADL ") sVersion = "new"; else sVersion = "old"
    }
    else if( File.compare("'Liquid Module:'") ||
      ( (File.isHeuristicScan() || File.compare("'NO'"))
         && extIs("liq"))
      ||  File.compare("21",0x01D6) && File.compare("21",0x0AD2) && File.compare("21",0x0BE6)
      && File.compare("FF",0x0ED4) && File.compare("FF",0x0EE8) ) {
        sName = "Liquid Tracker module (.LIQ)"; bDetected = true;
        sVersion = File.read_ansiString(0x41,0x14).trim();
        if(File.isVerbose()) {
            if(File.read_ansiString(0,2) == "NO")
                sOptionT(File.read_ansiString(5,0x14))
            else if(File.read_ansiString(0,3) == "Liq") {
                sOptionT(File.read_ansiString(0x0E,0x32));
                sOptionT(File.read_ansiString(0x0E,0x0F),"by: ");
            }
        }
    }
    else if(File.compare("'LME'00'(c)1990 Leggless/Silents UK     ") ) {
        sName = "Leggless module (.LME)"; bDetected = 1
    }
    else if(File.compare("0C'Nusa-team 89a'10610A6108610678006000",0x169)
      && File.compare("'dynamite89'",0x234)) {
        sName = "Music Assembler module (.MA)"; bDetected = 1
    }
    else if(File.compare("'MAD+'")) {
        sName = "Mlat Adlib Tracker module (.MAD)"; bDetected = 1
    }
    else if(File.compare("'MADG'") ) {
        sName = "B. Birney's PlayerPro module (.MAD)"; bDetected = true;
        if(File.isVerbose())
            sOption(File.read_ansiString(4,0x12))
    }
    else if(File.compare("D040D0404EFB")) {
        sName = "Mark Cooksey's module (.MC)"; sVersion = "new"; bDetected = 1
    }
    else if(File.compare("'TFMX-SONG'")) {
        //TODO deal with the tags, because sometimes the field is "by blahblah",
        // and other times it's like a bitfield
        sName = "TFMX or TFMX ST module (.MDAT,.MDST)"; bDetected = 1
    }
    else if(File.compare("'DMDL'..'IN'")) {
        sName = "Digitrakker module (.MDL)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x0B,32));
            sOptionT(File.read_ansiString(0x2B,20),"by: ");
        }
    }
    else if(File.compare("'MMDC'")) { //before the more generic compare("'MMD'")
        sName = "Tony Crowther's packed MED module (.MMDC)"; bDetected = true;
        if(File.isVerbose()) sOption(outSz(File.read_uint32(4,_BE)),"sz:")
    }
    else if(File.compare("'GameBoy Music Module'",0x0C)) {
        sName = "Gameboy Tracker module (.MGB)"; bDetected = 1
    }
    else if(File.compare("'MGT'11BD'MCS'")) {
        sName = "Megatracker module (.MGT)"; bDetected = true;
        sOptionT(File.read_ansiString(0x3A,0x20))
    }
    else if(File.compare("'MThd'") && File.compare("'MTrk'",8+File.read_uint32(4,_BE))
      && File.read_uint16(8,_BE) <= 2) {
        //ref. spec. from midi.org: RP-001_v1-0_Standard_MIDI_Files_Specification_96-1-4.pdf
        sName = "Standard MIDI File (.MID)"; sVersion = "v1.0";
        nV = File.read_uint16(8,_BE); bad = ""; bDetected = true;
        switch(nV) {
        case 0: sVersion += " t.0:one track"; break;
        case 1: sVersion += " t.1:tracks"; break;
        case 2: sVersion += " t.2:tracks+tempo"
        }
        bDetected = true;
        trk = File.read_uint16(0x0A,_BE); if(trk > 1) sOption(trk,"trk:");
        if(nV == 0 && trk != 1) bad += "!badvertrk";
        sz = p = 0x0E; txt = by = title = lyr = "";
        for(i=0; i < trk; i++) {
            var readmore = 20; //mostly enough to catch the info messages; set to 0xFFFFFFFF for all of 'em
            if(!File.compare("'MTrk'",p) || (i && !File.compare("FF2F00",p-3))) { bad += "!badtrk"; break }
            len = File.read_uint32(p+4,_BE); p += 8; sz = p;
            while(readmore && p < sz+len && p < File.getSize()) {
                readmore--; dt = readVarUInt(p);
//logpart1="@"+Hex(p)+" on "+dt+": ";
                p += dt[0];
                switch(File.read_uint8(p++)) {
                case 0xF0: case 0xF7: t = readVarUInt(p); p += t[0]+t[1]; break;
                case 0xFF: a = File.read_uint8(p++);
//_log(logpart1+"["+readVarUInt(p)+"] meta event "+Hex(a));
                    switch(a) {
                        case 0: if(File.read_uint8(p++) != 2) readmore = 0; else p += 4; break;
                        case 3: t = readVarUInt(p); p += t[0];
                            title = title.append(File.read_codePageString(p,t[1],"CP1252")); p += t[1];
                            break;
                        case 2: t = readVarUInt(p); p += t[0];
                            by = by.append(File.read_codePageString(p,t[1],"CP1252")); p += t[1]; break;
                        case 1: t = readVarUInt(p); p += t[0];
                            txt = txt.append(File.read_codePageString(p,t[1],"CP1252")); p += t[1]; break;
                        case 5: t = readVarUInt(p); p += t[0];
                            lyr = lyr.append(File.read_codePageString(p,t[1],"CP1252","-")); p += t[1];
                            break;
                        case 4: case 6: case 7: t = readVarUInt(p); p += t[0]+t[1]; break; //ins., lyrics, markers, cues
                        case 0x20: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 1) {
                            readmore = 0; bad += "!badprefix@"+(p-t[0]-t[1]) } break
                        case 0x2F: t = readVarUInt(p); p += t[0]+t[1]; if(t[1])
                            bad += "!badEoTtag@"+(p-t[0]-t[1]); readmore = 0; break;
                        case 0x51: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 3) {
                            readmore = 0; bad += "!badtempo@"+(p-t[0]-t[1]) } break;
                        case 0x54: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 5) {
                            readmore = 0; bad += "!badSMPTE@"+(p-t[0]-t[1]) } break;
                        case 0x58: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] < 2 || t[1] > 4) {
                            readmore = 0; bad += "!badtime@"+(p-t[0]-t[1]) } break;
                        case 0x59: t = readVarUInt(p); p += t[0]+t[1]; if(t[1] != 2) {
                            readmore = 0; bad += "!badkey@"+(p-t[0]-t[1]) } break;
                        default: t = readVarUInt(p); p += t[0]+t[1]
                    } break;
                default: //p += readVarUInt(p)[0];
//_log(logpart1+"default...");
                }
                if((txt != "" && by != "" && title != "") || p-sz > 0x200) readmore = 0;
            }
            sz += len; p = sz; if(p>File.getSize()) { bad += "!short"; break }
        } //for..trk
        if(bad) sVersion += "/malformed"+bad;
        if(File.isVerbose()) {
            sOptionT(title); sOptionT(by,"by: "); sOptionT(txt);
            sOption(outSz(sz),"sz:")
        }
    } else if(File.compare("'SMF2CLIP'")) {
        //ref. spec. from midi.org: M2-104-UM, MA06
        sName = "MIDI Clip File (.midi2)"; sVersion = "v2.0"; bDetected = true;
    }
    else if(File.compare("'MKJamz'")) {
        sName = "MK-Jamz module (.MKJ)"; bDetected = 1
    }
    else if(File.compare("'MLEDMODL'000000")
      && File.compare("'VERS'",File.read_uint32(8,_BE)+12)) {
        sName = "Musicline Editor module (.ML)"; bDetected = true;
        m = File.read_uint8(File.read_uint32(8,_BE)+21);
        sVersion += ""+(m>>4)+(m&0x0F)
        p = 4; //header skipped
        t = ""; c = ""; x = 0; smp = 0; ins = 0; ptn = 0;
        while (p < File.getSize()) {
            hkhd = File.read_ansiString(p,4);
            hksz = File.read_uint32(p+4,_BE);
            p+=0x08; //chunk header = 4xfourcc + 4xsize
            switch (hkhd) {
            case "VERS":
                sVersion = "v"+File.read_uint8(p)+".";
                  m = File.read_uint8(p+1);
                  sVersion += ""+(m>>4)+(m&0x0F);
                  break;
            case "TUNE":
                x ++; t += File.read_codePageString(p,hksz,"CP1252")+"\n"; break;
            case "INFO": //TODO smpls have hksz off
                q = 0;
                while(q < hksz) {
                    z = File.findSignature(p+q,hksz-q,"00");
                    if(z > -1) l = z-p-q;
                    else l = hksz;
                    //_log("p="+Hex(p)+" z="+Hex(z)+" l="+l+" q="+q);
                    c += File.read_codePageString(p+q,l,"CP1252")+"\n";
                    q += l+1;
                } break;
            case "PART": ptn++; break
            case "INST": ins++; break
            case "SMPL":
                smp++; p += 6; break;
            }
            p += hksz;
        }
        if(File.isVerbose()) {
            if(x>1) sOption(x,"×");
            sOptionT(t); sOptionT(c);
            sOption("ptn:"+ptn+" ins:"+ins+" smp:"+smp);
        }
    }
    else if(File.compare("'FORM'........'MMV8SDAT'................'SE'")) {
        sName = "Music Maker EXT module (.MM8,.MM4)"; bDetected = true;
        sVersion = "v8";
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x1A,0x14));
            sOptionT(File.read_ansiString(0x36,0x29));
            sOption(outSz(File.read_int32(4,_BE)+8),"sz:")
        }
    }
    //TODO insert the SDATA format of Music Maker
    else if(File.compare("'SEI1XX'00")) { //TODO find more than 2 files to test on :)
        sName = "Music Maker STD instrument (.IP)"; bDetected = true;
        sVersion = "v8 old";
    }
    else if(File.compare("'SO31'",0x7C)) {
        sName = "MultiMedia Sound module (.MMS)"; bDetected = 1
    }
    else if(File.compare("'MO3'")) {
        sName = "MO3 MOD module (.MO3)"; bDetected = 1
    }
    else if(File.compare("'RASP'",0x438)) {
        // TODO find out what format it was...
        sName = "Generic module (.MOD)"; bDetected = true;
        sVersion = File.read_ansiString(0x438,4);
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(0,0x14,"IBM850"));
            sOptionT(File.read_codePageString(0x14,0x16,"IBM850"),"by/inst: ")
        }
    }
    else if(File.compare("000000000000'MTN'000000", 0x5B2)) {
        sName = "SoundTracker module (.MOD)"; sVersion = "v2.6"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0,0x14));
            sOptionT(File.read_ansiString(0x14,0x16),"by: ")
        }
    }
    else if(File.compare("08'MONOTONE'")) {
        //ref https://github.com/MobyGamer/MONOTONE/blob/master/MTSRC/MT_SONG.PAS
        sName = "MONOTONE module (.MON)"; bDetected = true;
        sVersion = "v"+File.read_uint8(0x5B);
        if(File.isVerbose()) {
            sOptionT(File.read_ucsdString(0x09));
            sOptionT(File.read_ucsdString(0x32));
            sOption("ptn:"+File.read_uint8(0x5C)+
                " trk:"+File.read_uint8(0x5D));
        }
    }
    else if(/SONG[0-9.]{4}(COMP|NORM)/.test(File.read_ansiString(0,12))) {
        sName = "Megastation track (.MS)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(4,5).toLowerCase()
    }
    else if(/SNGM[0-9.]{4}(COMP|NORM)/.test(File.read_ansiString(0,12))) {
        sName = "Megastation MOD module (.MSM)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(4,5).toLowerCase()
    }
    else if(File.compare("'MSOB'00")) {
        sName = "Medley module (.MSO)"; bDetected = 1
    }
    else if(File.compare("'mpu401tr'92'kk'EE'r'")) {
        sName = "MPU-401 Trakker Adlib module (.MTK)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_ucsdString(0x18); sOptionT(t);
            sOptionT(File.read_ansiString(0x18+t.length+2),"by: ")
        }
    }
    else if(File.compare("'MTM'")) {
        //ref https://github.com/libxmp/libxmp/blob/master/docs/formats/Mtm-form.txt
        sName = "Multitracker module (.MTM)"; bDetected = true;
        v=File.read_uint8(3); sVersion = "v"+(v>>4)+"."+(v & 0x0F);
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(4,20));
            trk = File.read_uint16(24,_LE); ptn = File.read_uint8(26);
            ord = File.read_uint8(27)+1; csz = File.read_uint16(28,_LE);
            nos = File.read_uint8(30); bpt = File.read_uint8(32);
            pxc = 194+nos*37+trk*192+(ptn+1)*32*2;
            c = File.read_ansiString(pxc,csz);
            if(c.length < csz) c = c.trim()+"<...>";
            sOptionT(c);
            sOption("trk:"+(trk+1)+" ord:"+(ord+1)+" ptn:"+(ptn+1)+" smp:"+nos)
        }
    }

    else if(File.compare("'MT20'") && File.getSize() >= 0x184 && File.read_uint8(9) == 2
      && File.read_uint16(0x70) < 64 && File.read_uint16(0x6A) <= 0x100) {
        //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mt2.cpp
        sName = "MadTracker 2 module (.MT2)"; nV = File.read_uint8(8); bDetected = true;
        sVersion = "v"+File.read_uint8(9)+"."+nV.toString(16).padStart(2,'0');
        bad = "";
        ord = File.read_uint16(0x6A); loop = File.read_uint16(0x6C);
        ptn = File.read_uint16(0x6E); trk = File.read_uint16(0x70);
        flags = File.read_uint32(0x76);
        ins = File.read_uint16(0x7A); smp = File.read_uint16(0x7C);
        hasdrums = File.read_uint16(0x17E) != 0; //:477
        if(hasdrums) dptn = File.read_uint16(0x180); else dptn = 0; // this one line's :789
        p = 0x184+(hasdrums?0x112:0); //180 = filehdr+hasdrums+orders
        addp = p+File.read_uint32(p-4); msg = ""; vst2 = 0; igskip = 0;
//_log("addp @"+Hex(addp));
        smpszs = []; insszs = []; inss = []; smps = []; extsmp = [];
        while(p < addp) { //:578
            hkhd = File.read_ansiString(p,4); hksz = File.read_uint32(p+4); p += 8;
            switch(hkhd) {
                case "MSG": showmsg = File.read_uint8(p);
                    msg = File.read_codePageString(p+1,hksz-1,"CP1252")
                      .replace("\r","\n").replace("\n\n","\n"); break;
                case "SUM": artist = File.read_codePageString(p+6,hksz-6,"CP1252");
                    if(artist == "Unregistered") artist == ""; break;
                case "VST2": vst2 = File.read_uint32(p); break;
            }
            p += hksz
        }
//_log("ptns @"+Hex(p));
        if(p > addp) bad = bad.addIfNone("!badaddsz");
        else if(p > File.getSize()) bad = bad.addIfNone("!short");
        else {
            for(i=0; i < ptn && p < File.getSize(); i++) p += 6+((File.read_uint32(p+2)+1)&~1)
//_log("drums? @"+Hex(p));
            if(hasdrums) { //:786
                for(i=0; i < dptn && p < File.getSize(); i++) { //:811
                    p += 2+File.read_uint16(p)*32 }
            }
//_log("atmt @"+Hex(p));
            if(flags & 2) { //automation; 8 is drum autn., 0x10 is master autn.
                env = trk + (flags & 8) + ((nV >= 0x50) ? vst2 : 0) + ((flags & 0x10) ? 1 : 0);
                for(q = 0; q < ptn; q++)
                    for(e = 0; e < env && p+4 <= File.getSize(); e++) {
                        if(nV >= 3) { fl = File.read_uint32(p); p += 8 }
                        else { fl = File.read_uint16(p); p += 4 }
                        while(fl) {
                            if(fl&1) p += 0x104; fl >>= 1;
                            if(fl<0)fl=-fl //fixes something suspiciously like a JS glitch
                        }
                    }
            }
        }
//_log("ins @"+Hex(p));
        if(p > File.getSize()) bad = bad.addIfNone("!short");
        else {
            for(i=0; i < 255; i++) {
                if(!msg && inss.length < 3) {
                    t = File.read_codePageString(p,0x20,"CP1252"); if(t.trim() != "") inss.push(t) }
                dtlen = File.read_uint32(p+0x20); if(dtlen == 0x20) dtlen += 0x18C;
                if(nV > 1 && dtlen) dtlen += 4;
                if(dtlen) igskip += File.read_uint16(p+0x24)<<3; p += 0x24+dtlen;
            }
        }
//_log("smp @"+Hex(p));
        if(p > File.getSize()) bad = bad.addIfNone("!short");
        else {
            for(i=0; i < 256; i++) {
                if(!msg) {
                    t = File.read_ansiString(p,0x20); if(i < smp && t.trim() != "") smps.push(t) }
                dtlen = File.read_uint32(p+32); p += 0x24;
                if(dtlen && i < smp) {
                    slen = File.read_uint32(p); sfl = File.read_uint8(p+10);
                    if(sfl&5) smpszs.push({"ext":1,"slen":0}); else if(slen) smpszs.push({"ext":0,"slen":slen});
                    p += dtlen;
                }
            }
            p += igskip;
            if(p > File.getSize()) bad = bad.addIfNone("!short");
            else for(i=0; i < smp && p < File.getSize(); i++) {
                if(smpszs[i]["ext"]) {
                    slen = File.read_uint32(p); p += 0x10;
                    iextsmp.push(File.read_ansiString(p,slen));
                    p += slen;
                } else p += smpszs[i]["slen"];
            }
        }
        if(bad != "") sVersion += "/malformed"+bad
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x2A,0x40));
            if(msg != "")
                if(showmsg) sOption(addEllipsis(msg,0x80),"msg: ");
                else sOption("("+addEllipsis(msg,0x80)+")","msg: ");
            else if(inss.length) sOption("\""+addEllipsis(inss.join(" "),200)+"\"","ins/msg:");
            else if(smps.length) sOption("\""+addEllipsis(smps.join(" "),200)+"\"","smp/msg:");
            sOption("ord:"+ord+" loop:"+loop+" ptn:"+ptn+(hasdrums?"+"+dptn:"")+" ins:"+ins+
              " smp:"+smp+(extsmp.length > 0 ? " ext.smp: ["+extsmp.join(",")+"]" : "")+
              " trk:"+trk+(hasdrums?"+8":"")+(vst2?("vst2:"+vst2):"")+" sz:"+outSz(p))
        }
    }

    else if(File.compare("'MTRAC'")) {
        sName = "Master Tracker Adlib module (.MTR)"; bDetected = true;
        //TODO
        // x = readBytes(0,file.findSignature(0,TOEOF,"1A"));
        // tt = File.read_ansiString(5,20).trim();

        // if(v!=null) sVersion = "v"+v;
        // if(File.isVerbose())
        //     sOptionT(File.read_ansiString(0x2A,0x40))
    }
    else if(File.compare("'MVM'00")) {
        sName = "MVX Module (.MVM)"; bDetected = 1
    }
    else if(File.compare("'MVSM1'")) {
        sName = "MVS Tracker module (.MUS)"; bDetected = 1
    }
    else if(File.compare("'MUS'1A") && (File.read_uint16(4,_LE)>=File.read_uint16(6,_LE))) {
        sName = "DOOM music module (.MUS)"; bDetected = true;
        if(File.isVerbose())
            sOption(File.read_uint16(4,_LE),"len:")
    }
    else if(File.compare("'MXM'00")) {
        sName = "Cubic Tiny XM module (.MXM)"; bDetected = 1
    }
    else if(File.compare("'MXTX'00")) {
        sName = "MaxTrax module (.MXTX)"; bDetected = 1
    }
    else if(File.compare("'TWNNSNG1'00")) { // looks like the first ProTrekkr was called this
        sName = "NoiseTrekker module (.NTK)"; bDetected = true;
        sVersion = "v1.6b";
        if(File.isVerbose())
            sOption(File.read_ansiString(9,20))
    }
    else if(File.compare("'TWNNSNG2'00")) { // looks like the first ProTrekkr was called this
        sName = "NoiseTrekker module (.NTK)"; bDetected = true;
        sVersion = "v2.0";
        if(File.isVerbose())
            sOption(File.read_ansiString(9,20))
    }
    else if((File.compare("'TWNNSNG'..00") && ["6","G","I"].indexOf(File.read_ansiString(7,1) >= 0))
      || File.compare("'PROTREK'")) {
        sName = "ProTrekkr module (.PTK)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(0x7,1);
        if(File.isVerbose()) // this stuff has compression in most versions, TODO?
            if(sVersion == "v6") sOption(File.read_ansiString(9,20))
    }
    else if(File.compare("'NESM'1A") || File.compare("'NSFE'")) {
        //ref http://www.nesdev.org/wiiki/NSF
        sName = "Nintendo Sound Format audio (."; bDetected = true;
        sig = File.read_ansiString(0,4);
        if(sig === "NSFE") filever = "NSFe";
        else if(File.read_uint8(5) == 2) filever = "NSF2";
        else filever = "NSF";
        sName += filever+")";
        INFOready = false; NEND = false; hkhd = ""; pn = 4;
        nsf2jump = palntsc = sz = -1; spd = ""; vrc7repl = 0;
        playtime = Number(0); g=""; t=""; a=""; c=""; taut=[]; tlbl=[]; xc="";
        if(filever === "NSFe") {
            if(File.isVerbose()) //no base header, find INFO first
              while(pn < File.getSize()){
                hksz = File.read_uint32(pn,_BE); hkhd = File.read_ansiString(pn+4,4);
                if(hkhd === "INFO") break;
                else pn += hksz+8;
            }
            nsf2jump = 0;
        }
        else { //NSF v1/2 have a hardplaced header, let's read that
            bDetected = true;
            sVersion = "";
            if(File.isVerbose()) {
                palntsc = File.read_uint8(0x7A);
                chip = File.read_uint8(0x7B);
                tc = File.read_uint8(6); t = File.read_ansiString(0x0E,0x20);
                a = File.read_ansiString(0x2E,0x20); c = File.read_ansiString(0x4E,0x20);
            }
            INFOready = true;
            nsf2jump = File.read_uint24(0x7D,_LE);
            pn += nsf2jump; //NSF v1 files ALSO can have this jump... orz
        }
        // at this point we're standing at the first hunk unless it's a pure NSF, and at "INFO" for NSFe
        if(nsf2jump >= 0 && filever === "NSFe" && File.isVerbose())
            while(pn < File.getSize() && !NEND) {
                hksz = File.read_uint32(pn,_LE); hkhd = File.read_ansiString(pn+4,4); pn+=8;
                switch (hkhd) {
                    case "NEND": //end of hunks
                        NEND = true; break;
                    case "INFO": //really shouldn't happen with nsf1/2
                        if(INFOready) break;
                        palntsc = File.read_uint8(pn+6);
                        chip = File.read_uint8(pn+7);
                        tc = File.read_uint8(pn+7);
                        INFOready = true; break;
                    case "RATE":
                        spd = "rate :: NTSC: "+File.read_uint16(pn,_LE)+" ticks";
                        palspd = File.read_uint16(pn+2,_LE);
                        if(palspd > 0) spd += ", PAL: "+palspd+" ticks";
                        dendyspd = File.read_uint16(pn+4,_LE);
                        if(dendyspd > 0) spd += ", Dendy: "+dendyspd+" ticks";
                        break;
                    case "VRC7":
                        vrc7repl = File.read_uint8(pn); break;
                    case "time":
                        for(i=0; i*4 < hksz; i++) {
                            if(i*4 >= hksz) curtime=-1
                            else curtime = File.read_int32(pn+i*4,_LE);
                            if(curtime < 0) curtime = 2*60000; //default time: 2 minutes :V
                            playtime += curtime/1000;
                        } break;
                    case "auth":
                        apn = 0;
                        g = File.read_codePageString(pn,0x100,"UTF8"); apn+=g.length+1;
                        a = File.read_codePageString(pn+apn,0x100,"UTF8"); apn+=a.length+1;
                        c = File.read_codePageString(pn+apn,0x100,"UTF8"); apn+=c.length+1;
                        c += ", rip: "+File.read_codePageString(pn+apn,0x100,"UTF8");
                        break;
                    case "taut":
                        if(File.isDeepScan()) {
                            apn = 0;
                            while(apn < hksz) {
                                trkauth = File.read_codePageString(pn+apn,hksz-apn,"UTF8"); apn += trkauth.length+1;
                                taut.push(trkauth)
                            }
                        } break;
                    case "tlbl":
                        if(File.isDeepScan()) {
                            apn = 0;
                            while(apn < hksz) {
                                trklbl = File.read_codePageString(pn+apn,hksz-apn,"UTF8"); apn += trklbl.length+1;
                                tlbl.push(trklbl)
                            }
                        } break;
                    case "text":
                        if(File.isDeepScan())
                            xc = File.read_codePageString(pn,hksz,"UTF8");
                }
                pn += hksz; sz = pn;
            }
        if(File.isVerbose() && INFOready) {
            switch(palntsc) {
                case 0: sVersion += " NTSC"; break;
                case 1: sVersion += " PAL"; break;
                case 2: sVersion += " NTSC/PAL"
            }
            if(chip&0x01) sVersion += "#VRC6";
            if(chip&0x02) switch(vrc7repl) {
                case 1: sVersion += "#YM2413"; break;
                default: sVersion += "#VRC7" }
            if(chip&0x04) sVersion += "#FDS";
            if(chip&0x08) sVersion += "#MMC5";
            if(chip&0x10) sVersion += "#Namco163";
            if(chip&0x20) sVersion += "#Sunsoft5B";
            sOptionT(t);
            if(tc > 1) sOption(tc,"×");
            sOptionT(a,"by: ");
            sOptionT(c);
        }
        if(File.isVerbose()) {
            if(playtime > 1)
              sOption(new Date(Math.round(playtime*1000)).toISOString().substr(11, 8),"Playtime: ");
            if(Math.max(tlbl.length,taut.length)>0) sOption("[Tracks]");
            for (i=0; i < Math.max(tlbl.length,taut.length); i++) {
                if(i < tlbl.length) ttlbl = tlbl[i];
                else ttlbl = "#"+(i+1);
                if(i < taut.length) ttaut = " by: "+taut[i];
                else ttaut = "";
                sOption(ttlbl+ttaut)
            }
            if(xc != "") sOption(xc,"[Commentary]: ");
            if(sz > -1) sOption(outSz(sz),"sz:")
        }
    }
    else if(File.compare("'OKTASONG'")) {
        sName = "Oktalyzer module (.OKTA)"; bDetected = true;
        if(File.isVerbose()) {
            sOption("[Sample names]");
            for(i=0x20; i <= 0x100; i+=0x20)
                sOptionT(File.read_ansiString(i,0x14))
        }
    }
    else if(File.compare("'Onyx Music File'1A")) {
        // ref ftp://http.modland.com/pub/documents/format_documentation/Onyx%20Music%20File%20(.omf).txt
        sName = "Onyx Music File (.OMF)"; bDetected = true;
        if(File.isVerbose()) {
            ch = File.read_uint8(0x192)+1;
            ptn = File.read_uint8(0x193)+1;
            ord = File.read_uint8(0x194)/2+1;
            t = File.read_ansiString(0x195,0x1F);
            if(t == "") {
                sn=[];
                for(i=0; i < 31; i++) {
                    s = File.read_ansiString(0x1B4+i*28,21);
                    if(s != "") sn.push(s)
                }
                t = sn.join("\n")
            }
            sOptionT(t);
        }
    }
    else if((File.compare("'Org-02'") || File.compare("'Org-03'")) && File.getSize() >= 114) {
        sName = "Organya module (.ORG)"; bDetected = true;
        if(File.read_uint8(5) === 0x32) sVersion == "v1"; else sVersion = "v2";
        if(File.isVerbose()) {
            c = ins = 0; for(i=0; i < 16; i++) {
                t = File.read_uint16(0x16+6*i); if(t) { ins++; c += t }
            }
            sOption("ins:"+ins+" tempo:"+File.read_uint16(6)+" rhythm:"+File.read_uint8(8)
              +"/"+File.read_uint8(9)+" notes:"+c+" sz:"+outSz(114+(c<<3)))
        }
    }
    else if(File.compare("'OBISYNTHPACK'")) {
        sName = "Synth Pack module (.OSP)"; bDetected = 1
    }
    else if(File.compare("'PACG'")) {
        sName = "SBStudio module (.PAC)"; bDetected = true;
        if(File.read_uint32(4)+8 != File.getSize()) sVersion = "malformed";
        if(File.isVerbose()) {
            p = 8; //header skipped
            t=''; v='';
            while (p<File.getSize()) {
                hkhd = File.read_ansiString(p,4);
                hksz = File.read_uint32(p+4,_LE);
                p+=0x08; //chunk header = 4xfourcc + 4xsize
                switch (hkhd) {
                    case "SONA": t = File.read_codePageString(p,hksz,"CP850"); break;
                    case "PAOR": v = File.read_codePageString(p,hksz,"CP850"); break;
                    case "END ": p = File.getSize(); break
                }
                p += hksz;
            }
            sOptionT(t); sOption(v,"via: ")
        }
    }
    else if(File.compare("'PLM'1A6010")) {
        sName = "Disorder Tracker 2 module (.PLM)"; bDetected = true;
        sVersion = "v2";
        smp = File.read_uint8(0x5C); ptn = File.read_uint8(0x5D); ord = File.read_uint16(0x5E,_LE);
        sz = 0x61; ofs = 0;
        for(i=0; i < ord+ptn+smp; i++) { z = File.read_uint32(i*4+sz,_LE); if(z > ofs) ofs = z }
        sz += 4*ord+4*ptn+4*smp;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(6,0x30));
            sOption("ch:"+File.read_uint8(0x36)+" bpm0:"+File.read_uint8(0x3A)
                +" spd0:"+File.read_uint8(0x3B)+" ord:"+ord+" ptn:"+ptn+" smp:"+smp);
        }
    }
    else if(File.compare("'PLX'00")) {
        sName = "Palladix Adlib module (.PLX)"; bDetected = 1
    }
    else if(File.compare("FFFFE002E102")) {
        sName = "POKEYNoise chiptune (.PN)"; bDetected = 1
    }
    else if(File.compare("'PRT'")) {
        sName = "PreTracker module (.PRT)"; bDetected = true;
        nV = File.read_uint8(3);
        if(nV < 25) sVersion = "v<0.3"; else
        if(nV == 25) sVersion = "v0.3~0.866"; else
        if(nV == 26) sVersion = "v0.87~0.92"; else
        if(27<=nV && nV<30) sVersion = "v.[0.93~1.5)"; else
        if(nV == 30) sVersion = "v1.5+"; else
          sVersion = "v.TODO";
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x14,0x14));
            sOptionT(File.read_ansiString(0x28,0x14),"by: ");
            trks = File.read_uint8(0x5A); if(trks>31) sVersion += "/malformed";
            sOption("trks:"+File.read_uint8(0x5A)+
                " ord:"+File.read_uint8(0x5F)+
                " ptn:"+File.read_uint8(0x5D)+"("+File.read_uint8(0x5E)+")"+
                " restart:"+File.read_uint8(0x5C))
        }
    }
    else if(File.compare("'PSA'00") && 0x34 < File.read_uint32(0x28,_BE) < File.getSize()
      && 0x34 < File.read_uint32(0x2C,_BE) < File.getSize()
      && 0x34 < File.read_uint32(0x30,_BE) < File.getSize()) {
        sName = "Professional Sound Artists module (.PSA)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x4,0x14));
            //ref https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/PSA/PSA_v2.asm
            d2 = File.read_uint32(0x28,_BE);
            x = (d2-56) >> 3; if(x > 1) sOption(x,"×");
            d3 = File.read_uint32(0x2C,_BE); d0 = File.read_uint32(0x30,_BE);
            ins = ((d3-d2) >> 6); d3 = ins;
            synsmp = 0; smp = 0; allsmpsz = 0; d7 = 3;
            while(d3) { //hop
                ts = File.read_uint32(d2,_BE); //ins size?
                if(ts) {
                    if(ts < d7) {
                        d7 = ts;
                        smpsz = (File.read_uint16(d2,_BE)+4) << 1;
                        allsmpsz += smpsz;
                        smp++
                    }
                } else synsmp++; //synth
                d2 += 64; d3--
            }
            sz = File.read_uint32(36,_BE); songsz = sz-allsmpsz; ptn = (songsz-d0) >> 10;
            sOption("ptn:"+ptn+" ins:"+ins+" smp:"+smp+" synsmp:"+synsmp+" songsz:"+songsz+" sz:"+outSz(sz))
        }
    }
    else if(File.compare("'PSC V'........' COMPILATION OF '")) {
        sName = "Pro Sound Creator module (.PSC)";
        sVersion = "v"+File.read_ansiString(5,4); bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x19,0x14));
            sOptionT(File.read_ansiString(0x31,0x14),"by: ")
        }
    }
    else if(File.compare("'PSF'")) {
        //ref https://gist.github.com/SaxxonPike/a0b47f8579aad703b842001b24d40c00
        nV = File.read_uint8(3);
        if(nV>=0) {
            bDetected = true;
            sName = "Portable Sound Format module (.";
            switch(nV) {
                case 0x01: sName += "PSF,.MINIPSF)"; sVersion = "Playstation"; break;
                case 0x03: sName += "PSF2,.MINIPSF2)"; sVersion = "Playstation 2"; break;
                case 0x11: sName += "SSF,.MINISSF)"; sVersion = "Saturn"; break;
                case 0x12: sName += "DSF,.MINIDSF)"; sVersion = "Dreamcast"; break;
                case 0x21: sName += "USF,.MINIUSF)"; sVersion = 'Ultra64'; break;
                case 0x22: sName += "GSF,.MINIGSF)"; sVersion = 'Gameboy'; break;
                case 0x23: sName += "SNSF,.MINISNSF)"; sVersion = 'Super Nintendo'; break;
                case 0x24: sName += "2SF,.MINI2SF)"; sVersion = 'Nintendo DS'; break;
                case 0x25: sName += "NCSF,.MININCSF)"; sVersion = 'Nintendo DS Nitro Sound'; break;
                case 0x41: sName += "QSF,.MINIQSF)"; sVersion = 'Capcom Q-Sound'; break;
                //case ??:  sName += "DCSF,.MINIDCSF)"; sVersion = '?'; break;
                default: sName += "*SF,.MINI*SF)"; sVersion = "unk.console";
            }
            if(File.isVerbose()) {
                //fs1 = Math.max(File.getSize()-0x200,0); fs2 = Math.min(File.getSize(),0x200);
                //ptags = File.findSignature(fs1,fs2,"'[TAG]'")+5;
                ptags = File.read_uint32(8,_LE)+0x15; sig2 = false;
                if(0x15 < ptags && ptags < File.getSize()) sig2 = File.read_ansiString(ptags-5,5) === "[TAG]";
                if(!sig2) { //coincides with pv==2
                    ptags = File.read_uint32(4,_LE)+0x15;
                    if(0x15 < ptags && ptags < File.getSize()) sig2 = File.read_ansiString(ptags-5,5) === "[TAG]";
                }
                if(sig2) {
                    tags = File.read_codePageString(ptags,File.getSize()-ptags,"UTF8").trim();
                    a=""; g=""; t=""; y=""; l="";
                    tagl = tags.split('\n');
                      for (i=0; i<tagl.length;i++) {
                        tag = tagl[i].split("=");
                        switch (tag[0]) {
                            case "length": l = tag[1]; break;
                            case "title": t = tag[1]; break;
                            case "game": g = tag[1]; break;
                            case "artist": a = tag[1]; break;
                            case "copyright": if(a=="") a = tag[1]; break;
                            case "year": y = tag[1]; break;
                            case "dsfby","psfby","ssfby": if(a=="") a = tag[1]; break;
                        }
                      }
                    sOption(t);
                    sOption(a,"by: ")
                    sOption(y,"'");
                    sOption(g,"for: ");
                    sOption(l,"len ");
                }
                else sVersion += " library";
            }
            if(File.getSize() < 0x300) sVersion += " header"
        }
    }
    else if(File.compare("'PSM '") || File.compare("'PSM'FE")) {
        sName = "Epic Megagames MASI module (.PSM)"; bDetected = true;
        if(File.isVerbose()) {
            pt = File.findString(0,50,"MAINSONGTITL");
            if(pt>0) {
                ts = File.read_uint32(pt+12);
                sOptionT(File.read_ansiString(pt+16,ts));
            }
        }
    }
    else if(File.compare("'PSG'1A")) { sName = "fMSX/x128 PSG chiptune (.PSG)"; bDetected = 1 }
    else if(File.compare("'EPSG'1A")) {
        //ref https://web.archive.org/web/20070919070829/http://maxheadroom.no-ip.com/ay/formats_epsg.php
        sName = "Z80 Stealth Extended PSG chiptune (.EPSG)"; bDetected = true; hw = File.read_uint8(5);
        switch(hw) {
        case 0: sVersion = "ZX Spectrum 128k"; break;
        case 1: "Pentagon"; break;
        }
        if(hw<2 && !File.compare("00000000 000000000000",6)) sVersion += "/malformed"
    }
    else if(File.compare("'PSG2'")) {
        //ref https://web.archive.org/web/20070919070829/http://maxheadroom.no-ip.com/ay/formats_epsg.php
        sName = "PSG2 chiptune (.PSG2)"; bDetected = true;
    }
    else if(File.compare("'psm1'00", 0x08)) {
        sName = "Pro Sound Maker module (.PSM)"; sVersion = "compiled"; bDetected = true; bad = 0;
        posp = File.read_uint16(0,_LE); if(posp<0x0D || posp>File.getSize()) bad = 1;
        smpp = File.read_uint16(2,_LE); if(smpp<0x0D || smpp>File.getSize()) bad = 2;
        ornp = File.read_uint16(0,_LE); if(ornp<0x0D || ornp>File.getSize()) bad = 3;
        ptnp = File.read_uint16(0,_LE); if(ptnp<0x0D || ptnp>File.getSize()) bad = 4;
        if(File.isVerbose()) {
            t = posp; if(smpp < t) t = smpp; if(ornp < t) t = ornp; if(ptnp < t) t = ptnp;
            if(t-0x0D) sOptionT(File.read_ansiString(0x0D,Math.min(t-0x0D,128)),"msg: ");
            ord = 0; t = posp;
            while(File.read_uint8(t)<0xFF && t<File.getSize()) {
                ord++; t += 2;
            }
            loop = 0xFF; if(File.read_uint8(t) == 0xFF) loop = File.read_uint8(t+1); else bad = 5;
            if(loop == 0xFF) loop = "none";
            sOption("ord:"+ord+" loop:"+loop)
        }
        if(bad) sVersion += "/malformed"+bad;
    }
    else if(File.compare("'PSY'..'SONG")) {
        sName = "Psycle module (.PSY)"; bDetected = true;
        sV = File.read_ansiString(3,1); sVersion = "v"+sV;
        if(File.isVerbose()) {
            switch(sV) {
                case "0": case "1":
                    sOptionT(File.read_codePageString(0x08,0x20,"CP1252"));
                    sOptionT(File.read_codePageString(0x28,0x20,"CP1252"),"by: ");
                    sOptionT(File.read_codePageString(0x48,0x80,"CP1252")); break;
                case "2":
                //ref https://sourceforge.net/p/psycle/code/HEAD/tree/trunk/psycle-core/src/psycle/core/psy2filter.cpp
                    sOptionT(File.read_codePageString(0x08,0x20,"CP1252"));
                    sOptionT(File.read_codePageString(0x28,0x20,"CP1252"),"by: ");
                    sOptionT(File.read_codePageString(0x48,0x80,"CP1252"));
                    bpm = File.read_int32(0xC8); ord = File.read_int32(0x191);
                    trk = File.read_int32(0x195); ptn = File.read_int32(0x199);
                    p = 0x19D;
                    if(File.isDeepScan()) {
                        inss = []; vsts = []; macs = []; waves = mach = 0; bad = "";
                        for(i=0; i < ptn; i++) { rows = File.read_int32(p); p += 0x24+0xA0*rows }
                        p += 4; inss = [];
                        for(i=0; i < 0xFF; i++) { t = File.read_codePageString(p,0x20,"CP1252").trim();
                            if(t != "" && t != "empty") inss.push(t); p += 0x20 }
                        p += 0x37CC; //_log("waves @"+Hex(p))
                        if(p > File.getSize()) bad = bad.addIfNone("!short");
                        else for(i=0; i < 0xFF && p < File.getSize(); i++)
                            for(w=0; w < 0x10 && p < File.getSize(); w++) { t = File.read_uint32(p); p += 4;
//if(t || p+t*2 > File.getSize()) _log("wave["+i+","+w+"] @"+Hex(p)+" ["+Hex(t)+"]: "+File.read_codePageString(p,0x20,"CP1252").trim());
                                if(t) { if(!w) waves++; p += 0x2D; var st = File.read_uint8(p++)+1; p += t<<st }
                            }
//_log("vsts @"+Hex(p));
                        if(p > File.getSize()) bad = bad.addIfNone("!short");
                        else for(i=0; i < 0x100; i++) {
                            t = File.read_uint8(p++); if(t) {
//_log("vst["+i+"] @"+Hex(p));
                                t = File.read_codePageString(p,0x80,"CP1252").trim(); if(t != "") vsts.push(t);
                                p += 0x80; t = File.read_int32(p); p += 4+t*4;
                            }
                        }
//_log("mach @"+Hex(p));
                        m = []; for(i = 0; i < 128; i++) { t = File.read_uint8(p++); if(t) mach++; m[i] = t }
                        t = oldt = 0;
                        const mtMa=0,mtRM=1,mtDi=2,mtSa=3,mtDe=4,mtF2=5,mtGa=6,mtFl=7,mtPl=8,
                          mtVI=9,mtVF=10,mtSc=11,mtAB=12,mtA1=13,mtA2=14,mtA21=15,mtDu=255;
                        for(i=0; i < 128; i++) if(m[i]) {
                            t = File.read_int32(p+8); p += 12;
                            tn = File.read_ansiString(t===mtPl?p+0x100:p, 0x10);
                            tpn = t === mtPl ? File.read_ansiString(p,0x100) : ""; ts = tn+tpn;
//_log("ts: "+ts+", oldt = "+oldt);
                            nonascii = false; for(q=0; q < ts.length; q++)
                                if(ts[q] < " " || ts[q] > "~" || /[\"\+\?\*]/.test(ts[q])) {nonascii = true; break }
                            if(((15 < t && t < 255) || ts.length < 2 || nonascii) && oldt === mtPl) {
                                bad = bad.addIfNone("!pluginDefinedDataSize"); break
                            }
                            else if(15 < t && t < 255) { bad = bad.addIfNone("!badMachineType"); break }
                            oldt = t;
                            switch(t) {
                            case mtPl: macs.push(tn);
                                t = File.read_int32(p+0x110); p += 0x114+4*t;
//_log("   ["+macs[macs.length-1]+"] post-"+Hex(t)+" vals @"+Hex(p));
                                p += 0x181; break;
                            case mtVI: case mtVF: vsts.push(tn); p += 0x197; break;
                            default: macs.push(File.read_ansiString(p,0x10)); p += 0x191
                            }
                        }
                        if(p > File.getSize()) bad = bad.addIfNone("!short");
//_log("patch 0 @"+Hex(p));
                        p += 5*0xFF; //patch 0
                        if(p > File.getSize()) bad = bad.addIfNone("!noP0");
//_log("patch 1 @"+Hex(p));
                        p += 0x40;
                        if(p > File.getSize()) bad = bad.addIfNone("!noP1");
//_log("patch 2 @"+Hex(p));
                        if(p < File.getSize() && FIle.read_uint8(p++))
                            p += 4+File.read_uint32(p);
                        if(bad != "") sVersion += "/malformed"+bad;
                        if(inss.length) sOption("inss: ["+inss.join(",")+"]");
                        if(vsts.length) sOption("vsts: ["+vsts.join(",")+"]");
                        if(macs.length) sOption("mcn: ["+macs.join(",")+"]");
                        sOption("ord:"+ord+" ptn:"+ptn+" trk:"+trk+" bpm:"+bpm+" mach:"+mach+" smp:"+waves+" sz:"+outSz(p));
                    } //isdeep
                    else sOption("ord:"+ord+" ptn:"+ptn+" trk:"+trk+" bpm:"+bpm);
                    break;
                case "3":
                //ref https://sourceforge.net/p/psycle/code/HEAD/tree/trunk/psycle-core/src/psycle/core/psy3filter.cpp
                    nV = File.read_int32(8);
                    sVersion+="/"+nV;
                    p = 0x10+File.read_uint32(0x0C); //chunk header = 4xfourcc + 4xver + 4xsize
                    hkn = File.read_uint32(0x10,_LE);
                    t=''; a=''; c=''; trk=0; bpm=0; ptnlns=0; ptn=0; mac=0; ins=0; eins=0;
                    while (hkn > 0) {
                        hkhd = File.read_ansiString(p,4);
                        hkn--;
                        cV = File.read_uint32(p+4,_LE);
                        hksz = File.read_uint32(p+8,_LE);
                        p+=0x0C;
                        switch (hkhd) {
                            case "INFO":
                                p1 = p;
                                if((cV & 0xFF00)==0) {
                                    t = File.read_codePageString(p1,128,"CP1252"); p1+=t.length+1;
                                    a = File.read_codePageString(p1,64,"CP1252"); p1+=a.length+1;
                                    c = File.read_codePageString(p1,0xFFFF,"CP1252"); p1+=c.length+1;
                                    if(cV == 0) hksz=t.length+a.length+c.length+3 //bug workaround
                                }
                                break;
                            case "SNGI":
                                if((cV & 0xFF00) == 0) {
                                    trk=File.read_int32(p,_LE);
                                    if(cV==0) hksz = 11*4+trk*2; //bug workaround
                                    bpm=File.read_int16(p+4,_LE) + File.read_int16(p+6,_LE)/100.0;
                                }
                                break;
                            case "SEQD":
                                if((cV & 0xFF00) == 0)
                                    seqlen=File.read_int32(p+4,_LE);
                                break;
                            case "PATD":
                                if((cV & 0xFF00) == 0) {
                                    _idx = File.read_int32(p,_LE);
                                    ptnlns = File.read_int32(p+4,_LE);
                                    ptnn = File.read_ansiString(p+0x0C,0x20);
                                    p1=p+0x0C+ptnn.length+1;
                                    ptnsz = File.read_int32(p1,_LE);
                                    ptn++; p1+=4;
                                    if((cV == 0) && (p1+ptnsz == p+hksz+4)) hksz += 4; //bug workaround
                                }
                                break;
                            case "MACD":
                                mac++;
                                //bugfix impossible here, let's pretend it won't happen and move on
                                break;
                            case "INSD":
                                ins++;
                                break;
                            case "EINS":
                                if((cV & 0xFFFF0000) == 0x10000) {
                                    eins = File.read_uint32(p,_LE)
                                    //and again, pretending there's no bug (and no samples)
                                }
                                break;
                        }
                        p += hksz;
                    }
                    sOptionT(t); sOptionT(a,"by: "); sOptionT(c);
                    sOption("bpm:"+bpm+" trk:"+trk+" ptnlns:"+ptnlns+
                        " mac:"+mac+" ptn:"+ptn+"/idx:"+_idx+" ins:"+ins+" eins:"+eins+" sz:"+outSz(p));
                    break;
                case "4":
                //ref https://sourceforge.net/p/psycle/code/HEAD/tree/trunk/psycle-core/src/psycle/core/psy4filter.cpp
                    sVersion+="/future";
                    //yet another zipped-xmls format
            }
        }
    }
    else if(File.compare("'PTCOLLAGE-'") || File.compare("'PTTUNE--20'")) {
        //ref source code / pxtnService.cpp, pxtnDescriptor.cpp
        bDetected = true;
        v1 = File.read_ansiString(2,1);
        if(v1 === "T") { var rough = 10; //used for clocking
            sName = "pxtone tune (.PTTUNE)" }
        else {  var rough = 1;
            sName = "pxtone project (.PTCOP)" }
        dt = File.read_ansiString(10,6);
        if(dt <= "050227") { sV = "v.x1x"; nV = 1 }
        else if(dt <= "050608") { sV = "v.x2x"; nV = 2 }
        else if(dt <= "060115") { sV = "v.x3x"; nV = 3 }
        else if(dt <= "060930") { sV = "v.x4x"; nV = 4 }
        else if(dt <= "071119") { sV = "v5"; nV = 5 }
        d = dt.substr(0,2)+"-"+dt.substr(2,2)+"-"+dt.substr(4,2);
        sVersion = sV+"/20"+d;
        if(File.isVerbose()) {
            switch(nV) {
                case 1: case 2: p = 0x10; break;
                case 3: case 4: p = 0x14; break;
                default: p=0x14
            }
            t = ""; c = ""; bclock = 0; bnum = 0; btempo = 0; bps = 0; bEnd = false;
            while(!bEnd && p < File.getSize()) {
                hkhd = File.read_ansiString(p,8); hksz = File.read_uint32(p+8,_LE);
                switch (hkhd) {
                    case "PROJECT=":
                        t = File.read_codePageString(p+12,0x10,"Shift_JIS");
                        btempo = File.read_float32(p+0x1C,_LE).toFixed(0);
                        bclock = File.read_int16(p+0x20,_LE);
                        bnum = File.read_int16(p+0x22,_LE);
                        break;
                    case "evenMAST":
                        if(File.read_uint16(p+12,_LE) != 3)
                            sVersion += "/unk";
                        else {
                            var p_ = File.findSignature(p+3,0x100,"'textNAME'")-12;
                            if(p_ > -12) p = p_-hksz;
                            else {
                                p_ = File.findSignature(p+3,0x100,"'textCOMM'")-12;
                                if(p_ > -12) p = p_-hksz;
                            }
                        }
                        break; //not gonna parse what vari-read gives me
                    case "MasterV5":
                        bclock = File.read_int16(p+12,_LE)*rough;
                        bnum = File.read_int8(p+14);
                        btempo = File.read_float32(p+15,_LE).toFixed(0);
                        break;
                    case "Event V5":
                        var evtn = File.read_uint32(p+12,_LE);
                        hksz = 4;
                        for(e=0; e < evtn; e++) { //hksz is broken in this chunk so it's like this
                            for(i=0; i < 5; i++) { hksz++; if(File.read_uint8(p+11+hksz) < 0x80) break }
                            hksz += 2;
                            for(i=0; i < 5; i++) { hksz++; if(File.read_uint8(p+11+hksz) < 0x80) break }
                        }
                        break;
                    case "textNAME":
                        t = File.read_codePageString(p+12,hksz,"Shift_JIS"); break;
                    case "textCOMM":
                        c = addEllipsis(File.read_codePageString(p+12,hksz,"Shift_JIS"),0xA0); break;
                    case "END=====": case "pxtoneND":
                        bEnd = true; break
                }
                p += 12+hksz
            }
            if(t != "no name") sOption(t);  sOptionT(c);
            if(bclock+btempo+bnum > 0)
                sOption("btempo:"+btempo+" bclock:"+bclock+" bnum:"+bnum);
            sOption(outSz(p),"sz:")
        }
    }
    else if(File.compare("'PTNOISE-'")) {
        sName = "pxtone Noise instrument (.PTNOISE)"; bDetected = 1
    }
    else if(File.compare("'PTVOICE-'")) {
        sName = "pxtone Voice instrument (.PTVOICE)"; bDetected = true;
        if(File.isVerbose()) sOption(outSz(File.read_uint32(0x0C)+0x10),"sz:")
    }
    else if(File.compare("'PTMF'",0x2C) && File.compare("1A",0x1C)) {
        // ref  https://web.archive.org/web/20151027135201if_/http://hackipedia.org/File%20formats/Music/Sample%20based/text/Poly%20Tracker%20module%20format.cp437.txt.utf-8.txt
        sName = "Poly Tracker module (.PTM)"; bDetected = true;
        sV = Hex(File.read_uint16(0x1D,_LE));
        sVersion = "v"+sV.substr(0,1)+"."+sV.substr(1,2);
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0,0x1C));
            bad = "";
            ch = File.read_uint16(0x26,_LE); if(!ch || ch > 32) bad = bad.addIfNone("!badchn");
            ord = File.read_uint16(0x20,_LE); if(ord>256) bad = bad.addIfNone("!badord");
            ins = File.read_uint16(0x22,_LE); if(!ins || ins > 255) bad = bad.addIfNone("!badins");
            ptn = File.read_uint16(0x24,_LE); if(!ptn || ptn > 128) bad = bad.addIfNone("!badptn");
            if(bad) sVersion += "/malformed"+bad;
            sOption("ch:"+ch+" ins:"+ins+" ptn:"+ptn+" ord:"+ord)
        }
    }
    else if(File.compare("'FORM'.. .. .. ..'MODLVERS'")){
        sName = "ProTracker IFF module (.PTM)"; bDetected = true;
        sVersion = File.read_ansiString(0x18,File.read_uint32(0x10,_BE)-0x10);
        if(File.isVerbose()) {
            p = File.findSignature(0x1E,0x200,"'INFO'");
            if(p > 0) sOptionT(File.read_ansiString(p+8,0x20));
            p = File.findSignature(p+File.read_uint32(p+4,_BE),0x200,"'CMNT'");
            if(p > 0) sOptionT(File.read_ansiString(p+8,File.read_uint32(p+4,_BE)))
        }
    }
    else if(File.compare("'RAD by REALiTY!!'")) {
        //partial ref https://www.3eality.com/Public/Releases/RAD/radv2_0a.zip source/validate20.cpp
        // & https://www.3eality.com/Public/Releases/RAD/radv1_1a.zip player.asm
        sName = "Reality Adlib Tracker module (.RAD)"; bDetected = true;
        sV = File.read_uint8(0x10).toString(16);
        sVersion = "v"+sV[0]+"."+sV[1]; bad = "";
        if(File.isVerbose()) {
            al = File.read_uint8(0x11);
            fbpm = (sV > "20") && (al&0x20); // in v2.1, if the bpm flag is set, bpm != 125 and is the next byte
            if((sV=="10" && (al&0x80)) || (sV >= "20")) { //desc present
                l = 0x12; if(fbpm) l+=2;
                desc = ""; maxdesc = 250; cutoff = false;
                r = File.findSignature(l,File.getSize()-32-l,"00"); p = r+1;
                if(r < 0) { maxdesc = 0; bad = bad.addIfNone("!baddesc") }
                else if(r > l+maxdesc) { r = l+maxdesc; cutoff = true }
                tmp = readBytes(l,r-l);
                for(i=0; desc.length < maxdesc; i++)
                    switch(tmp[i]) {
                    case 0: i = r-l; break;
                    case 1: desc += "\n"; break;
                    default:
                        if(tmp[i] < 0x20) for(j=0; j<tmp[i] && desc.length<=maxdesc; j++) desc += " ";
                        else desc += String.fromCharCode(tmp[i])
                    }
                if(cutoff) { desc+="..." } sOption(desc);
            } else p = 0x12;
            spd0 = al&0x1F;
            if(fbpm) bpm = File.read_uint16(0x12,_LE); else bpm = 125;
            ins = 0;
            while(1) {
                insn = File.read_uint8(p++); if(!insn || p>File.getSize()) break;
                ins++;
                if(sV == "10") p += 11;
                else if(sV == "21") {
                    nmlen = File.read_uint8(p);
                    p += nmlen+1;
                    rm = File.read_uint8(p); riff = rm&0x80; midi = (rm&7)==7;
                    if(midi) p += 7; else p += 24;
                    if(riff) p += File.read_uint16(p,_LE)+2;
                } else bad = bad.addIfNone("!badver");
            }
            ord = File.read_uint8(p++); p += ord;
            ptn = 0; for(i=0; i < 32; i++) if(File.read_uint16(p+i*2,_LE)) ptn++; p += 64;
            if(!ord || ord>128) bad = bad.addIfNone("!badord");
            if(!ptn) bad = 5; if(!ins) bad = bad.addIfNone("!badptn");
            if(sV > "20") { riffs = 0;
                while(1) {
                    riffn = File.read_uint8(p++);
                    if(riffn==0xFF || p>File.getSize()) break;
                    riffs++; p += File.read_uint16(p)
                }
                sOption("spd0:"+spd0+" bpm:"+bpm+" ord:"+ord+" ptn:"+ptn+" ins:"+ins+" riffs:"+riffs)
            }
            else sOption("spd0:"+spd0+" bpm:"+bpm+" ord:"+ord+" ptn:"+ptn+" ins:"+ins)

            if(bad) sVersion += "/malformed"+bad
        }
    }
    else if(File.compare("'RAWADATA'") && File.getSize()>10) {
        sName = "Raw OPL Capture chiptune (.RAW)"; bDetected = true;
        if(File.isVerbose() && (File.isDeepScan() || File.getSize()<0xFFFF)) { //may take a sec
            //taken from https://github.com/adplug/adplug/blob/master/src/raw.cpp
            p = 10; len = (File.getSize()-10) >> 1;
            tagdata = false; t = ""; a = ""; d = ""; next = 0;
            for(i=0; i < len && p < File.getSize(); i++) {
                x = (tagdata ? 0xFFFF : File.read_uint16(p));
                p += 2;
                if(!tagdata && x==0xFFFF) { //trying to stop at the RAW EOF data marker
                    tagcode = File.read_uint8(p++);
                    if(tagcode == 0x1A) tagdata = true; //tag marker found
                    else if(!tagcode) { //old comment (music archive 2004)
                        d = File.read_ansiString(p,1023); p += 1023; tagdata = true }
                    else p--;
                }
            }
_log("after-for "+Hex(p)+", tagdata? "+tagdata);
            if(tagdata) { //searching for tags
                t = File.read_ansiString(p,40); p += 40;
                if(File.read_uint8(p++) != 0x1B) { //if not, skip "author"
                    p--;
                    if(File.read_uint8(p) >= 0x20) { //older version tag, eg. stunts.raw
                        a = File.read_ansiString(p,60); p += 60;
                        d = File.read_ansiString(p,1023); p += 1023;
                        next = 2; //end_section
                    } else p--;
                    next = 1; //desc_section
                }
                if(!next) {
                    a = File.read_ansiString(p,40); p += 40
                }
                if(next == 1) { next = 0;
                    if(File.read_uint8(p++) != 0x1C) next = 2;
                    if(!next) {
                        d = File.read_ansiString(p,1023); p += 1023
                    }
                }
            }
            sOption(t); sOption(a,"by: "); sOption(d)
        } //isDeepVerbose
        if(File.isVerbose()) sOption(File.read_uint16(8,_LE),"clkspd:")
    }
    else if(File.compare("00407F40 00C081C0") && File.compare("41FAFFEE",0x38)) {
        //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/RobHubbard_ST/src/Rob Hubbard ST_v2.asm
        sName = "Rob Hubbard ST module (.RHO)"; sVersion = "v1.1"; bDetected = true;
        if(File.isVerbose()) {
            a2 = 180; bad = false;
            do { //findplay
                t = File.read_uint16(a2,_BE); a2 += 2;
            } while(a2 < File.getSize() && t != 0x4E75);
            if(a2 > File.getSize()) { bad = true; break }
            playp = a2;
            do { //findend
                t = File.read_uint16(a2,_BE); a2 += 2;
            } while(a2 < File.getSize() && t != 0xDFFC);
            if(a2 > File.getSize()) { bad = true; break }
            a2 += 4; endp = a2;
            do { //findinit
                t = File.read_uint16(a2,_BE); a2 += 2;
            } while(a2 < File.getSize() && t != 0x4E75);
            if(a2 > File.getSize()) { bad = true; break }
            do { //findIt1
                t = File.read_uint16(a2,_BE); a2 += 2;
            } while(a2 < File.getSize() && t != 0x7E02);
            if(a2 > File.getSize()) { bad = true; break }
            songp = a2+File.read_uint16(a2+2,_BE)+2;
            d0 = File.read_uint32(songp,_BE)/12;
            if(d0 > 1) sOption(d0,"×");
            d0 = d0*3-1; d5 = 0;
            a1 = a3 = songp;
            while(d0 >= 0) {
                a1 = songp;
                a1 += File.read_uint32(a3,_BE); a3 += 4;
                while(a1 < File.getSize()) {
                    d1 = File.read_uint32(a1,_BE); a1 += 4;
                    if(!d1) break;
                    if(d1 > d5) d5 = d1
                }
                d0--
            }
            a1 = songp+d5;
            do {
                t = File.read_uint8(a1); a1++
            } while(a1 <= File.getSize() && t != 0x87)
            sz = a1; // - moduleptr, 0 here
            if(a1 > File.getSize()) { bad = true; break }
            sOption(outSz(sz),"sz:")
            if(bad) sVersion += "/malformed";
        }
    }
    else if(File.compare("'RNS0'") && File.compare("'>>> Chunk Start <<<'", 0x09)) {
        sName = "Renoise module (.RNS)"; bDetected = true;
        sV = File.read_ansiString(3,4); sVersion = "v"+sV;
        if(sV < "05") sVersion += "/RN<1.1.1";
        else if(sV === "05") sVersion += "/RN1.1.1";
        else if(sV < "015") sVersion += "/RN<1.2.7";
        else if(sV === "015") sVersion += "/RN1.2.7";
        else if(sV < "018") sVersion += "/RN<1.5.2";
        else if(sV === "018") sVersion += "/RN1.5.2";
        else sVersion += "/RN>1.5.2"
        if(File.isVerbose()) {
            p = 8;
            while (p < File.getSize()) {
                p = File.findSignature(p+1,TOEOF,"'>>> Chunk Start <<<'");
                if(p == -1)
                    break;
                p += 20;
                if(File.read_ansiString(p,10) == "Header V00") {
                    sOptionT(File.read_codePageString(p+20,20,"CP1252"));
                    sOptionT(File.read_codePageString(p+40,20,"CP1252"),"by: ");
                    sOptionT(File.read_codePageString(p+60,20,"CP1252"),"style: ");
                    break;
                }
                else if(File.read_ansiString(p,10) == "Header V01") {  p += 20;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252")); p+=sz+4;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252"),"by: "); p+=sz+4;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252"),"style: ");
                    break;
                }
                else if(File.read_ansiString(p,10) == "Header V02") {  p += 20;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252")); p+=sz+4;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252"),"by: "); p+=sz+4;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252"),"style: "); p+=sz+4+5;
                    sz = File.read_uint32(p); sOptionT(File.read_codePageString(p+4,sz,"CP1252"));
                    break;
                }
                p = File.findSignature(p+1,TOEOF,"''>>>  Chunk End  <<<'");
                if(p == -1) break
            }
        }
    }
    else if(extIs("xrns") && File.compare("'PK'0304")) {
        //TODO when ZIP parsing is available
        sName = "Renoise module (.XRNS)"; sVersion = "xml"; bDetected = 1
        //versions vs doc_version:
        // 10: 1.9.1    14: 2.0.0    15: 2.1.0    21: 2.5.1    22: 2.6.1    30: 2.7.0, 2.7.1, 2.7.2
        // 37: 2.8.2    54: 3.0.1    63: 3.1.0    65: 3.3.2    66: 3.4.2
    }
    else if(extIs("xrdp") && File.findString(0,0x100,"<FilterDevicePreset")>=0) {
        sName = "Renoise filter device preset (.XRDP)"; bDetected = true;
        p = File.findString(20,0x100,"doc_version=");
        if(p < 0) sVersion = "malformed";
        else {
            sVp = File.read_ansiString(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
        }
    }
    else if(extIs("xrni") && File.compare("'PK'0304")) {
        //TODO when ZIP parsing is available
        sName = "Renoise instrument (.XRNI)"; sVersion = "xml"; bDetected = 1
    }
    else if(extIs("xrno") && (File.findString(0,0x100,"<SampleModulationSet")>=0)) {
        sName = "Renoise sample modulation set (.XRNO)"; bDetected = true;
        p = File.findString(15,0x100,"doc_version=");
        if(p < 0) sVersion = "malformed";
        else {
            sVp = File.read_ansiString(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
        }
    }
    else if(extIs("xrnt") && (File.findString(0,0x100,"<RenoiseDeviceChain")>=0)) {
        sName = "Renoise effect chain (.XRNT)"; bDetected = true;
        p = File.findString(15,0x100,"doc_version=");
        if(p < 0) sVersion = "malformed";
        else {
            sVp = File.read_ansiString(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
        }
    }
    else if(extIs("xrnt") && File.compare("'PK'0304")) {
        sName = "Renoise effect chain (.XRNT)"; sVersion = "v<3.0"; bDetected = 1
    }
    else if(extIs("xrnz") && (File.findString(0,0x100,"<InstrumentPhrase")>=0)) {
        sName = "Renoise instrument phrase (.XRNZ)"; bDetected = true;
        p = File.findString(15,0x100,"doc_version=");
        if(p < 0) sVersion = "malformed";
        else {
            sVp = File.read_ansiString(p,0x10); sVersion = "v"+/doc_version=\"(\d*)\"/.exec(sVp)[1]
        }
    }
    else if(File.compare("'ofTAZ!'")) {
        sName = "Extra Simple Music module (.XSM)"; bDetected = 1
    }
    else if(File.compare("'NuRIFFRAFF'",0x22)) {
        sName = "Riff Raff module (.RIFF)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_codePageString(0x64,0x100,"CP1252");
            a = File.read_codePageString(0x64+t.length+1,0x100,"CP1252");
            c = File.read_codePageString(0x64+t.length+a.length+2,0x100,"CP1252");
            sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
        }
    }
    else if(File.compare("'RON_KLAREN_SOUNDMODULE!'00", 0x28)) {
        sName = "Ron Klaren module (.RK)"; bDetected = 1
    }
    else if(File.compare("00000400") && File.compare("000000000000000000000000000000000000000000000000000000000000000000000000000000'Tempo'00", 0x8F)) {
        sName = "Visual Composer module (.ROL)"; bDetected = 1
    }
    else if(File.compare("'RTMM '")) {
        sName = "Real Tracker module (.RTM)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(0x37,7);
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(5,0x20,"CP1252"));
            sOptionT(File.read_codePageString(0x3E,0x20,"CP1252"),"by: ");
            sOptionT(File.read_codePageString(0x8C,0x20,"CP1252"),"orig.: ")
        }
    }
    if(File.compare("00000000 00000200",0x0C) && File.compare("00")
      && File.read_uint16(0x18,_LE) === File.read_uint16(0x40,_LE)
      && File.compare("00'ST-Module.'", 0x4B7)) {
        sName = "Amstrad CPC Soundtrakker module (.STK)"; bDetected = true;
        datasz = File.read_uint16(0x18,_LE); sz = datasz + 0x80;
        if(sz < File.getSize()) sVersion = "malformed";
        crc = 0; for(i=0; i < 67; i++) crc += File.read_uint8(i); crc &= 0xFFFF;
        if(crc != File.read_uint16(0x43)) sVersion += sVersion != "" ? "!badCRC" : "/malformed!badCRC";
        if(File.isVerbose())
            sOptionT(File.read_ansiString(1,8));
            sOption(outSz(sz),"sz:")
    }
    else if(File.compare("00000000 00000200",0x0C) && File.compare("00")
      && File.read_uint16(0x18,_LE) === File.read_uint16(0x40,_LE)
      && File.read_ansiString(1,8) === File.read_ansiString(0xB10,8)) {
        sName = "Amstrad CPC Soundtrakker 128K module (.128)"; bDetected = true;
        datasz = File.read_uint16(0x18,_LE); sz = datasz + 0x80;
        if(sz < File.getSize()) sVersion = "malformed";
        crc = 0; for(i=0; i < 67; i++) crc += File.read_uint8(i); crc &= 0xFFFF;
        if(crc != File.read_uint16(0x43)) sVersion += sVersion != "" ? "!badCRC" : "/malformed!badCRC";
        if(File.isVerbose())
            sOptionT(File.read_ansiString(1,8));
            inst = []; ins = 0;
            for(i=0; i < 16; i++) if(![0,0x20].indexOf(File.read_uint8(0xB18+8*i))) {
                ins++; inst.push(File.read_ansiString(0xB18+8*i,8).trim())
            } //TODO confirm that that's how to count the instruments
            sOption(inst);
            sOption("ins:"+ins+" sz:"+outSz(sz))
    }
    else if(File.compare("00'ST-Module.'",0x437)) {
        sName = "Amstrad CPC Soundtrakker module (.STK)"; bDetected = true;
        sVersion = "headerless"
    }
    else if(File.compare("'SCRM'", 0x2C) && File.compare("10",0x1D)
      && [1,2].indexOf(File.read_uint8(0x2A)) >= 0 && File.getSize() > 0x60) {
        //ref https://moddingwiki.shikadi.net/wiki/S3M_Format
        //from https://github.com/OpenMPT/openmpt/blob/master/soundlib/S3MTools.h & Load_s3m.cpp
        // & http://asle.free.fr/prowiz/prowiz.src.zip / r/S3M.c
        bDetected = true;
        _V = Hex(File.read_uint16(0x28,_LE));
        sVersion = "v"+_V.substr(1,1)+"."+_V.substr(2,2);
        if(_V === "1300") sVersion = "v3.00"; else //because they're weird duh
        if(_V === "1320") sVersion = "v3.20"; else
        if(_V === "1301") sVersion = "v3.01";
        switch(_V.substr(0,1)) {
            case "1": sName = "ScreamTracker 3 module (.S3M)"; break;
            case "2": sName = "Imago Orpheus module (.S3M)"; break;
            case "3": sName = "Impulse Tracker module (.S3M)"; break;
            case "4":
                if(_V === "4100") {
                    sName = "BeRoTracker module (.S3M)"; sVersion = "v.2004-2012" }
                else
                    sName = "Schism Tracker module (.S3M)";
                break;
            case "5":
                if(_V=== "4754") {
                    sName = "GraoumfTracker module (.S3M)"; sVersion = "" }
                else
                    sName = "OpenMPT module (.S3M)"; break;
            case "6": sName = "BeRoTracker module (.S3M)"; break;
            case "7": sName = "CreamTracker module (.S3M)"; break;
            case "C":
                if(_V === "CA00") {
                    sName = "Camoto/libgamemusic module (.S3M)"; sVersion = ""; break }
            default: sName = "ScreamTracker-compatible module (.S3M)";
        }
        if(File.isVerbose()) {
            bad = "";
            if(!File.compare("1A",0x1C)) bad = bad.addIfNone("!badsig1a");
            z1 = File.read_uint16(0x1E); if(z1>0) bad = bad.addIfNone("!badsig0");
            ord = File.read_uint16(0x20,_LE); if((ord%2) == 1) bad = bad.addIfNone("!oddord");
            smp = File.read_uint16(0x22,_LE); ptn = File.read_uint16(0x24,_LE);
            gvol = File.read_uint8(0x30); spd = File.read_uint8(0x31);
            tmp = File.read_uint8(0x32); mvol = File.read_uint8(0x33);
            p = 0x60+ord+smp*2;
            max = sz = 0;
            for (i=0; i < ptn; i++) {
                var p1 = File.read_uint16(p+i*2) << 4;
                if(!p1) continue;
                if (p1 > max) { max = p1; sz = max + File.read_uint16(p1) }
            }
            if(sz%16) sz += 16-(sz%16);
            p = 0x60+ord;
            for(i=0; i < smp; i++) {
                si = File.read_uint16(p+i*2) << 4; if(!si) continue;
                if(File.read_uint8(si) === 1) {
                    sofs = File.read_uint16(si+14) << 4;
                    if (sofs > max) {
                        ssz = File.read_uint32(si+16);
                        if (File.read_uint8(si+31)&4) ssz *= 2;
                        if(ssz%16) ssz += 16-ssz%16; //the padding might be important
                        max = sofs; if(sz < max+ssz) sz = max+ssz;
                    }
//_log("sofs:"+Hex(sofs)+" ["+Hex(ssz)+"] -> "+Hex(sofs+ssz)+", max="+Hex(max))
                }
            }

            if(bad != "") sVersion += "/malformed"+bad;
            sOptionT(File.read_codePageString(0,0x1C,"CP1252"));
            ch = 1 + (mvol >> 7); mvol &= 0x7F;
            sOption("ch:"+ch+" ord:"+ord+" ptn:"+ptn+" smp:"+smp
                +" gvol:"+gvol+" mvol:"+mvol+" r/s:"+(tmp/spd).toFixed(1)+" sz:"+outSz(sz));
        }
    }
    else if(File.compare("'S98'3.")) {
        //ref https://www.purose.net/befis/download/lib/t98/ins98131s.zip / s98spec1.txt,
        //    http://www.purose.net/befis/download/kmp/old/s98spec2.txt,
        //    https://github.com/rururutan/s98spec3/blob/master/s98spec3-ja.txt
        sName = "S98 tune (.S98)"; bDetected = true;
        sV = File.read_ansiString(3,1); sVersion = "v"+sV;
        if(File.isVerbose()) {
            a=""; c="";s=""; g=""; t=""; y="";
            switch(sV) {
                case "0": case "1": case "2":
                    p = File.read_uint32(0x10,_LE);
                    if(p > 0)
                        sOptionT(File.read_ansiString(p,0x40));
                    pk = File.read_uint32(0x0C,_LE);
                    if(pk) sVersion += " compressed";
                    break;
                case "3":
                    ptags = File.read_uint32(0x10,_LE);
                    if(ptags > 6 && File.read_ansiString(ptags,5)=="[S98]") {
                        ptags+=5;
                        bUTF8 = File.compare("EFBBBF",ptags); //if BOM's there
                        if(bUTF8) tags = File.read_codePageString(ptags+3,0x200,"UTF8"); //test whether this parses BOM too
                        else tags = File.read_codePageString(ptags,0x200,"Shift_JIS");
                        tagl = tags.split('\x0A');
                        for (i=0; i < tagl.length; i++) {
                            tag = tagl[i].split("=");
                            switch (tag[0]) {
                                case "title": case "ｔｉｔｌｅ": t = tag[1]; break;
                                case "game": case "ｇａｍｅ": g = tag[1]; break;
                                case "system": case "ｓｙｓｔｅｍ": s = tag[1]; break;
                                case "artist": case "ａｒｔｉｓｔ": a = tag[1]; break;
                                case "year": case "ｙｅａｒ": y = tag[1]; break;
                                case"copyright": case "ｃｏｐｙｒｉｇｈｔ": if(a=="") a = tag[1]; break;
                                case"s98by": case "ｓ９８ｂｙ": if(a=="") a = tag[1]; break;
                                case"comment": case "ｃｏｍｍｅｎｔ": c = tag[1]; break;
                            }
                        }
                        sOption(t); sOption(a,"by: "); sOption(y,"'");
                        sOption(g,"for: "); sOption(s,"on: "); sOption(c);
                        dev = File.read_uint32(0x1C,_LE); if(!dev) dev = 1;
                        if(dev > 64) sVersion += "/malformed";
                        sOption("devices: "+dev)
                        break
                    } //if correct tag
            }//switch sV
        }//isVerbose
    }
    else if(File.compare("'SOARV1.0STBL'")) {
        //TODO unify all SAs, rewrite the check, and extract the lengths using this source:
        // https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Sonic_Arranger/Sonic Arranger_v1.asm
        sName = "Sonic Arranger module (.SA)"; sVersion = "v1.0"; bDetected = 1
    }
    else if(File.compare("4EFA002A 4EFA06FC 4EFA077C")) {
        sName = "Sonic Arranger module (.SA)"; sVersion = "old"; bDetected = 1
    }
    else if(File.compare("4EFA002A 4EFA06E4 4EFA0764")) {
        sName = "Sonic Arranger module (.SA)"; sVersion = "v.E464"; bDetected = 1
    }
    else if(File.compare("4EFA001A 4EFA06C6 4EFA073A")) {
        sName = "Sonic Arranger module (.SA)"; sVersion = "v.C63A"; bDetected = 1
    }
    else if(File.compare("'SAP'0D0A")) {
        sName = "Slight Atari Player module (.SAP)"; bDetected = true;
        if(File.isVerbose()) {
            bad = false; t=""; a=""; dt=""; tp=""; tm="";
            p = File.findSignature(3,TOEOF,"0D0AFFFF");
            if(p == 0) bad=true;
            else {
                taghunk = File.read_ansiString(5,p);
                if(taghunk.length > 5) {
                    tags = taghunk.split("\x0D\x0A");
                    for(i=0; i < tags.length; i++) {
                        tagdiv = tags[i].indexOf(" ");
                        tagl = tags[i].substr(0,tagdiv);
                        tagr = tags[i].substr(tagdiv+1,tags[i].length);
                        switch(tagl) {
                            case "NAME": if(tagr != '"<?>"') t = tagr.substr(1,tagr.length-2); break;
                            case "AUTHOR": if(tagr != '"<?>"') a = tagr.substr(1,tagr.length-2); break;
                            case "DATE": if(tagr != '"<?>"') dt = tagr.substr(1,tagr.length-2); break;
                            case "TYPE": sVersion = "v."+tagr; break;
                            case "TIME": tm = tagr; break
                        }
                    }
                }
            }
            if(bad) sVersion += "/malformed";
            sOption(t); sOption(a,"by: "); sOption(dt,"'"); sOption(tm,"len: ")
        }
    }
    else if(File.compare("'SAdT'")) {
        //ref http://www.adlibtracker.net/files/at2_sourcecode_09-09-2019.zip
        // in /adt2play/iloaders.inc -> sat_file_loader
        bDetected = true; nV = File.read_uint8(4); sVersion = "v"+nV;
        if(nV < 8)  sName = "Surprise! AdLib Tracker module (.SAT)";
        else sName = "Surprise! AdLib Tracker 2 module (.SA2)";
        if(File.isVerbose()) {
            if(nV < 5) {
                instnp = 0x15A;
                ptn = File.read_uint16(0x448,_LE);
                ord = File.read_uint8(0x44A);
                ticks = File.read_uint8(0x44C)
            } else { //v5,6, 8,9 are the same
                instnp = 0x1D6;
                ptn = File.read_uint16(0x446,_LE);
                ord = File.read_uint8(0x448);
                ticks = File.read_uint8(0x44A)
            }
            inst=[];
            for(i=0; i < 3 /*31*/; i++) //that's enough title info
                inst.push(File.read_ucsdString(instnp+i*17).trim());
            t = inst.join(" ").trim(); if(t != "") t+="...";
            sOption(t); sOption("rate:"+ticks+"Hz ord:"+ord+" ptn:"+ptn)
        }
    }
    else if(File.compare("'SC68 Music-file'")) {
        sName = "SC68 module (.SC68)"; bDetected = true;
        if(File.isVerbose()) {
            hdrl = File.read_ansiString(0,0x100).length+1;
            p = hdrl; //header skipped
            t=''; a=''; cp=''; st=''; df=-1; x=0; ef=false; mn = [];
            while (p < File.getSize()) {
                hkhd = File.read_ansiString(p,4);
                hksz = File.read_uint32(p+4,_LE);
                p += 0x08; //chunk header = 4xfourcc + 4xsize
                switch (hkhd) {
                    case "SC68": if(hksz+hdrl != File.getSize()) sVersion = "malformed"; hksz = 0; break;
                    case "SCFN": t = File.read_codePageString(p,hksz,"CP1252"); break;
                    case "SCDF": df = File.read_uint32(p,_LE)+1; break;
                    case "SCMN":
                        mn.push(File.read_codePageString("CP1252",p,hksz));
                        if(st=="" || df==mn.length)
                            st = mn[mn.length-1];
                        break;
                    case "SCAN": a = File.read_codePageString(p,hksz,"CP1252"); break;
                    case "SCCN": cp = File.read_codePageString(p,hksz,"CP1252"); break;
                    case "SCEF": ef = true; p = File.getSize(); break
                }
                p += hksz;
            }
            sOptionT(t); if(mn.length > 1) sOption(mn.length,"×");
            sOptionT(mn.join(";"));
            sOptionT(a,"by: "); sOptionT(cp,"(c)");
            if(!ef) sVersion = "malformed"
        }
    }
    else if(File.compare("'shro'020000")) {
        sName = "Mario Paint's Shroom module (.SHO)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(7,0x20));
            sOptionT(File.read_ansiString(0x27,0x20),"by: ")
        }
    }
    else if(File.compare("0000001C000000040000", 0x06)
      && File.compare("'SIDMON II - THE MIDI VERSION'", 0x3A)) {
        sName = "SidMon II module (.SID2)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(7,0x20));
            sOptionT(File.read_ansiString(0x27,0x20),"by: ")
        }
    }
    else if(File.compare("'PSID'") || File.compare("'RSID'")) {
        v1 = File.read_ansiString(0,1); bDetected = true;
        if(v1 == "P")  sName = "PlaySID chiptune (.SID, .PSID)";
        else sName = "RealSID chiptune (.SID, .RSID)";
        v2 = File.read_uint16(4,_BE);
        sVersion = "v"+Hex(v2);
        bad = 0;
        x = File.read_uint16(0x0E,_BE);
        if(x<1 || x>256) bad = 1;
        else if(x > 1) sOption(x,"×");
        startSong = File.read_uint16(0x10,_BE);
        if(startSong > x) bad = 2;
        dataOfs = File.read_uint16(0x06,_BE);
        if((v2==1 && dataOfs!=0x0076) || (v2==2 && dataOfs!=0x007C))
            bad = 3;
        loadAddr = File.read_uint16(0x08,_BE);
        if(v1=="R" && loadAddr>0 && (loadAddr<0x07E8)) bad = 4;
        initAddr = File.read_uint16(0x0A,_BE);
        if(v1=="R" &&
           ( initAddr<0x07E8 || (0xA000<=initAddr && initAddr<0xC000) || 0xD000<=initAddr))
            bad = 5;
        flags = File.read_uint16(0x76,_BE);
        if(v1=="R" && ((flags&2) >> 1) && initAddr>0)
            bad = 6;
        switch((flags&0x30)>>4) {
            case 1: sVersion += "/6581"; break;
            case 2: sVersion += "/8580"; break;
            case 3: sVersion += "/6581&8580"; break;
            default: sVersion += "/unk.chip"
        }
        switch((flags&0x0C)>>2) {
            case 1: sVersion += "/PAL"; break;
            case 2: sVersion += "/NTSC"; break;
            case 3: sVersion += "/PAL&NTSC"; break;
        }
        sidn = (dataOfs-0x7C)/2+1;
        if(sidn > 1) sVersion += "/"+sidn+"SID";
        if(bad > 0) sVersion += "/malformed"+bad;
        if(File.isVerbose()) {
            t = File.read_codePageString(0x16,0x20,"CP1252"); if(t == "<?>") t = ""; sOptionT(t);
            a = File.read_codePageString(0x36,0x20,"CP1252"); if(a == "<?>") a = ""; sOptionT(a,"by: ");
            c = File.read_codePageString(0x56,0x20,"CP1252"); if(c == "<?>") c = ""; sOptionT(c);
        }
    }
    else if(File.compare("00 FF00FF00 9100FF00 FF008000 92..00967F 01", 0x07)
      && File.compare("9908", 0x1542)) {
        sName = "Sound Images Generation 2 module (.SIG)"; bDetected = 1
    }
    else if(File.compare("0100FEFF09000000'ALIM3'")) {
        sName = "Skale Tracker module (.SKM)"; bDetected = true;
        if(File.isVerbose()) sOptionT(File.read_ansiString(0x19));
    }
    else if(File.compare("6000000A60000090600000'xM'F900DFF00047FA07FC'=|'000F0096'=|'00FF009E700641EBFFF810FC000051C8FFFA41EB0006'p-'117C00000034117C00000068117C0000009C10FC000051C8FFE87600162B00DF1743FFFB610001E241EBFFFA1082176B00DD00DB08B9000100BFE001'J+'00E1660808F9000100BFE001'NuG'FA078C1740FFFD1740FFFC177C0001FFFE'NuM'F900DFF0007E0347FA07'pK'FA07'l3'EB00D200DFF096'7|'000000D0177C000000DA'Jm'001A661041EB00E6'-H'00A0'=|'000100A4600C'=m'001A00A4'-m'001C00A0'J+'FFF8660E'J+'FFF96604610000B2610001E4DDFC00000010DBFC000000'4Q'CFFFBC33EB00D000DFF096'7|'820000D0'7|'000000D44DF900DFF0007E0347FA06F24BFA06EE'J+'FFF8660461000200610003D8DDFC00000010DBFC000000'4Q'CFFFE46100008C'0+'00D0806B00D233C000DFF096'7k'00D400D2'J+'FFFD6706'S+'FFFD'`4'102BFFFC12000201007F'g('1741FFFD4A006B0C'J+'00D9671A'S+'00D960140C2B004000D96608177C0000FFFC6004'R+'00D9'Nur'007000122BFFFA41EB02E8D1ED000010301800D1FC000004001B70180000'0r'00122B00DEC2C041EB1068D288'+A'0016'Nuv'00162BFFFB142BFFFA122BFFF9102BFFF8'J+'00DA6706122B00DE55015200B02B00DB'f6p'005401B22B00DE'f,r'00B42B00D767045202'` R'03B62B00E06616162B00DF177C0000FFFC176B00DD00DB177C000000D961121743FFFB1742FFFA1741FFF91740FFF8'NuA'EB00E843EB00D6780212F03800D1FC000000'@Q'CCFFF41A'08'0043EB00'1x'03D1FC0000004012B03800D3FC000000'4Q'CCFFEE'J+'FFFE'fRJ'056B08177C0000FFFC")) {
        sName = "Sound Master II module (.SMPRO)"; sVersion = "v1"; bDetected = 1
    }
    else if(File.compare("'<track'") && (File.findString(6,0x100,"rowhighliohtingminor=")>0)) {
        sName = "Picatune module v1 (.SMUFI) or v2 (.PT2)"; bDetected = true;
        if(File.isVerbose()) {
            s = File.read_codePageString(0,0x100,"UTF8");
            t = /.*name="([^"]*)".*/.exec(s);
            if(t != null) sOptionT(t[1]);
            a = /.*author="([^"]*)".*/.exec(s);
            if(a != null) sOptionT(a[1],"by: ");
            spd = /.*speed="([^"]*)".*/.exec(s);
            bpm = /.*bpm="([^"]*)".*/.exec(s);
            if(spd != null) sVersion+=" spd: "+spd[1];
            if(bpm != null) sVersion+=" bpm: "+bpm[1];
        }
    }
    else if(File.compare("'SNGs'")) {
        sName = "Sound Club module (.SN)"; sVersion = "v1"; bDetected = true;
        if(File.isVerbose()) sOptionT(File.read_ansiString(0x0F));
    }
    else if(File.compare("'SN2'") && File.compare("'NAM'",0x1F)) {
        sName = "Sound Club module (.SN)"; sVersion = "v2"; bDetected = true;
        if(File.isVerbose()) sOptionT(File.read_ansiString(0x26,File.read_uint32(0x22,_LE)));
    }
    else if(File.compare("'SNDH'",0x0C) && (File.isHeuristicScan() || File.compare("6000............6000"))) {
        //ref https://sndh.atari.org/files/sndhv21.txt
        sName = "Atari ST module (.SND,.SNDH)"; sVersion = "uncompressed"; bDetected = true;
        if(File.isVerbose()) {
            p = 0x10; title=artist=yr=""; totaltime = i = 0; x = 1;
            while(i < 10 && p < File.getSize()) {
                t = File.read_ansiString(p,4); if(t === "TIME") {
                    _log("time!")
                    for(j=0; j < x; j++) totaltime += File.read_uint16(p+4+j*2,_BE);
                    tlen = 4+x*2-1; if((p+tlen+1)%1) tlen++;
                }
                else { t = File.read_ansiString(p,Math.min(File.getSize()-p),0x100); tlen = t.length }
                if(tlen >= 4) {
                    hd = t.substr(0,4); tag = t.substr(4,tlen);
                    switch(hd) {
                        case "TITL": if(tag.substr(0,7) != "Unknown") title = tag; break;
                        case "COMM": if(tag.substr(0,7) != "Unknown") artist = tag; break;
                        case "YEAR": yr = tag; break;
                        case "HDNS": i = 8; break;
                        default: if(/##\d\d/.test(hd)) x = hd.slice(2,4);
                            else if(/#!\d\d/.test(hd)) if((p+tlen+1)%1) tlen++;
                            break;
                    }
                }
                p += tlen+1; i++
            }
            sOptionT(title); if(x > "01") sOption(x,"×"); sOptionT(artist,"by: "); sOptionT(yr,"'");
            if(totaltime) sOption(Math.floor(totaltime/60)+":"+(totaltime%60).padStart(2,'0'),"time: ");
        }
    }
    else if(File.compare("'FMC!'")) {
        sName = "Faust Music Creator module (.SNG)"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(4,20));
    }
    else if(/GTS[25!]/.test(File.read_ansiString(0,4))) {
        sName = "GoatTracker module (.SNG)"; bDetected = true;
        if([0x32,0x35].indexOf(File.read_uint8(3)) >= 0) sVersion = "v2"
        else sVersion = "v1";
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(4,0x20,"CP1250"));
            tc = File.read_uint8(0x64);
            if(tc > 1) sOption(tc,"x ");
            sOptionT(File.read_codePageString(0x24,0x20,"CP1250"),"by: ");
            sOptionT(File.read_codePageString(0x44,0x20,"CP1250"))
        }
    }
    else if(File.compare("'ObsM'")) {
        sName = "Jonne Valtonen's SNG Player module (.SNG)"; bDetected = true;
        if(File.read_uint8(0x0B)) sVersion += "compressed";
        len = File.read_uint16(4,_LE); if(len>File.getSize()) sVersion += "malformed";
        if(File.isVerbose()) {
            //ref https://github.com/adplug/adplug/blob/master/src/sng.cpp
            start = File.read_uint16(6,_LE);
            loop = File.read_uint16(8,_LE); delay = File.read_uint16(0x0A,_LE);
            sOption("len:"+Hex(len)+" start:"+Hex(start)+" loop:"+Hex(loop)+" delay:"+delay)
        }
    }
    else if(File.compare("'RJP'3.'SMOD'")) {
        sName = "Richard Joseph's module (.SNG)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(3,1)
    }
    else if(File.compare("'SYNC'") || File.compare("'SYNB'")) {
        sName = "Synder SNG-player module (.SNG)"; bDetected = true;
        sVersion = "ver."+File.read_ansiString(3,1);
        if(File.isVerbose()) sOption(File.read_ansiString(0x10,0x200))
    }
    else if(File.compare("'SYND'....'S0'")) {
        sName = "Synder Tracker module (.SNG)";  bDetected = true; //TODO unpack & read tags
        sVersion = "ver."+File.read_ansiString(3,1)
        //if(File.isVerbose()) sOption(File.read_ansiString(0x10,0x200))
    }
    else if(File.compare("'SYND'") || File.compare("'SYNF'") || File.compare("'SYNH'")) {
        sName = "Synder SNG-player Stereo module (.SNG)"; bDetected = true;
        sVersion = "ver."+File.read_ansiString(3,1);
        if(File.isVerbose()) sOption(File.read_ansiString(0x10,0x200))
    }
    else if(File.compare("'RJP'3. 0000 0000")) {
        sName = "Richard Joseph's module instruments (.INS)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(3,1)
    }
    else if(File.compare("'SNES-SPC700 Sound File Data'")) {
        // ref http://snesmusic.org/files/spc_file_format.txt   and snippets from
        // http://www.alpha-ii.com/Source/SAmp310s.rar/snes/id666.cpp, .h
        sName = "Nintendo SNES SPC module (.SPC)"; bDetected = true;
        sVersion = "v0."+File.read_uint8(0x24);
        if(File.isVerbose()) {
            id666inhdr = File.read_uint8(0x23)==0x1A;
            t=""; a=""; c=""; g=""; dumper=""; emu="";
            preferBin = false;
            if(id666inhdr){
                t = File.read_ansiString(0x2E,0x20);
                g = File.read_ansiString(0x4E,0x20);
                dumper = File.read_ansiString(0x6E,0x10);
                dumpdate = File.read_ansiString(0x9E,11);
                emu = File.read_uint8(0xD2);
                if(emu>=0x30 && emu<=0x39)
                    emu -= 0x30;
                switch(emu) {
                    case 1: emu = "ZSNES"; break;
                    case 2: emu = "Snes9x"; break;
                    case 3: emu = "ZST2SPC"; break;
                    case 4: emu = "ETC"; break;
                    case 5: emu = "SNEShout"; break;
                    case 6: emu = "ZSNESW"; break;
                    default: emu = ""
                }
                c = File.read_ansiString(0x7E,0x20);
                slen = File.read_ansiString(0xA9,3); lp=File.read_ansiString(0xAC,4);
                if((slen+lp+dumpdate).length < 5) {
                    chnDis = File.read_uint8(0xD1);
                    if(chnDis == 1 && emu == "") bin = true; else bin = preferBin;
                } else if( /[0-9/]*/.test(slen+lp+dumpdate) ) { //id666 text format
                    songlen = Number(slen); //in seconds
                    a = File.read_ansiString(0xB1,0x20);
                } else {
                    bin = true;
                    //check songlen bitness
                    songlen = File.read_uint8(0xA9)<<16+File.read_uint8(0xAA)<<8+File.read_uint8(0xAB); //in seconds
                    a = File.read_ansiString(0xB0,0x20);
                }
            }
            else
                t = File.read_ansiString(0x30,0x14);
            sOption(t); sOption(g,"for: "); sOption(a,"by: "); sOption(c);
            if(emu != "") sVersion += " "+emu;
        }
    }

    else if(File.compare("'STK1.0SONG'") && [1,2,3].indexOf(File.read_uint8(0x34)) >= 0
      && File.read_uint8(0x35) <= File.read_uint8(0x3A) && File.read_uint8(0x36) <= File.read_uint8(0x35)
      && File.read_uint8(0x38) <= 0x3F && File.read_uint8(0x39) <= 5) {
        // format details here: https://modland.com/pub/documents/format_documentation/STarKos%20(.sks).md
        sName = "STarKos module (.SKS)"; bDetected = true;
        debug = 0; bad = "";
        switch(File.read_uint8(0x39)) {
        case 0: hz = 13; break;  case 1: hz = 25; break;  case 2: hz = 50; break;
        case 3: hz = 100; break;  case 4: hz = 150; break;  case 5: hz = 300; break;
        default: hz = 0; bad = bad.addIfNone("!badRepFreq")
        }
        xpos = File.read_int8(0x37).toString(); if(xpos[0] != "-") xpos = "+"+xpos;
        spd0 = File.read_uint8(0x38); ord = 1+File.read_uint16(0x3A);
        ptn = -1; rows = ptnxpos = 0; p = 0x3C;
        for(i=0; i < ord*4; i++) {
            if((i%4) == 3) rows += File.read_uint8(p)+1;
            else { t = File.read_uint8(p); if(ptn < t) ptn = t; if(File.read_int8(p+1)>>1) ptnxpos++ }
            p += 2
        } ptn++;
        insns = [];
        const ifHard = 0x80, ifPitch = 0x40, ifArp = 0x20, ifNoiseEtc = 0x10;
        for(ins = 0; p < File.getSize() && ins < 0x100; ins++) {
            insn = File.read_uint16(p); p += 2;
            if(insn == 0xFFFF) break;
            ip = p; isz = File.read_uint16(ip); p += 4;
            iend = 1+File.read_uint8(p+2);
            p += 5;
            iname = File.read_ansiString(p,8).trim(); p += 8; if(iname != "") insns.push(iname);
            if(File.isDeepScan())
            for(l = 0; l < iend; l++) {
                x = File.read_uint8(p++);
                if(!x) continue;
                if((x & ifHard)) {
                    y = File.read_uint8(p++); if(x & 8) p++; if(y & 0x40) p++; if(x & 2) p++;
                    if(x & 4) p += 2; if(x & 0x10) p += 2; if(x & 0x20) p += 2
                } else {
                    if(x & ifNoiseEtc) { y = File.read_uint8(p++);
                        if(y & 0x40) { p += 2;
                            if(x & (ifArp | ifPitch)) { bad = bad.addIfNone("!badinsflags"); ins=l=0xFFFE; break }
                        }
                    }
                    if(x & ifArp) p++; if(x & ifPitch) p += 2
                }
            } else p = ip+isz;
        }
        for(sptn = 0; p < File.getSize(); sptn++) {
            curptn = File.read_uint16(p); p += 2; if(curptn == 0xFFFF) break;
            p += File.read_uint8(p)
        }
        if(File.isDeepScan()) notecnt = 0;
        if(p < File.getSize() && ins < 0xFFFE) for(i = 0; i <= ptn; i++) {
            curptn = File.read_uint16(p); p += 2;
            if(curptn != 0xFF && curptn != 0xFFFF && curptn > 0x200) { bad = bad.addIfNone("!badnptn"); break }
            if(curptn == 0xFFFF) break;
            pp = p; psz = File.read_uint16(pp);
            if(File.isDeepScan()) {
                p += 2; var pvol = pins = -1, lc = 0;
                while(p < Math.min(File.getSize(), pp+psz)) {
                    var ppitch = 0, lnp = p,
                        pfnote = pfvol = pfpitch = false;
                    x = File.read_uint8(p++);
                    if(x == 0xFF) break;
                    else if(x & 0x80) lc += x & 0x7F;
                    else if(x >= 0x60)
                        switch(x & 0xF) {
                        case 0: pfvol = true; pfpitch = false; pvol = 0xF - File.read_uint8(p++);
                            if(pvol < 0) { bad = bad.addIfNone("!badptnvol0"); i = ptn; p = pp+psz; break; }
                        case 1: pfvol = false; pfpitch = true; ppitch = - File.read_uint8(p++); break;
                        case 2: pfvol = pfpitch = true;  pvol = 0xF - File.read_uint8(p++);
                            if(pvol < 0) { bad = bad.addIfNone("!badptnvol2"); i = ptn; p = pp+psz }
                            ppitch = - File.read_uint8(p++); break;
                        case 3: pfnote = true; pnote = "rst"; break;
                        case 4: pfnote = true; pnote = "spl"; pins = File.read_uint8(p++); break;
                        }
                    else {
                        pfnote = true;
                        y = File.read_uint8(p++);
                        pfvol = !(y & 0x40);
                        if(pfvol) pvol = 0xF - (y & 0xF);
                        if(pins < 0 || !(y & 0x20)) pins = File.read_uint8(p++);
                        pfpitch = y & 0x10;
                        if(pfpitch) ppitch = - File.read_int8(p++);
                    }
                    if(pfnote) notecnt++;
                    lc++
                } //single pattern cycle
            } else p = pp+psz;
        } //patterns cycle
        if(p > File.getSize()) bad = bad.addIfNone("!short");
        if(bad != "") sVersion = sVersion.appendS("malformed"+bad,"/");
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x14,0x20));
            sOptionT(File.read_ansiString(0x0A,0x0A),"by: ");
            sOption("spd0:"+spd0+(xpos!="+0"?" xpos:"+xpos:"")+" ord:"+ord+" ptn:"+ptn+"+"+sptn+" ins:"+ins
                +(ptnxpos?" ptn.xpos:"+ptnxpos:"")+" smp.ch:"+File.read_uint8(0x34)+" rep.freq:"+hz+"Hz"
                +" loop:"+(File.read_uint8(0x36) ? Hex(File.read_uint8(0x36))+"-" : "")
                +Hex(File.read_uint8(0x35))+(File.isDeepScan()?" notes:"+notecnt:" rows:"+rows)+" sz:"+p)
        }
    }//.SKS
    else if(!File.read_uint8(0) && File.compare("'SK10'",0x80) && [1,2,3].indexOf(File.read_uint8(0x86)) >= 0
      && [13,25,50,100,150,300].indexOf(File.read_uint16(0x87)) >= 0 && File.read_uint16(0x18) == File.read_uint16(0x40)) {
        //from https://web.archive.org/web/20240331033458_nf/https://www.grimware.org/doku.php/documentations/software/starkos/start#tweaking.the.song
        sName = "STarKos module (.BIN)"; sVersion = "compiled/ofs:80h"; bDetected = true;
        crc = 0; for(i=0;i<67;i++) crc += File.read_uint8(i); crc &= 0xFFFF;
        if(crc != File.read_uint16(0x43)) sVersion += "/malformed!badCRC";
        if(File.isVerbose()) {
            sOption(File.read_ansiString(1,8).trim()+"."+File.read_ansiString(9,3).trim());
            sOption(File.read_uint16(0x87)+"Hz");
            sOption(Hex(File.read_uint16(0x84)),"base:");
            sOption(outSz(File.read_uint24(0x40)+0xBC),"sz:");
        }
    }
    else if(File.compare("'Nu!SOPROL!'", 0x22)) {
        sName = "Sound Programming Language module (.SPL)"; bDetected = true;
        if(File.isVerbose()) {
            t = File.read_ansiString(0x58,0x100);
            a = File.read_ansiString(0x58+t.length+1,0x100);
            c = File.read_ansiString(0x58+t.length+a.length+2,0x100);
            sOptionT(t); sOptionT(a,"by: "); sOptionT(c)
        }
    }
    if(File.compare("'SPM'0.")) {
        //ref http://aminet.net/mus/edit/stonefree1.lha
        sName = "Stonetracker tune file (.SPM)"; sVersion = "v"+File.read_uint8(3); bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(4,0x20));
            //TODO find pointer and read the extra stuff
        }
    }
    if(File.compare("'SPS'0.0.")) {
        sName = "Stonetracker samples file (.SPS)"; sVersion = "v"+File.read_uint8(3); bDetected = true;
        if(File.isVerbose()) {
            smp=File.read_uint8(5);
            sOption(smp,"smp:")
            for(i=0; i < Math.min(10,smp); i++)
                sOptionT(File.read_ansiString(0x20*i+6,8))
        }
    }
    else if(File.compare("'SPU'00") || File.compare("'SPU1'") ||
        (File.findSignature(File.getSize()-6,TOEOF,"1D80FF"))>-1) {
        bDetected = true; sName = "Eternal SPU music RAM log (.SPU)";
        if(File.compare("'SPU'")) {
            if(!File.read_uint8(3)) sVersion = "v0"; else sVersion = "v1"; }
        else sVersion = "headerless";
        if(File.isVerbose() && (sVersion != "headerless")) {
            game = File.read_codePageString(4,0x40,"Shift_JIS");
            title = File.read_codePageString(0x44,0x40,"Shift_JIS");
            sOptionT(title); sOptionT(game, "game: ");
            artist = File.read_codePageString(0x84,0x20,"Shift_JIS"); sOptionT(artist,"by: ");
            cmt = File.read_codePageString(0xA4,0xF00,"Shift_JIS"); sOptionT(cmt);
        }
    }
    else if(File.compare("'SPEEDY-SYSTEM'")) {
        bDetected = true; sName = "Speedy System module (.SS)"; sVersion = "v1"
    }
    else if(File.compare("'ZXAYST11'")) {
        sName = "Sound Tracker module (.ST1, .ST11)"; bDetected = true;
        sVersion = "v1.1/uncompiled";
        if(File.isVerbose()) {
            t_= File.read_uint8(0x17); t = File.read_ansiString(0x18,t_-1).trim();
            if(t != "Some SoundTracker Song") sOption(t);
        }
    }
    else if(File.compare("'KSA SOFTWARE COMPILATION OF '",0x0A) && (File.getSize()<0x2800)) {
        sName = "Sound Tracker Pro module (.STP)"; sVersion = "compiled"; bDetected = true;
        if(File.isVerbose())
            sOptionT(File.read_ansiString(0x26,0x19)); sOption("tempo: "+File.read_uint8(0))
    }
    else if(File.compare("'STP3'")) {
        sName = "Soundtracker Pro II module (.STP)"; bDetected = true;
    }
    else if(File.compare("48E7FFFE 4DFA.... 4A2E....")) {
        sName = "SUNtronic module (.SUN)"; bDetected = 1
    }
    else if(File.compare("'SVOX'00000000")) {
        sName = "SunVox module (.SUNVOX)"; bDetected = true;
        if(File.isVerbose()) {
            p=8;
            t=''; bpm=0; spd=0; tme=0; ptn=0; blk=0;
            while (p < File.getSize()) {
                hkhd = File.read_ansiString(p,4); p += 4;
                hksz = File.read_uint32(p,_LE); p += 4;
                switch(hkhd) {
                    case "VERS":
                        nV = File.read_uint32(p,_LE); aV=[];
                        for(i=0; i < 4; i++) aV[3-i] = (nV>>(8*i))%256;
                        sVersion += "v"+aV.join("."); break;
                    case "BVER":
                        nB = File.read_uint32(p,_LE);
                        if(nB != nV) { aV = [];
                            for(i=0; i < 4; i++) aV[3-i] = (nB>>(8*i))%256;
                            sVersion += "/v"+aV.join(".");
                        }
                        break;
                    case "NAME": t = File.read_ansiString(p,hksz); break;
                    case "BPM ": bpm = File.read_uint32(p,_LE); break;
                    case "SPED": spd = File.read_uint32(p,_LE); break;
                    case "TIME": tme = File.read_uint32(p,_LE); break;
                    case "SNAM": blk++; break;
                    case "PDTA": ptn++; break;
                }
                p += hksz;
            }
            sOptionT(t);
            if(bpm > 0) sOption(bpm,"bpm:"); if(spd > 0) sOption(spd,"spd:");
            if(tme > 0) sOption(tme,"time:"); if(ptn > 0) sOption(ptn,"ptn:"); if(blk > 0) sOption(blk,"blk:");
        }
    }
    else if(File.compare("'SymM'")) {
        //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_symmod.cpp
        sName = "Symphonie module (.SYMMOD)"; bDetected = true;
        sVersion = "v"+File.read_uint32(4,_BE);
        if(File.isVerbose()) {
            p=8;
            t=''; ef=false; ch=0; len=0; extsmp=false; pro=false;
            while (p<File.getSize()) {
                if(ef) break;
                hkhd = File.read_int32(p,_BE); hkhx = Hex(File.read_uint32(p,_BE)); hksz=4;
                p += 0x04; //chunk header = 4xtype (the following will change by chunk type)
                switch (hkhd) {
                    case -16: pklen=File.read_uint32(p,_BE); hksz+=pklen; o=4; //InfoText RLE-COMPRESSED YO
                      if(pklen>=10 && File.compare(p+o,"'PACK'FFFF")) { o+=6;
                        _log("yes we're FUCKING doing this. The algo's 100% from openMPT");
                        unplen = File.read_uint32(p+o,_BE); o+=4;
                        maxlen = pklen-10; if(4294967295/170 >= maxlen) maxlen *= 170; else maxlen = 4294967295;
                        if(unplen > maxlen) unplen = maxlen;
                        done = false; ofs = 0; left = unplen;
                        while(!done && o<hksz) {
                            tp = File.read_int8(p+o); o++;
                            switch(tp) {
                            case 0:
                                l = File.read_uint8(p+o); o++;
                                if(left >= l) {
                                    t += File.read_ansiString(p+o,l);
                                    o += l; left -= l;
                                } else done=true;
                                break;
                            case 1:
                                l = FIle.read_uint8(p+o); o++;
                                dw = File.read_ansiString(p+o,4); o += 4;
                                if(left >= (l*4) && (o<pklen)) {
                                    left -= l*4;
                                    while(l--) t += dw;
                                } else done=true;
                                break;
                            case 2:
                                dw = File.read_ansiString(p+o,4); o += 4;
                                if(left >= l*4 && o<pklen) {
                                    unp += dw+dw;
                                    left -= 8;
                                } else done = true;
                                break;
                            case 3:
                                l = File.read_uint8(p+o); o++;
                                if(left >= l) left-=l;
                                else done = true;
                                break;
                            case -1: done = true; break;
                            default: sVersion += "/malformed"; done = true; break;
                            }//switch
                        }
                      } else //if no RLE sig:
                      t=File.read_codePageString(p+o,hksz,"CP1252"); break;
                    case -1: ch = File.read_int32(p,_BE); break; //NumChannels
                    case -2: len = File.read_int32(p,_BE); if(len > 1024) len = "malformed"; break; //TrackLength
                    case -3: case -4: case -5: break;
                    case -7: extsmp = true; break; //ExternalSamples
                    case 10: case 11: case 12: pro=true; break; //Sample Boost/Detune/Phase
                    case -6: tmp = Math.round(1.24*Math.min(File.read_int32(p,_BE),800)); break; //Tempo
                    case -12: hksz = 0; break; //EmptySample
                    case -10: case -11: case -13: case -14: case -15: case -17: case -18:
                    case -19: case -20: case -21: hksz+=File.read_int32(p,_BE); break;
                    default: _log(Hex(p,8)+": "+hkhd+"/"+hkhx+" ("+Hex(hksz,8)+"): ?!?!?!?!?!");
                }
                p += hksz;
            }
        }
        sOption(t); sOption(ch,"ch:"); sOption(len,"len:"); sOption(tmp,"tmp:");
        if(extsmp) sOption("extsmp"); if(pro) sVersion+="/Pro"
    }
    else if(File.compare("'Synth'") && !File.compare("'esi'",5)) {
        sName = "Synthesis module (.SYN)"; bDetected = true;
        if(File.compare("'Synth'",0x1F0E)) {
            sVersion = "v"+File.read_ansiString(0x1F13,3);
            if(File.isVerbose()) {
                sOptionT(File.read_ansiString(0x1F32,0x1B));
                sOptionT(File.read_ansiString(0x1F4E,0x100))
            }
        } else {
            sVersion = "v"+File.read_ansiString(5,3);
            if(File.isVerbose()) {
                sOptionT(File.read_ansiString(0x24,0x1B));
                sOptionT(File.read_ansiString(0x40,0x100))
            }
        }
    }
    else if(File.compare("'SYNTRACKER-SONG:'00")) {
        sName = "SynTracker module (.SYNMOD)"; bDetected = true;
        if(File.isVerbose()) {
            t1 = File.read_codePageString(0x14,0x20,"CP1252"); sOptionT(t1,"title/inst: ");
            t2 = File.read_codePageString(0x34,0x20,"CP1252"); sOptionT(t2);
            t3 = File.read_codePageString(0x54,0x20,"CP1252"); sOptionT(t3);
        }
    }
    else if(File.compare("'T0AST'")) {
        // ref T0AST.src.zip/ SoundEnginePlayer.pas, synth.pas
        // Kudos to BeRo for the tracker sources
        //TODO calcsize, move away to deepscans
        sName = "The 0ok Amazing Synth Tracker module (.T0AST)"; bDetected = true;
        if(File.compare("'0OK'",5)) { nV = 1; sVersion = "v1"; } else
        if(File.compare("010001",5)) { nV = 2; sVersion = "v2"; } else
        { nV = -1; sVersion = "v.unk"; }
        if(File.isVerbose() && nV != -1) {
            if(nV == 1) p = 0x1DA;
            else if(nV == 2) p = 0x34A;
            else break;
            drummode = File.read_uint8(p++);
            chipmode = File.read_uint8(p++);
            chs = File.read_uint8(p++); ch = 0;
            if(chs > 16) { sVersion += "/malformed"; break; }
            for(i=0; i<16; i++)
                if(File.read_uint8(p++)) { ch++; p += 3; }
            ins = 0;
            for(i=0; i<16; i++)
                if(File.read_uint8(p++)) { ins++; p += 25; }
            ord = File.read_uint16(p,_LE); p += 2+ord;
            ptn = 0; notes = 0;
            for(k=0; k<=255; k++)
                if(File.read_uint8(p++)) { //gotta do what you gotta do
                    ptn++;
                    for(i=0; i<chs; i++) {
                        j = 0;
                        while(j < 64) {
                            b = File.read_uint8(p++);
                            if(b & 0x80) { //RLE!
                                c = b & 0x7F;
                                if(c) {
                                    p++;
                                    while(c && j < 64) { notes++; j++; c--; }
                                }
                                else j++;
                            }
                            else { //just skipping the note parameters
                                if(b&0x01) p++; if(b&0x02) p++; if(b&0x04) p++;
                                if(b&0x08) p++; if(b&0x10) p++; if(b&0x20) p++;
                                if(b&0x40) {
                                    c = File.read_uint8(p++);
                                    while(c && j < 64) { notes++; j++; c--; }
                                }
                                else { notes++; j++; }
                            }
                        }
                    }
                }
            loop = File.read_uint8(p++);
            t_ = File.read_uint32(p,_LE); p += 4;
            t = File.read_ansiString(p,t_); p += t_;
            a_ = File.read_uint32(p,_LE); p += 4;
            a = File.read_ansiString(p,a_); p += a_;
            c_ = File.read_uint32(p,_LE);
            c = File.read_ansiString(p+4,c_);
            sOption(t);
            sOption(a,"by: ");
            sOption(c);
            sOption("ch:"+ch+" ins:"+ins+" ord:"+ord+" ptn:"+ptn+" notes:"+notes+" loop:"+loop);
        }
    }
    else if(File.compare("'T0ASTINS")) {
        sName = "The 0ok Amazing Synth Tracker instrument file"; bDetected = 1
    }
    else if(File.compare("'AN COOL'")) {
        //ref ftp://ftp.scene.org/pub/resources/gotpapers/manuals/tcb_tracker_1.0_manual_1990.pdf
        sName = "-TCB TRACKER- module (.TCB)"; bDetected = true;
        if(File.isVerbose())
            sOption("ptn:"+File.read_uint32(8,_BE)+" tempo:"+File.read_uint8(0x0C));
    }
    else if(File.compare("'TFMD'")) {
        sName = "TFM Music Maker tune (.TFD)"; bDetected = true;
        if(File.isVerbose()) {
            p = 4;
            t = File.read_ansiString(p,0x200); //I've seen CP1251 and CP936 already, nobody seems to care
            p = File.findSignature(p,0x200,"00")+1;
            a = File.read_ansiString(p,0x200);
            p = File.findSignature(p,0x200,"00")+1;
            c = File.read_ansiString(p,0x200);
            sOptionT(t); sOptionT(a,"by: "); sOptionT(c);
        }
    }
    else if(File.compare("'TFMfmtV2'")) {
        sName = "TFM Music Maker module (.TFE)"; bDetected = true;
        if(File.isVerbose()) {
            p = 0x13;
            t = File.read_ansiString(p,0x40); //I've seen CP1251 and CP936 already, nobody seems to care
            p = File.findSignature(p,0x40,"00")+1;
            a = File.read_ansiString(p,0x40);
            p = File.findSignature(p,0x200,"00")+1;
            c = File.read_ansiString(p,0x200);
            sOptionT(t); sOptionT(a,"by: "); sOptionT(c);
            d1 = File.read_uint16(0x0D,_LE); d2 = File.read_uint16(0x0F,_LE);
            spd1 = File.read_uint8(8); spd2 = File.read_uint8(9);
            intlv = File.read_uint8(0x0A); ptn0len = File.read_uint8(0x060);
            sOption(
              //"created: "+Hex(d1)+", modified: "+Hex(d2)+ //TODO figure out the dates
              "save count: "+File.read_uint8(0x11));
            sOption("spd0:"+spd1+"/"+spd2+" intlv:"+intlv);
        }
    }
    else if(File.compare("0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F0000050F", 0x14)
      && File.compare("FFFF001000000030000000", 0x181)) {
        sName = "The Musical Enlightenment module (.TME)"; bDetected = 1
    }
    else if(File.getSize() >= 0x3000 && File.compare("'TRK01/TV.ES.'")) {
        sName = "RamTracker module (.TRK)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(0x0F,0x20,"CP850"));
            sOptionT(File.read_codePageString(0x2F,0x20,"CP850"),"by: ");
            p = 0x453; ptn = -1; ord = 0;
            for(;;) { t = File.read_uint8(p++); if(t >= 0xFE || p > File.getSize()) break;
                ord++; if(ptn < t) ptn = t }
            ptn++;
            sOption("ord: "+ord+" ptn:"+ptn)
        }
    }
    else if(File.compare("'S'8F'NG.'")) {
        sName = "Unique Development module (UDS.+SMP.)"; bDetected = 1
    }
    else if((File.getSize() > 0x133) && File.compare("'MAS_UTrack_V00'")) {
        //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_ult.cpp
        //TODO calcsize
        sName = "UltraTracker module (.ULT)"; bDetected = true;
        nV = File.read_uint8(0X0E);
        if(nV<0x31 || nV>0x34) sVersion = "malformed";
        else {
            sVersion = "v"+["<1.4","1.4","1.5","1.6"][nV-0x31];
            if(File.isVerbose()) {
                sOptionT(decAnsi(0x0F,0x20,CP437)); //TODO File.read_codePageString(0x0F,0x20,"CP437")
                msgn = File.read_uint8(0x2F);
                if(File.getSize() < msgn*0x20+0x50) sVersion += "/malformed";
                else { msg=[];
                    sOption(File.read_uint8(msgn*0x20+0x30),"smp:");
                    for(i=0; i < msgn; i++)
                      msg[i] = decAnsi(i*0x20+0x30,0x20,CP437).trim();
                    sOption(msg.join(" "));
                }
            }
        }
    }
    else if(/(UN0[4-6].*|APUN\x01[1-6])/.test(File.read_ansiString(0,6))) {
        // ref https://github.com/sezero/mikmod/blob/master/libmikmod/loaders/load_uni.c
        sName = "UNIMOD module (.UNI)"; bDetected = true;
        if(File.read_ansiString(3,1) != 'N') {
            v = +File.read_ansiString(3,1);
            if(v > 6) v = File.read_uint16(4,_LE);
            sVersion = "v"+v;
        }
        else {
            sVersion = "v.APlayer";
            v = 0x100;
        }
        if(File.isVerbose()) {
            p = 4;
            if(v >= 6) {
                if(v == 6) p++;
                flg = File.read_uint16(p,_LE); p+=2;
                ch = File.read_uint8(p++);
                voc = File.read_uint8(p++);
                pos = File.read_uint16(p,_LE); p+=2;
                ptn = File.read_uint16(p); p+=2;
                trk = File.read_uint16(p); p+=2;
                ins = File.read_uint16(p); p+=2;
                smp = File.read_uint16(p); p+=2;
                reppos = File.read_uint16(p); p+=2;
                spd0 = File.read_uint8(p++);
                tmp0 = File.read_uint8(p++);
                vol0 = File.read_uint8(p++);
                if(v >= 0x106) { bpmlimit = File.read_uint16(p,_LE); p+=2; }
                  else bpmlimit = 32;
            }
            else {
                ch = File.read_uint8(p++);
                pos = File.read_uint16(p,_LE); p+=2;
                if(v == 5) { reppos = File.read_uint16(p,_LE); p+=2; }
                  else reppos = 0;
                ptn = File.read_uint16(p,_LE); p+=2;
                trk = File.read_uint16(p,_LE); p+=2;
                ins = File.read_uint16(p,_LE); p+=2;
                smp = 0;
                spd0 = File.read_uint8(p++);
                tmp0 = File.read_uint8(p++);
                p += 256+32; // positions+panning
                flg = File.read_uint8(p++);
            }
            ts = File.read_uint16(p,_LE);
            sOptionT(File.read_ansiString(p+2,ts));
            if(v < 0x102) {
                porig = p+2+ts;
                origs = File.read_uint16(porig,_LE);
                if(origs>0) sOptionT(File.read_ansiString(porig+2,origs),"orig: ");
                p = porig+2+origs;
                sOptionT(File.read_ansiString(p+2,File.read_uint16(p,_LE)))
            }
            sOption("ch:"+ch+" trk:"+trk+" ord:"+pos+" ptn:"+ptn+" ins:"+ins+" smp:"+smp
              +" spd0:"+spd0+" tmp0:"+tmp0)
        }
    }
    else if(File.compare("'VGEfmtV'3.")) {
        sName = "Shiru's VGM Music Maker module (.VGE)"; bDetected = true;
        sV = File.read_ansiString(7,1); sVersion = "v"+sV;
    }
    else if( File.compare("'Vgm '") && ( !File.read_uint32(0x14,_LE) ||
        File.compare("'Gd3 '",File.read_uint32(0x14,_LE)+0x14) )) {
        //ref https://vgmrips.net/wiki/VGM_Specification
        // & https://vgmrips.net/wiki/GD3_Specification
        bDetected = true; bad = false;
        sName = "Video Game Music chiptune (.VGM)";
        eofofs = File.read_uint32(4,_LE)+4;
        sV = Hex(File.read_uint32(8,_LE),8);
        sVersion = "v"+sV.substr(5,1)+"."+sV.substr(6,2);
        if(File.isVerbose()) {
            tags = []; //11 of them
            gd3p = File.read_uint32(0x14,_LE)+0x14;
            if(gd3p > 0x14)
            if(File.read_ansiString(gd3p,4) === "Gd3 ") {
                sVersion += "/Gd3 v"+(readBytes(gd3p+4,4).join("")/100);
                taglen = File.read_uint32(gd3p+8,_LE); gd3p += 12;
                if(gd3p+taglen > File.getSize()) bad = true;
                i = 0;
                while(i < 11 && gd3p <= File.getSize()) {
                    tpos = File.findSignature(gd3p,TOEOF,"0000");
                    if(tpos >= 0) {
                        tags[i] = File.read_unicodeString(gd3p,taglen); //TODO rename to read_utf16String
                        gd3p += tags[i].length*2+2;
                    }
                    else {
                        tags[i] = ""; gd3p += 2
                    }
                    i++;
                }
                if(i < 11) bad = true; else tagn = Math.max(0,i-1);
                sOption(slashTag(tags[0],tags[1])); sOption(slashTag(tags[6],tags[7]),"by: ");
                sOption(slashTag(tags[2],tags[3]),"for: "); sOption(slashTag(tags[4],tags[5]),"on: ");
                sOption(tags[8],"date: "); sOption(tags[9],"ripper: "); sOption(tags[10],"notes: ")
            }
            psgclk = File.read_uint32(0x0C,_LE); //TODO add a chip list?
            ym2413clk = File.read_uint32(0x10,_LE);
            rate = File.read_uint32(0x24,_LE);
            if(sV >= "v1.10") {
                ym2612clk = File.read_uint32(0x2C,_LE);
                ym2151clk = File.read_uint32(0x30,_LE)
            }
            if(eofofs < File.getSize())
                if(File.read_ansiString(eofofs,4) == "Vgm ") sOption("multisong");
                else sOption("+extra data");
            sOption(File.read_uint32(0x18,_LE),"smp: ")
        }
        dataofs = File.read_uint32(0x34,_LE);
        if((sV >= "v1.50") && (!dataofs)) bad = true;
        if(bad) sVersion+= "/malformed";
    }
    else if(File.compare("00'3T1'")) {
        sName = "VicTracker module (.VT)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x19E,0x10));
            sOptionT(File.read_ansiString(0x1AE,0x10),"by: ");
            sOptionT(File.read_ansiString(0x1BE,0x10),"'");
        }
    }
    else if(File.compare("'Creative Voice File'1a1a")) { //TODO organise to a non-module file
        sName = "Creative Voice audio (.VOC)"; bDetected = 1
    }
    else if(File.findSignature(File.getSize()-0x20,TOEOF,"'VSS0'00") > -1) {
        //TODO thaaat's not a good way to detect a thing...
        sName = "Voodoo Supreme Synthesizer audio (.VSS)"; bDetected = 1
    }
    else if(File.compare("'Vortex Tracker II 1.0'")) {
        sName = "Vortex Tracker II audio (.VT2)"; bDetected = true;
        sVersion = "v"+File.read_ansiString(0x12,3).trim();
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(0x1E,0x20,"CP1251"));
            sOptionT(File.read_codePageString(0x42,0x20,"CP1251"),"by: ");
        }
    }
    else if(File.compare("6000") && File.compare("48E7FFFE610000",0x04)
        && File.compare("4CDF7FFF'Nu'",0x0C)) {
        sName = "Wally Beben's module (.WB)"; bDetected = 1
    }
    else if(File.compare("'WSRF'",File.getSize()-0x20) && File.compare("EA",File.getSize()-0x10)) {
        //ref http://daifukkat.su/docs/wsman/#wsr
        sName = "WonderSwan chiptune (.WSR)"; bDetected = true;
        p = File.getSize()-0x20;
        sVersion = "v"+File.read_uint8(p+4);
        if(File.isVerbose()) {
            sOption(Hex(File.read_uint8(p+0x18)),"CartID:")
            pub = File.read_uint8(p+0x16);
            switch(pub) {
            case 0: sOption("(invalid publisher)"); break;
            case 1: sOption("Bandai"); break;
            case 2: sOption("Taito"); break;
            case 3: sOption("Tomy"); break;
            case 4: sOption("Koei"); break;
            case 5: sOption("Data East"); break;
            case 6: sOption("Asmik Ace"); break;
            case 7: sOption("Media Entertainment"); break;
            case 8: sOption("Nichibutsu"); break;
            case 0x0A: sOption("Coconuts Japan"); break;
            case 0x0B: sOption("Sammy"); break;
            case 0x0C: sOption("Sunsoft"); break;
            case 0x0D: sOption("Mebius"); break;
            case 0x0E: sOption("Banpresto"); break;
            case 0x10: sOption("Jaleco"); break;
            case 0x11: sOption("Imagineer"); break;
            case 0x12: sOption("Konami"); break;
            case 0x16: sOption("Kobunsha"); break;
            case 0x17: sOption("Bottom Up"); break;
            case 0x18: sOption("Kaga Tech"); break;
            case 0x19: sOption("Sunrise"); break;
            case 0x1A: sOption("Cyber Front"); break;
            case 0x1B: sOption("Mega House"); break;
            case 0x1D: sOption("Interbec"); break;
            case 0x1E: sOption("Nihon Application"); break;
            case 0x1F: sOption("Bandai Visual"); break;
            case 0x20: sOption("Athena"); break;
            case 0x21: sOption("KID"); break;
            case 0x22: sOption("HAL Corporation"); break;
            case 0x23: sOption("Yuki Enterprise"); break;
            case 0x24: sOption("Omega Micott"); break;
            case 0x25: sOption("Layup"); break;
            case 0x26: sOption("Kadokawa Shoten"); break;
            case 0x27: sOption("Shall Luck"); break;
            case 0x28: sOption("Squaresoft"); break;
            case 0x2B: sOption("Tom Create"); break;
            case 0x2D: sOption("Namco"); break;
            case 0x2E: sOption("Movic(?)"); break;
            case 0x2F: sOption("E3 Staff(?)"); break;
            case 0x31: sOption("Vanguard"); break;
            case 0x32: sOption("Megatron"); break;
            case 0x33: sOption("Wiz"); break;
            case 0x34: sOption("Capcom"); break;
            default: sOption("(unknown publisher)")
            }
            sOption(File.read_uint8(p+5),"1sttrk: ");
        }
    }
    else if(File.compare("3026b2758e66cf11a6d900aa0062ce6c")) { //TODO organise to a non-module file
        sName = "Windows Media (.WMV/WMA)"; bDetected = 1
    }
    else if(File.compare("'XAD!'")) {
        sName = "Exotic AdLib module (.XAD)"; bDetected = true;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0x04,0x24));
            sOptionT(File.read_ansiString(0x28,0x24),"by: ");
        }
    }
    else if(File.compare("'Extended Module: '") && File.compare("1A",0x25)
      && File.read_uint16(0x48) && File.read_uint16(0x48) <= 256) {
        //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_xm.cpp et al.
        debug = 1; V = File.read_uint16(0x3A); charset = "CP437";
        sName = "Fast Tracker 2 Extended module (.XM)"; bDetected = true;
        text = tracker = mVlsw = bad = ""; insns = []; smpns = [];
        if(File.isDeepScan()) {
            var hdrp = 0x3C, hdrsz = File.read_uint32(hdrp),
              restartp = File.read_uint16(hdrp+6), flags = File.read_uint16(hdrp+0x0E),
              linearSlides = flags&1, xFilter = flags&0x1000;
            ord = File.read_uint16(hdrp+4); chn = File.read_uint16(hdrp+8);
            ptn = File.read_uint16(hdrp+0x0A); ins = File.read_uint16(hdrp+0x0C);
            tmp0 = File.read_uint16(hdrp+0x10); bpm0 = File.read_uint16(hdrp+0x12);
            var ptnhdp = p = hdrp+hdrsz;
           if(V >= 0x104) for(i=0; i < ptn; i++) p += File.read_uint32(p)+File.read_uint16(p+7);
            //version detection pt.1/9001
            const verUnknown = 0x00, verOldModPlug = 0x01, verNewModPlug = 0x02,
            verModPlug1_09 = 0x04, verOpenMPT = 0x08, verConfirmed = 0x10,
            verFT2Generic = 0x20, verOther = 0x40, verFT2Clone = 0x80,
            verDigiTrakker = 0x100, verUNMO3 = 0x200, verEmptyOrders = 0x400;
            var isMadTracker = isOMPTMade = isOXM = mixlevCompatFT2 = false;
            madewith = verUnknown;
            if(File.compare("'FastTracker v2.00   '",0x26) && hdrsz === 276) {
                if(V < 0x104) madewith = verFT2Generic | verConfirmed;
                else if(File.findSignature(0x11,0x14,"00") > -1)
                    madewith = verFT2Clone | verNewModPlug | verEmptyOrders;
                else
                    madewith = verFT2Generic | verNewModPlug;
            }
            else if(File.compare("'FastTracker v 2.00  '",0x26)) madewith = verOldModPlug;
            else {
                madewith = verUnknown | verConfirmed;
                tracker = File.read_codePageString(0x26,0x14,charset).trim();
                if(File.compare("'OpenMPT '",0x26)) madewith = verOpenMPT | verConfirmed | verEmptyOrders;
                else if(File.compare("'MilkyTracker '",0x26)) { if(!File.compare("'       '",0x32)) mixlevCompatFT2 = true }
                else if(File.compare("'Fasttracker II clone'",0x26)) madewith = verFT2Generic | verConfirmed;
                else if(File.compare("'MadTracker 2.0'00",0x26)) isMadTracker = true;
                else if(File.compare("'*Converted '",0x26)) madewith = verDigiTrakker;
            }
            if(xFilter && madewith == (verFT2Generic | verNewModPlug))
                madewith = verFT2Clone | verNewModPlug | verConfirmed;
            //back on track
            smp = 0; var insp = p, itype = -1;
            var anyADPCM = smpReserved = smpsz = 0, sflags = [];
            if(!ord && !verEmptyOrders) ord = 1; //a fix for dark lighthouse.xm
            for(i=0; i < ins; i++) {
                var ihdsz = File.read_uint32(p); if(!ihdsz) ihdsz = 263;
                inst = File.read_codePageString(p+4,0x16,"CP437").trim();
                if(inst.length) insns.push(inst);
                var smpn = File.read_uint16(p+0x1B), smphdsz = File.read_uint32(p+0x1D);
                if(madewith == verOldModPlug) { madewith |= verConfirmed;
                    if(ihdsz == 245) { mVlsw = "1.00.00.A5"; tracker = "ModPlug Tracker 1.0 alpha" }
                    else if(ihdsz == 263) { mVlsw = "1.00.00.B3"; tracker = "ModPlug Tracker 1.0 beta" }
                    else madewith = verUnknown | verConfirmed;
                } else if(!smpn) {
                    if(ihdsz == 263 && !smphdsz && (madewith & verNewModPlug)) madewith |= verConfirmed;
                    else if(ihdsz != 29 && (madewith & verDigiTrakker)) madewith &= ~verDigiTrakker;
                    else if((madewith & (verFT2Clone|verFT2Generic)) && ihdsz != 33) madewith = verUnknown;
                }
                if(itype == -1) itype = File.read_uint8(p+0x1A);
                else if(itype != File.read_uint8(p+0x1A) && (madewith & verFT2Generic)) {
                    madewith &= ~verFT2Generic; madewith |= verFT2Clone;
                }
                var midichecks = File.read_uint8(p+0xD0) | File.read_uint8(p+0xD1)
                  | File.read_uint16(p+0xD2) | File.read_uint8(p+0xD6);
                p += ihdsz; smp += smpn;
                if(p > File.getSize()) { bad = bad.addIfNone("!short"); break }
                if(V >= 0x104) sflags = [];
                var ssizes = [], slens = 0;
                if(smpn) {
                    if(midichecks) madewith &= ~(verOldModPlug | verNewModPlug);
                    for(j=0; j < smpn; j++) {
                        slen = slens[j] = File.read_uint32(p);
                        var sf = File.read_uint8(p+0xE), sr = File.read_uint8(p+0x11),
                          sname = File.read_codePageString(p+0x12,22,"CP437").trim(),
                          isADPCM = sr === 0xAD && (!sf & !(sf&0x30)); smpReserved |= sr;
                          if(sname.length) smpns.push(sname);
                        sflags.push([sf, isADPCM]); if(isADPCM) anyADPCM = true;
                        slens += isADPCM ? 16+(slen+1 >> 1) : slen;
                        p += 40; //p+=smphdsz; //considering there are files with smphdsz = 0...
                        if((sf&3) == 3 && (madewith&verNewModPlug))
                            madewith |= verModPlug1_09;
                    }
                    smpsz += slens;
                    if(V >= 0x104) { if(File.compare("'OggS'",p)) isOXM = true; p += slens }
                }
            }
            if(!smpReserved && (madewith&verNewModPlug) && File.findSignature(0x11,0x14,"00") > -1)
                madewith |= verConfirmed;
            if(V < 0x104) {
                for(i=0; i < ptn; i++)
                    p += File.read_uint32(p)+File.read_uint16(p+(V == 0x102 ? 6 : 7));
                if(File.compare("'OggS'",p)) isOXM = true;
                p += smpsz
            }
            basesz = p;
            if(tracker == "") {
                if((madewith&verDigiTrakker) && !smpReserved && (itype ? itype : -1) == -1)
                    tracker = "Digitrakker";
                else if(madewith&verFT2Generic)
                    tracker = "FastTracker 2 or compatible";
                else tracker = "Unknown"
            }
            //catch plugins, settings, messages from various XM flavours
            fx = 0; xt = "";
            while(p+6 < File.getSize()) {
                t = File.read_ansiString(p,4); it = File.read_uint32(p);
                if(it && File.read_uint16(p+4)
                  && (t === "228\x04" || (it & 0x80808080) || !(it & 0x60606060))) {
                    //I haven't seen files with that to see how that goes so we'll just cut here!
                    break
                }
                if(t === "text") {
                    t = File.read_uint32(p+4); p += 8; xt = xt.append("t");
                    text = File.read_codePageString(p,t,"CP437").trim(); p += t;
                    madewith |= verConfirmed
                } else if(/F[0-9X]\d\d/.test(t)) {
                    t = File.read_uint32(p+4); if(p+8+t <= File.getSize()) { p += 8+t; fx++ }
                    madewith |= verConfirmed
                } else if(t === "MIDI") {
                    t = File.read_uint32(p+4); madewith |= verConfirmed;
                    if(p+8+t <= File.getSize()) { p += 8+t; xt = xt.append("m") }
                } else if(t === "CHFX" || t === "CNAM" || t === "PNAM") {
                    t = File.read_uint32(p+4); if(p+8+t <= File.getSize()) { p += 8+t; }
                    madewith |= verConfirmed
                } else if(t === "XTPM") {
                    p += 4; xt = xt.append("x"); madewith |= verConfirmed; isOMPTMade = true;
                    t = File.read_ansiString(p,4);
                    while(p+7 < File.getSize()) {
                        if(!File.read_uint8(p)) { p++; break }
                        code = File.read_ansiString(p,4); icode = File.read_uint32(p);
                        if(code === "STPM" || code === "228\x04"
                          || (icode & 0x80808080) || !(icode & 0x60606060))
                            break;
                        prsz = File.read_uint16(p+4); p += 6;
                        for(i=0; i < ins; i++) p += prsz }
                } else if(t === "STPM") {
                    p += 4; xt = xt.append("s"); madewith |= verConfirmed;
                    while(p+6 < File.getSize()) {
                        if(!File.read_uint8(p)) { p++; break }
                        if(File.compare("'VWSL'",p)) { v = 0;
                            function itV(v) { return v.slice(0,1)+'.'+v.slice(1,3)+'.'+v.slice(3,5)+'.'+v.slice(5,7) }
                            switch(File.read_uint16(p+4)) {
                            case 1: v = File.read_uint8(p+6); break; case 2: v = File.read_uint16(p+6); break;
                            case 3: v = File.read_uint24(p+6); break; case 8: v = File.read_uint64(p+6); break;
                            default: v = File.read_uint32(p+6) }
                            if(v) mVlsw = itV(v.toString(16).toUpperCase().padStart(7,'0')); break;
                        }
                        p += 6+File.read_uint16(p+4)
                    }
                } else break;
            }
            if(madewith&verConfirmed)
                if(madewith&verModPlug1_09) {
                    mVlsw = "1.09"; tracker = "ModPlug Tracker 1.09";
                } else if(madewith&verNewModPlug) {
                    mVlsw = "1.16"; tracker = "ModPlug Tracker 1.10 - 1.16";
                }
            if(File.compare("'OpenMPT '",0x26)) {
                mVlsw = File.read_ansiString(0x2E,12).trim(); madewith = verOpenMPT|verConfirmed;
            }
            if(isOMPTMade && mVlsw < "1.17") mVlsw = "1.17";
            if(mVlsw >= "1.17") tracker = "OpenMPT v"+mVlsw;
            sz = p;
            charset = (mVlsw != "" || isMadTracker) ? "CP1252" : "CP437";
        }
        else tracker = File.read_codePageString(0x26,0x14,charset).trim();
        if(File.isVerbose()) {
            sOptionT(File.read_codePageString(0x11,0x14,charset));
            sOptionT(tracker,"in:");
            if(File.isDeepScan() && isOXM) sOption("OggMod FastTracker 2 (.OXM)","via:");
            if(text.length) sOption(addEllipsis(text,0x100));
            if(insns.length) sOption(addEllipsis(insns.join(" "),0x100),'ins/msg:"','"');
            if(smpns.length) sOption(addEllipsis(smpns.join(" "),0x100),'smp/msg:"','"')
            if(File.isDeepScan()) {
                var info = "chn:"+chn+" ord:"+ord+" ptn:"+ptn+" ins:"+ins+" smp:"+smp;
                if(fx) info += " fx:"+fx; if(xt.length) info += " xt:"+xt;
                if(sz != basesz) info += " base_sz:"+basesz; info += " sz:"+outSz(sz); sOption(info)
            }
        }
        sVersion = "v"+(V>>8)+"."+(V&0xFF)+bad+(anyADPCM?"/ADPCMpacked":"");
    }

    else if(File.compare("'FORM'........'XDIRINFO'")) {
        bDetected = true;
        sName = "Extended MIDI chiptune (.XMI)";
        if(File.isVerbose())
            sOption(File.read_uint16(0x14,_LE),"×")
    }
    else if(File.compare("'YM'3.'!'") || File.compare("'YM3b!'")
          || File.compare("'YMT'3.'LeOnArD!'") || File.compare("'MIX1LeOnArD!'")) {
        //ref https://github.com/cpcsdk/libstsound/blob/master/Ymload.cpp
        bDetected = true; bad = 0; frm = 0; smp = 0; voc = 0; loop = 0; ddn = 0;
        sName = "ST-Sound chiptune (.YM)";
        sV = File.read_ansiString(0,4).replace(/!/g,"");
        switch(sV) {
        case "YM1": sVersion = "YM1"; break;
        case "YM2": sVersion = "MADMAX specific"; break;
        case "YM3": sVersion = "YM-Atari"; break;
        case "YM3b": sVersion = "YM-Atari+loopinfo";
            loop=File.read_uint32(File.getSize()-4,_LE); break;
        case "YM4": sVersion = "YM-Atari extended"; break;
        case "YM5": case "YM6": sVersion = "Generic YM2149 extended"; break;
        case "MIX1": sVersion = "Atari Remix digital"; break;
        case "YMT1": case "YMT2": sVersion = "YM-Tracker"; break;
        }
        if(["YM2","YM3","YM3b"/*,"YM4"*/].indexOf(sVersion) >= 0) {//No YM4 in the wilderness, ignoring
            frm = (File.getSize-4)/14;
        }
        if(["YM5","YM6","YMT1","YMT2","MIX1"].indexOf(sV) >= 0) {
            if(File.read_ansiString(4,8)!="LeOnArD!") bad = 1;
            if((["YM6!","YMT1","YMT2"].indexOf(sV) >=0)
                && (File.read_ansiString(File.getSize()-4)!="End!")) bad = 2;
            //if(sV==="YM4") p=0x1C; else
            if(["YM5","YM6"].indexOf(sV) >= 0) {
                ddn = File.read_uint16(0x14,_BE);
                loop = File.read_uint32(0x1C,_BE);
                p = File.read_uint16(0x20,_BE)+0x22;
                for(i=0;i<ddn;i++) {
                    ds = File.read_uint16(p,_BE); p += 2+ds;
                    if(p >= File.getSize()) {bad = 3; break}
                }
            } else
            if(sV === "MIX1") {
                p = 0x18;
                smp = File.read_uint32(0x10,_BE);
                mixblk = File.read_uint32(0x14,_BE);
                for(i=0;i<mixblk;i++) p+=12; //u32 smpst,smplen; u16 repeat,rplfreq skipped
            } else
            if(["YMT1","YMT2"].indexOf(sV)>=0) {
                ddn = File.read_uint16(0x18,_BE);
                voc = File.read_uint16(0x0D,_BE);
                frm = File.read_uint32(0x10,_BE);
                p = 0x1E;
            }
            //read the three tags from here
            if(p>=File.getSize()) bad = 4;
            else {
                t = p;
                t_ = File.findSignature(p,TOEOF,"00")-p;
                if(t_ >= 0) {
                    p += t_+1; a = p;
                    a_ = File.findSignature(p,TOEOF,"00")-p;
                    p += a_+1; c = p;
                    c_ = File.findSignature(p,TOEOF,"00")-p;
                    p += c_+1;
                    if(["YM5","YM6"].indexOf(sV) >= 0) {
                        frm = File.read_uint32(0x0C,_BE);
                        if(frm<<4 != File.getSize()-p-4) {
                            bad = 5; sOption("frm/frames: "+(frm<<4)+"/"+(File.getSize()-p-4)) }
                    }
                }
            }
            if(File.isVerbose()) {
                sOptionT(File.read_ansiString(t,t_));
                sOptionT(File.read_ansiString(a,a_),"by: ");
                sOptionT(File.read_ansiString(c,c_));
            }
        }
        if(File.isVerbose()) {
            if(voc) sOption(voc,"voc:");
            if(smp) sOption(smp,"smp:");
            if(ddn) sOption(ddn, "digidrums:")
            if(frm) sOption(frm,"len:");
        }
        if(bad) sVersion+="/malformed"+bad;
    }
    else if(File.compare("'YMST'")) {
        bDetected = true;
        sName = "MYST ST-YM module (.YMST,.YM)";
        if(File.isVerbose()) {
            p=4;
            for(i=0;i<48;i++) {
                p+=8; if(File.read_uint16(p-8,_BE)==0) break;
            }
            t=p;
            t_=File.findSignature(p,TOEOF,"00")-p;
            if(t_>=0) {
                p+=t_+1; a=p;
                a_=File.findSignature(p,TOEOF,"00")-p;
                p+=a_+1; c=p;
                c_=File.findSignature(p,TOEOF,"00")-p;
                //p+=c_+1 //unused
                if(File.isVerbose()) {
                    sOptionT(File.read_ansiString(t,t_));
                    sOptionT(File.read_ansiString(a,a_),"by: ");
                    sOptionT(File.read_ansiString(c,c_));
                }
            }
        }
    }

//pure signature detection end


// AND NOW,
// THE HEAVIEST STUFF GOES DOWN HERE
// THE SANITY CHECK-STYLE DETECTION
// (* It's still hella fast tho, no worries)

function isProtoTracker() {
    if(!File.compare("'SONG'") ) return false;
    //ref https://github.com/kometbomb/prototracker/blob/master/doc/FORMAT.TXT
    //TODO tighten somewhat
    secsz = File.read_uint32(4,_BE);
    v = File.read_uint8(8); trk = File.read_uint8(9); p = 0xA;
    fxc = 0; if(v >= 17) { fxc = File.read_uint8(p); p++ }
    title = File.read_ansiString(p,0x100);
    if(!File.compare("'SEQU'",p+title.length+1+2)) return false;
    return true
}
if(!bDetected && isProtoTracker()) {
        sName = "ProtoTracker module (.SONG)";  bDetected = true;
        sVersion = sVersion = "v"+v;
        if(File.isVerbose()) {
            sOption(title);
            sOption("trk:"+trk+" fx:"+fxc)
        }
}

function isAnders0land() {
    //ref https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Anders0land/SRC_Anders0land/Anders 0land_v1.asm
    if(!File.compare("'mpl'")) return false;
    p = File.read_uint32(4,_BE); if(p > File.getSize() || (p&1)) return false;
    songp = p+8;
    if(!File.compare("'mdt'",p)) return false;
    p += File.read_int32(p+4,_BE); if(p > File.getSize() || (p&1)) return false;
    if(!File.compare("'msm'",p)) return false;
    p += File.read_int32(p+4,_BE); if(p < File.getSize()) return false;
    sz = p;
    smpp = songp+File.read_int32(songp-4,_BE);
    smpsz = File.read_int32(smpp-4,_BE);
    smp = (File.read_int16(songp+20,_BE) - File.read_int16(songp+18,_BE)) >> 2;
    songsz = songp-8;
    x = (File.read_int16(songp+4,_BE) - File.read_int16(songp+2,_BE)) >> 2;
    p = File.read_int16(songp,_BE);
    ord = File.read_int16(songp+6,_BE)-p-File.read_int32(songp+p+12,_BE);
    return true
}
if(!bDetected && isAnders0land()) {
    sName = "Anders 'Zonix' 0land's Music & Player module (.HOT)"; bDetected = true;
    sVersion = "v"+File.read_ansiString(3,1);
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("ord:"+ord+" smp:"+smp+" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz));
        sOption(outSz(sz),"sz:")
    }
}

function isMOD() {
    //from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mod.cpp
    if(File.getSize() < 0x43C) return false;
    var b = readBytes(0x438,4); var s = File.read_ansiString(0x438,4);
    var isStarTrekker = false; var isGenericMCh = false; var isMdKd = false;
    var maybeWOW = false;
    var ibtrsh = 40; //typical threshold for invalid bytes (in samples); _FRAGILE is 1

    //test signatures
    if(/(M.K.|M!K!|PATT|NSMS|LARD)/.test(s)) {
        chn = 4; tracker = "generic ProTracker-compatible";
        if(s === "M.K.") { isMdKd = true; maybeWOW = true } }
    else if(/(M&K!|FEST|N.T.)/.test(s)) {
        chn = 4; if(s === "N.T.") tracker = "NoiseTracker"; else tracker = "His Master's NoiseTracker" }
    else if(/O[KC]TA/.test(s)) { chn = 8; tracker = "Oktalyzer" }
    else if(/CD[68]1/.test(s)) { chn = b[2]-0x30; tracker = "Oktalyser (Atari)" }
    else if(b === [0x4D,0,0,0] || b === [0x38,0,0,0]) {
        if(b[0] === 0x38) chn = 8; else chn = 4; ibtrsh = 1; // fragile, needs more detects!
        tracker = "Inconexia demo" }
    else if(/FA0[4-8]/.test(s)) { chn = b[3]-0x30; tracker = "Digital Tracker" }
    else if(/(FLT|EX0)[4-9]/.test(s)) { chn = b[3]-0x30; isStarTrekker = true; tracker = "Startrekker" }
    else if(/CHN[1-9]/.test(s)) { chn = b[0]-0x30; isGenericMCh = true; tracker = "generic MOD-compatible" }
    else if(/[1-9][0-9]C[HN]/.test(s)) {
        chn = b[0]*10+b[1]-0x210; isGenericMCh = true; tracker = "generic MOD-compatible" }
    else if(/TDZ[1-9]/.test(s)) {
        chn = b[3]-0x30; tracker = "TakeTracker" }
    else return false;
    if(!chn) return false;
    isFLT8 = isStarTrekker && chn == 8;

    //test samples
    p = 0x14; wowsmpsz = 0; ib = 0;
    for(var i=0; i < 31; i++) {
        var ssz = File.read_uint16(p+0x16,_BE)*2; //length
        wowsmpsz += ssz;
        var sft = File.read_uint8(p+0x18);
        if(sft) maybeWOW = false; if(sft & 0xF0) ib++; //finetune <= 15
        var svol = File.read_uint8(p+0x19); if(svol > 0x40) ib++; //volume <= 64
        if(ssz && svol != 0x40) maybeWOW = false;
        if(File.read_uint16(p+0x1A,_BE)*2 > ssz) ib++; //loopstart. loopend gets weird, it's ok
        if(ib > ibtrsh) return false;
        p += 0x1E;
    }
    var smpsz31 = wowsmpsz;
    hdr = 0x43C/*1084*/; plist = 0x3B8/*952*/; msmp = 0x1F;
    if(File.read_uint8(plist-1)) { maybeWOW = false; wowsmpsz = 0 }

    //test patterns
    function cntBadPtnData(ofs) {
        mask = 0xE0; var c = 0;
        for(i = 0; i < chn*64; i++)
            if(File.read_uint8(ofs+i) & mask) c++;
        return c
    }
    function getNumPatterns(smpsz) {
        ord = File.read_uint8(plist-2); if(!ord) return false;
        if(ord > 128) ord = 128;
        if(tracker === "Digital Tracker") plist = 0x3BB;
        ptn = iptn = optn = 0; i = plist;
        for(i=0; i < 0x80; i++) {
            var p = File.read_uint8(plist+i);
            if(p < 0x80 && p >= ptn) { ptn = p+1; if(ord > i) optn = ptn }
            if(ptn >= iptn) iptn = ptn+1
        }
        //weirdness checks
        if(wowsmpsz && (wowsmpsz+hdr+ptn*8*256 == (File.getSize() & ~1))) {
            if(cntBadPtnData(hdr+ptn*4*256) < 16) chn = 8; //⚠no good for ripping -- best to err toward no WOW
        } else
        if(ptn != optn && cntBadPtnData(true,hdr+optn*chn*256) < 64) ptn = optn;
        if(iptn > ptn && hdr+smpsz+iptn*chn*256 == File.getSize()) ptn = iptn;
    }
    //modsize
    p = plist+0x80; getNumPatterns(smpsz31);
    if(maybeWOW && chn === 8) { tracker = "Mod's Grave"; isGenericMCh = true }
    sz = hdr+ptn*chn*256;
    if(sz > File.getSize()) return false;
    p = 0x14; smp = 0; songsz = sz; ib = 0; smpsz = 0; smps = [];
    while(msmp) {
        t = File.read_codePageString(p,20,"IBM850").trim(); if(t != "") smps.push(t);
        t = File.read_uint16(p+0x16,_BE)*2;
        if(t) smp++;
        smpsz += t; p += 0x1E; msmp--
    }
    sz += smpsz;
    return true
}
if(!bDetected && isMOD()) {
    sName = "Generic module (.MOD)"; bDetected = true;
    sVersion = File.read_ansiString(0x438,4);
    if(File.isVerbose()) {
        sOptionT(File.read_codePageString(0,0x14,"IBM850"));
        sOptionT(addEllipsis(smps.join(" "),200),'smp/msg:\"','"');
        sOption(tracker,"in:");
        sOption("chn:"+chn+" ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
    }
}

function isKRIS() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/ChipTracker/ChipTracker_v3.asm
    // & https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mod.cpp
    if(File.getSize() < 0x7C0+0x100 || !File.compare("'KRIS'",0x3B8)) return false;
    ord = File.read_uint8(0x3BC); if(ord > 0x80) return false;
    loop = File.read_uint8(0x3BD); if(loop > 0x7F) return false;
    const ibtrsh = 40; //typical threshold for invalid bytes (in samples); _FRAGILE is 1
    p = 0x16; var totalsmpsz = ib = synwf = 0; smps = [];
    for(var i=0; i < 31; p += 0x1E, ++i)
        if(!File.read_uint8(p)) {
            var maxwf = Math.max(File.read_uint8(p+1),File.read_uint8(p+5),File.read_uint8(p+10),File.read_uint8(p+19));
            if(maxwf+1 > synwf) synwf = maxwf+1;
        } else {
            t = File.read_codePageString(p,20,"IBM850").trim(); if(t != "" && t != "\x01") smps.push(t);
            var ssz = File.read_uint16(p+0x16,_BE)*2; //length
            totalsmpsz += ssz;
            var sft = File.read_uint8(p+0x18);
            if(sft & 0xF0) ib++; //finetune <= 15
            var svol = File.read_uint8(p+0x19); if(svol > 0x40) ib++; //volume <= 64
            if(File.read_uint16(p+0x1A,_BE)*2 > ssz) ib++;
            if(ib > ibtrsh) return false;
        }
    ptn = 0; p = 0x3BE;
    ord = File.read_uint8(0x3BC);
    for(i=0; i < (ord << 2); i++) {
        t = File.read_uint8(p); p += 2;
        if(ptn < t) ptn = t
    }
    ptn++;
    sz = 0x7C0+(synwf<<6)+(ptn<<8)+totalsmpsz;
    return true;
}
if(!bDetected && isKRIS()) {
    sName = "Krister Wombell's ChipTracker module (.KRIS,.MOD)"; bDetected = true;
    if(File.isVerbose()) {
        sOption(File.read_ansiString(0,16));
        sOptionT(addEllipsis(smps.join(" "),200),'smp/msg:\"','"');
        sOption("ord:"+ord+" ptn:"+ptn+" sz:"+outSz(sz));
    }
}

function isOctaMed() {
    //from http://fileformats.archiveteam.org/wiki/OctaMED_module_(MED)
    // & https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/ModuleConverters/ModuleConverter/Formats/Med4Format.cs
    // & https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/Med/MedWorker.cs
    if(!/MED[\x02-\x04]/.test(File.read_ansiString(0,4))) return false;
    nV = File.read_uint8(3); switch(nV) {
        //TODO find the non-\x04 files to test on
        case 2: sVersion = "v1.12"; break;
        case 3: sVersion = "v2.00"; break;
        case 4: sVersion = "v2.10+"; break;
    }
    x = 1; p = sz = ptn = ord = trk = midi = syhy = smp = realsmp = 0; smps = []; cs = bad = "";
    if(nV < 3) {
        //TODO
    } else {
        //going through the sample info
        var smpbmp0 = File.read_uint8(4); p = 5;
        while(smpbmp0) {
            if(smpbmp0&1) { var smpbmp = File.read_uint8(p++);
                while(smpbmp) { smp += smpbmp&1; smpbmp >>= 1 } }
            smpbmp0 >>= 1;
        }
        for(i=0; i < smp; i++) {
            fl = File.read_uint8(p++); smpnl = File.read_uint8(p++);
            smps.push(File.read_ansiString(p,smpnl)); p += smpnl;
            if(!(fl&1)) p+=2; if(!(fl&2)) p+=2; if(!(fl&4)) p++; if(!(fl&8)) p++;
            if(!(fl&0x30)) svol = File.read_uint8(p++); if(svol > 0x40) return false;
            if(!(fl&0x40)) p++;
        }
        for(i = 62; i >= 0; i--) if(smps[i] && smps[i].length) {realsmp = i+1; break }
        ptn = File.read_uint16(p,_BE); ord = File.read_uint16(p+2,_BE); if(!ord || ord > 0x100) return false;
        p += 4; for(i=0; i < ord; i++) if(File.read_uint8(p++) > ptn) return false;
        extsmp = !(File.read_uint8(p+3)&8);
        tmp0 = File.read_uint16(p,_BE)+"//"+File.read_uint16(p+4,_BE); p += 26;
        for(i=0; i < 16; i++) if(File.read_uint8(p++) > 0x40) return false;
        mvol = File.read_uint8(p++); if(mvol > 0x40) return false;
        if(p > File.getSize()) return false;
        function SkipMidi() { var f = File.read_uint32(p,_BE); p += 4;
            for (var i=0; i < 32; i++) { if(f < 0) f = -f; if((f & 0x80000000)) {midi++; p++ } f <<= 1 } }
        if(nV === 3) { SkipMidi(); SkipMidi() }
        for(i=0; i < ptn; i++) {
            var hdsz = File.read_uint8(p++);
            var ptntrk = File.read_uint8(p); if(trk < ptntrk) trk = ptntrk;
            var rows = File.read_uint8(p+1), hksz = File.read_uint16(p+2,_BE);
    //_log("ptn["+i+"] @"+Hex(p)+": ("+Hex(hdsz)+") -> "+Hex(p+hdsz+hksz)+"], rows:"+rows+" trk:"+ptntrk);
            p += hdsz+hksz;
        }
        if(!extsmp) {
            var br = new BitReader(p,_BE), smp2process = 0; br.read(1);
            for(i=0; i < realsmp; i++) smp2process += br.read(1); delete br;
    //_log("found "+smp2process+" samples to process");
            p += 8; for(i=0; i < smp2process; i++) {
                hksz = File.read_uint32(p,_BE); hktp = File.read_uint16(p+4,_BE); p += 6;
    //_log("smp["+i+"] ("+Hex(p)+" -> "+Hex(p+hksz)+")")
                if(hktp == 0xFFFF || hktp == 0xFFFE) syhy++; p += hksz;
            }
        }
        if(File.compare("'MEDV'",p))
            while(p+8 <= File.getSize() && /[A-Z]{4}/.test(File.read_ansiString(p,4))) {
                hkhd = File.read_ansiString(p,4); hksz = File.read_uint32(p+4,_BE); p += 8;
                switch(hkhd) {
                case "MEDV": sVersion = "v"+File.read_uint8(p+2)+'.'+File.read_uint8(p+3).padStart(2,'0'); break;
                case "ANNO": cs = File.read_codePageString(p,hksz,"CP1252"); break;
                case "HLDC": break;
                default: _log("Unknown MED header: "+hkhd) //shouldn't exist but you know how they get
                }
                p += hksz
            }
    }
    sz = p;
    if(sz > File.getSize()) bad = "!short";
    return true
}
if(!bDetected && isOctaMed()) {
    sName = "OctaMED module (.MED)"; bDetected = true;
    if(bad != "") sVersion += "/malformed"+bad;
    if(File.isVerbose()) {
        sOptionT(cs);
        while(smps[smps.length] == '') delete(smps[smps.length]);
        if(smps.length) sOption("["+smps.join(",")+"]","smps:");
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+(realsmp != smp ? "("+realsmp+")" : "")
          +(extsmp?"(ext.)":"")+(syhy?" synth+hybrid:"+syhy:"")+(midi?" midi:"+midi:"")+" trk:"+trk+" tmp0:"+tmp0
          +" mvol:"+mvol+" sz:"+outSz(sz))
    }
}

function isOctaMedMMD() {
    //from https://web.archive.org/web/20220109073109/http://www.textfiles.com/programming/FORMATS/med-form.txt
    if(!File.compare("'MMD'")) return false;
    nV = File.read_uint8(3)-48; if(nV < 0 || nV > 3) return false;
    switch(nV) {
        case 0: sname = "MED module (.MED,.MMD0)"; sversion = "v2.1 Med MoDule 0"; break;
        case 1: sname = "OctaMED Professional module (.MED,.MMD1)"; sversion = "v.3.00-4.x"; break;
        case 2: sname = "OctaMED Professional module (.MED,.MMD2)"; sversion = "v5.x"; break;
        case 3: sname = "OctaMED Professional module (.MED,.MMD3)"; sversion = "v6.x?"; break;
    }
    ptnsp = File.read_uint32(0x10,_BE); if(ptnsp < 0x34) return false;
    smpsp = File.read_uint32(0x18,_BE); if(smpsp && smpsp < 0x34) return false;
    expp = File.read_uint32(0x20,_BE); if(expp > File.getSize()) return false;
    sec = p = 0; sngp = File.read_uint32(p+8,_BE);
    if(sngp < 0x34 || sngp > 0xFFFFFEFF-63*8) return false;
    if(File.getSize() < Math.max(sngp+63*8+0x100, ptnsp, smpsp?smpsp:0x34, expp+0x34)) return false;
    songname = anno = iinfo = ""; ch = 4; xsngs = File.read_uint8(0x33); x = expp?xsngs+1:1;
    if(expp) {
        psongname = File.read_uint32(expp+0x2C,_BE);
        if(psongname) {
            songnamelen = File.read_uint32(expp+0x30,_BE);
            if(songnamelen)
                songname = File.read_codePageString(psongname,songnamelen,"CP1252");
        }
        pannotxt = File.read_uint32(expp+0x0C,_BE);
        if(pannotxt > 0) {
            annolen = File.read_uint32(expp+0x10,_BE);
            anno = File.read_codePageString(pannotxt,annolen,"CP1252");
        }
        pMMDInstrInfo = File.read_uint32(expp+0x14,_BE);
        if(pMMDInstrInfo > 0)
            iinfo = File.read_codePageString(expp,40,"CP1252");
    } //if expp
    ptn = 0; ord = []; var ptntp = File.read_uint32(0x10,_BE);
    for(i=0; i < x; i++) {
        p = sngp+63*8;
        ptn1 = File.read_uint16(p,_BE); if(ptn1 > 0x7FFF) ch = 4; ptn += ptn1;
        for(j = 0; j < ptn1; j++) {
            pj = j*4+ptntp; if(pj > File.getSize()) continue;
            pj = File.read_uint32(pj,_BE); if(pj > File.getSize()) continue;
            pj = nV < 1 ? File.read_uint8(pj+4) : File.read_uint16(pj+4,_BE); if(pj > ch) ch = pj
        }
        if(nV < 2) { ord[0] = File.read_uint16(p+2,_BE); if(ord[0] > 256) return false }
        else {
            sec = File.read_uint16(p+2,_BE); trk = File.read_uint16(p+0x10,_BE);
            if(!trk || trk > 0x40) return false;
            sectp = File.read_uint32(p+8,_BE); if(sectp+sec*2 > File.getSize()) continue;
            playseqtp = File.read_uint32(p+4,_BE); nplayseq = File.read_uint16(p+0x12,_BE);
            secs = []; for(j = 0; j < sec; j++) secs.push(File.read_uint16(sectp+j*2,_BE));
            for(j = 0; j < secs.length; j++) if(j <= nplayseq)
                ord.push(File.read_uint16(File.read_uint32(playseqtp,_BE)+0x28,_BE));
        }
        expp = File.read_uint32(sngp+0x20,_BE);
        if(expp && (File.read_uint32(expp,_BE) < sngp || expp > File.getSize())) { x = i+1; break }
        sngp = File.read_uint32(expp,_BE);
    }
    smp = File.read_uint8(p+0x11B); if(smp > 63) return false;
    return true
}
if(!bDetected && isOctaMedMMD()) {
    sName = sname; sVersion = sversion; bDetected = true;
    if(File.isVerbose()) {
        if(songname != "<unnamed>" && songname != "<ohne Namen>")
            sOption(songname);
        sOptionT(anno);
        sOptionT(iinfo,"ins0:");
        if(x > 1) sOption(x,"×");
        sOption((File.isDeepScan()?"ch:"+ch+" ":"")+"ord:"+ord.join("+")+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(File.read_uint32(4,_BE)))
    }
}

function isAHX() {
    //ref http://lclevy.free.fr/exotica/ahx/ahxformat.txt
    // & https://github.com/pete-gordon/hivelytracker/blob/master/Replayer_Windows/hvl_replay.c
    if(File.getSize() < 30) return false;
    if(File.compare("'THX'0.")) fmt = "ahx";
    else if(File.compare("'HVL'0.")) fmt = "hvl";
    else return false;
    bad = 0;
    var b6 = File.read_uint8(6);
    trk0saved = b6 >> 7;
    switch((b6 >> 4) & 7) {
    case 0: spd = "50Hz"; break;
    case 1: spd = "100Hz"; break;
    case 2: spd = "150Hz"; break;
    case 3: spd = "200Hz"; break;
    default: bad++;
    }
    ord = File.read_uint16(6,_BE) & 0xFFF;
    if(ord > 999) bad++; if(!ord || ord > 1024) bad++;
    if(fmt === "ahx") {
        lp = File.read_uint16(8,_BE); if(lp >= ord) bad++;
    }
    else chn = (File.read_uint8(8) >> 2)+4;
    if(bad > 1) return false;
    trl = File.read_uint8(10); if(!trl || trl > 64) return false;
    trk = File.read_uint8(11);
    ins = File.read_uint8(12); if(ins > 63) return false;
    sub = File.read_uint8(13);
    if(fmt === "ahx") {
        sz = 14+sub*2+ord*8+trk*trl*3; if(!trk0saved) sz += trl*3
        for(i=0; i < ins; i++) {
            var spls = File.read_uint8(sz+21);
            sz += 22+spls*4;
        }
    }
    else {
        sz = 16+sub*2+ord*chn*2;
        if(trk0saved) i = 1; else i = 0;
        for(; i <= trk; i++)
            for(j=0; j < trl; j++) {
                if(File.read_uint8(sz) == 0x3F) { sz++; continue }
                sz += 5;
            }
        for(i=0; i < ins; i++) {
            var spls = File.read_uint8(sz+21);
            sz += 22+spls*5;
        }
    }
    title = "";
    for(i=0;i <= ins;i++) { var r = sz;
        while(File.read_uint8(sz) && sz<File.getSize()) sz++;
        if(!i) title = File.read_ansiString(r,sz-r); sz++
    }
    if(sz > File.getSize()) bad++;
    return true
}
if(!bDetected && isAHX()) {
    if(fmt === "ahx") {
        sName = "Abyss' Highest eXperience module (.AHX)";
        if(!File.read_uint8(3)) sVersion = "v1.00~1.27"; else sVersion = "v2.0+";
    }
    else
        sName = "Hively Tracker module (.HVL)";
    bDetected = true;
    if(bad) sVersion += "/malformed"+bad;
    if(File.isVerbose()) {
        if(title != "") sOption(title); if(sub) sOption(sub,"×");
        n=0; p = 14+sub*2+ord*8; c = trk*trl; if(!trk0saved) c += trl; hp = false;
        for(i=0;i < c;i++) {
            note = File.read_uint8(p+i*3)>>2; if(note) n++;
            if(note > 60) if(fmt == "ahx") hp = true;
        }
        if(hp) sVersion += "/hi-pitch!"
        if(fmt === "ahx")
            sOption("spd:"+spd+" ord:"+ord+" lp:"+lp+" trk:"+trk+" ins:"+ins+" notes:"+n+" sz:"+outSz(sz))
        else
            sOption("ch:"+chn+" spd:"+spd+" ord:"+ord+" trk:"+trk+" ins:"+ins+" notes:"+n+" sz:"+outSz(sz))
    }
}

function isSTM() {
    //ref https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_stm.cpp
    t = readBytes(0x14,8); for(i = 0; i < 8; i++) { if(t[i] < 0x20 || t[i] >= 0x7F) return false }
    function _isstm() {
        if([0x1A,2].indexOf(File.read_uint8(0x1C)) < 0) return false;
        if([1,2].indexOf(File.read_uint8(0x1D)) < 0) return false;
        if(!File.compare("02",0x1E)) return false;
        if([0,10,20,21].indexOf(File.read_uint8(0x1F)) < 0) return false;
        if(File.read_uint8(0x21) > 0x40) return false;
        gvol = File.read_uint8(0x22);
        if(gvol > 0x40 && gvol != 0x58) return false;
        nVm = File.read_uint8(0x1F); ptn = File.read_uint8(0x21);
        if(nVm) maxord = 128; else maxord = 64;
        minsz = 0x30+31*0x20+maxord+ptn*256; if(File.getSize() < minsz) return false;
        smp = 0; ord = -1; tracker = "Scream Tracker 2 module"; ext = "STM"; return true
    }
    function _isstx() {
        if(!File.compare("'SCRM'",0x3C)) return false;
        ptnsz = File.read_uint16(0x1C);
        if((ptnsz < 64 && ptnsz != 0x1A) || ptnsz > 0x840) return false;
        t = File.read_uint8(0x2A); if(t > 0x40 && t != 0x58) return false;
        ptn = File.read_uint16(0x30); if(ptn > 64) return false;
        smp = File.read_uint16(0x32); if(smp > 96) return false;
        ord = File.read_uint16(0x34); if(ord > 0x81 && ord != 0x101) return false;
        if(File.read_uint16(0x1E) || File.read_uint32(0x26) || File.read_uint32(0x2C) != 1) return false;
        ptntp = File.read_uint16(0x20)<<4; smptp = File.read_uint16(0x22)<<4;
        chtp = File.read_uint16(0x24)<<4;
        minsz = 0x40 + Math.max(ptntp+ptn*2, smptp+smp*2, chtp+32+ord*5);
        if(File.getSize() < minsz) return false;
        p = chtp+32; for(i=0; i < ord; i++) {
            t = File.read_uint8(p+i*5); if(t > 63 && t != 99 && t != 255) return false;
        }
        tracker = "Scream Tracker Music Interface Kit module"; ext = "STX"; return true;
    }
    isstm = _isstm(); if(!isstm) isstx = _isstx(); if(!isstx) return false;
    sz = sz1 = minsz; smp0 = ""; max = 0;

    if(isstm) { //STM things
        for(i = 0; i < 31; i++) {
            zero = File.read_uint8(0x30+i*0x20+12);
            if(zero && zero != 46) return false;
            t = File.read_codePageString(0x30+i*0x20,12,"CP437").trim();
            if(i < 3 && File.isVerbose()) if(t.length) smp0 = smp0.append(t);
            var sofs = File.read_uint16(0x30+i*0x20+0x0E,_LE) << 4;
            var ssz = File.read_uint16(0x30+i*0x20+0x10,_LE);
            if(ssz && sofs > 0x30 && sofs < File.getSize()) {
                if(ssz) sz1 = sofs+ssz;
                //Actually it seems that if ssz is 1 it still must be 0, but then it'll just ignore
                //  the potentially interesting byte, so let's rip it too, just in case.
                if(sz1 > sz) sz = sz1;
                //We won't count it towards the smp counter though, that one's for the meaningful stuff.
                if(ssz > 1) smp++
            }
        }
        for(i=0; i < maxord; i++) {
            t = File.read_uint8(0x410+i);
            if(t === 99 || t === 255) t = 255;
            else if(t > 63) return false;
            else ord++;
        } ord++;
    }
    else if(isstx) { //STX things
        p = smptp; for(i = 0; i < smp; i++) {
            si = File.read_uint16(p+i*2) << 4;
            t = File.read_codePageString(si+0x30,28,"CP437").trim();
            if(i < 3 && File.isVerbose()) if(t.length) smp0 = smp0.append(t);
            if(!si) continue;
            if(File.read_uint8(si) === 1) {
                sofs = File.read_uint16(si+14)<<4;
                if(sofs > max) {
                    ssz = File.read_uint32(si+16); if(File.read_uint8(si+31)&4) ssz *= 2; //16bit
                    //if(ssz%16) ssz += 16-ssz%16; //the padding. Although STM2STX.EXE v1.0 doesn't pad
                    max = sofs; if(sz < max+ssz) sz = max+ssz
                }
            }
        }
        fmt = 1;
        if(ptn && ptnsz != 0x1A) {
            p = File.read_uint16(ptntp) << 4;
            if(p > File.getSize())
                if(File.isHeuristicScan()) {
                    bad = bad.addIfNone("!badptnp"); return true
                }
                else return false;
            if(File.read_uint16(p) === ptnsz) fmt = 0;
        }
        // I'll skip parsing the patterns, too much trouble when the samples always come later in the file
    }
    else return false; return true
}
if(!bDetected && isSTM()) {
    sName = tracker+" (."+ext+")"; bDetected = true;
    if(File.isVerbose()) {
        sOptionT(File.read_codePageString(0,0x14,"CP437"));
        if(smp0.length) sOption(smp0,"by/smp: ");
        if(ext === "STM") sVersion = "v"+File.read_uint8(0x1E)+"."+nVm;
        else sVersion = "v1."+fmt;
        tmp0 = File.read_uint8(0x20); gvol = File.read_uint8(0x22);
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" gvol:"+gvol+" sz:"+outSz(sz))
    }
}

function isIT() {
    //from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_it.cpp et al.
    // Now, this detection is TOO MUCH
    // I'm still going to do as much as I can, but people should have a choice to live without most of it :D
    // Which is to say, do NOT use deepscan unless you need calcsize
    var t = 0;
    if(File.compare("'IMPM'")) type = "it";
    else if(File.compare("'tpm.'")) type = "mpt";
    else return false;
    ord = File.read_uint16(0x20,_LE);
    ins = File.read_uint16(0x22,_LE); if(ins > 0x63) t++;
    smp = File.read_uint16(0x24,_LE); if(smp > 255) t++;
    ptn = File.read_uint16(0x26,_LE); if(!ptn && !File.isHeuristicScan()) t++;
    if(File.getSize() < 0xC0+4*(ins+smp+ptn)) return false; //itfileheader size
    cwtv /*make*/ = File.read_uint16(0x28,_LE); cmwt /*compat*/ = File.read_uint16(0x2A,_LE);
    flags = File.read_uint16(0x2C,_LE); special = File.read_uint16(0x2E,_LE);
    gvol = File.read_uint8(0x30); if(gvol > 0x80) t++;
    mvol = File.read_uint8(0x31); if(mvol > 0x80) t++;
    spd0 = File.read_uint8(0x32); if(!spd0) t++;
    tmp0 = File.read_uint8(0x33); if(tmp0 < 31) t++;
    sep = File.read_uint8(0x34); if(sep > 0x80) t++;
    if(t > 1) return false; bad = ""; if(t) bad = bad.addIfNone("!badinitinfo");
    msglen = File.read_uint16(0x36,_LE); msgofs = File.read_uint32(0x38,_LE);
    pwd = File.read_uint8(0x35);
    nreserved = File.read_uint32(0x3C,_LE); sreserved = File.read_ansiString(0x3C,4);
    tracker = auth = ""; mVlsw = mVcw = sV ="?"; ch = 1; cord = 0; sz = -1;
    var mptp = lastsmpofs = 0; type = "it"; chnm = []; smpnlst = [];
    if(File.isDeepScan()) {
        //The DeepScan part of the crazily complex Impulse Tracker/MPTM detection
        //Would be much easier if we didn't have to calculate the size! But we do, so there we go.
        const ITSampleSz = 0x50, ITHistorySz = 8, verLoadLimit = 0x1000;
        const sEmbedMsg = 1, sEditHistory = 2, sEmbedPtnHL = 4, sEmbedMIDIC = 8;
        const fEmbedMIDIC = 0x80, fEFR = 0x1000;
        var interpretModPlugMade = false; var FFchnpan = File.findSignature(0x40,0x40,"FF") >= 0;
        var mptV = 0;
        function itV(v) { return v.slice(0,1)+'.'+v.slice(1,3)+'.'+v.slice(3,5)+'.'+v.slice(5,7) }

        //TODO this↓ part is not ripper-friendly but thankfully also likely unnecessary:
        if(File.compare("'tpm.'")) { type = "mpt";
            p = File.getSize()-4; mptp = File.read_uint32(p,_LE);
            if(!File.compare("'228'04'mptm'",mptp)) mptp = 0;
        } else {
            if(0x888 < cwtv <= 0xFFF) {
                p = File.getSize()-4; mptp = File.read_uint32(p,_LE);
                if(0x100 <= mptp < File.getSize()-4)
                    if(File.compare("'228'04'mptm'",mptp)) {
                        type = "mpt"; if(cwtv >= verLoadLimit) {
                            sV = "future"; charset = "UTF8"; extsmp = 0;  return true }
                    } else mptp = 0;
                //if the above fails, there's a near-zero chance the ords will be misread and everything goes oof
                //that's expected behaviour, as the v1.17.02.46 format was experimental :)
            }
            //detecting made-with, part 1 of 3496987
            if(type === "it") {
                if((cwtv & 0xF000) === 0x5000) {
                    mptV = (cwtv&0xFFF) << 16;
                    if(File.compare("'OMPT'",0x3C)) interpretModPlugMade = true;
                    else if(mptV >= 0x01290000) mptV |= nreserved & 0xFFFF;
                    mVlsw = itV(mptV.toString(16).toUpperCase().padStart(7,'0'));
                } else if(cwtv === 0x888 || cmwt === 0x888) {
                    interpretModPlugMade = true; mVlsw = "1.17.00.00";
                } else if(cwtv === 0x214 && cmwt === 0x202 && !nreserved) {
                    mVlsw = "1.09.00.00"; tracker = "ModPlug Tracker b3.2 - 1.09";
                    interpretModPlugMade = true
                } else if(cwtv === 0x300 && cmwt === 0x300 && !nreserved
                            && ord === 256 && sep === 128 && !pwd) {
                    mVlsw = "1.17.02.20"; interpretModPlugMade = true
                }
            }
        }
        var b32ord = -1, lastord = 0;
        // 32bit orders: a deprecated format. OpenMPT 1.17.02.46 - 1.17.02.48.
        if(type === "mpt" && 0x88A < cwtv && cwtv <= 0x88D) {
            p = 0xC0; if(File.read_uint16(p,_LE)) return false; p += 2;
            b32ord = File.read_uint32(p,_LE); p += 4;
            if(b32ord > 256 || File.getSize() < p+b32ord*4) return false
        } else p = 0xC0;
        if(b32ord >= 0) ord = b32ord;
        for(i=0; i < ord; i++)
            if(b32ord < 0) { o = File.read_uint8(p++);
                if(o === 0xFF) lastord = 0xFFFF; else if(o === 0xFE) lastord = 0xFFFE; else cord++ }
            else { o = File.read_uint32(p,_LE); p += 4; lastord = o; if(o < 0xFFFF) cord++ }

//_log("read pointers & find the smallest @"+Hex(p));
        //TODO maybe, at some point in the future, audodetect b32ord
        //by making sure these pointers actually lead to intended things, and trying the b32 way if not
        minp = 0xFFFFFFFF;
        var insp = []; for(i=0; i < ins; i++) { t = File.read_uint32(p,_LE); p += 4;
            if(t <= p || t > File.getSize()) return false;
            if(sz < t) sz = t;  if(t && t < minp) minp = t; insp.push(t) }
        var smpp = []; for(i=0; i < smp; i++) { t = File.read_uint32(p,_LE); p += 4;
            if(t > File.getSize()) bad = bad.addIfNone("!short");
            if(!(special & sEmbedMsg)) {
                q = File.read_ansiString(t+0x14,26).trim(); if(q != "") smpnlst.push(q) }
            if(sz < t) sz = t; if(t && t < minp) minp = t; smpp.push(t) }
        var ptnp = []; for(i=0; i < ptn; i++) { t = File.read_uint32(p,_LE); p += 4;
            if((t > 0x1E && t <= p) || t > File.getSize()) return false;
            if(sz < t) sz = t; if(t && t < minp) minp = t; ptnp.push(t) }
        if(special & sEmbedMsg) { minp = Math.min(minp,msgofs);
            if(sz < msgofs+msglen) sz = msgofs+msglen }
        var possiblyUNMO3 = cmwt === 0x214 && (!cwtv || cwtv === 0x0214)
          && !File.read_uint16(0x1E) && !pwd && !nreserved && !(flags & 0xC0);
        if(possiblyUNMO3 && !ins && smp && p+4*smpp.length+2 <= minp) {
            var oldUNMO3 = true;
            for(i=0; i < smp; i++)
                if(File.read_uint32(p)) { oldUNMO3 = false; p -= i*4; break } else p += 4;
            if(oldUNMO3) tracker = "UNMO3 <= 2.4";
        }
        if(possiblyUNMO3 && !cwtv) tracker = "UNMO3 v0/1";
        if(special & sEditHistory) {
            //is edit history stored?
            var nflt = File.read_uint16(p);
            if(File.getSize() > p+nflt*8 && p+nflt*8 <= minp) {
                p += 2+nflt*8;
                if(possiblyUNMO3 && !nflt)
                    if(special & sEmbedPtnHL) tracker = "UNMO3 <= 2.4.0.1";
                    else tracker = "UNMO3"
            }
        } else if(possiblyUNMO3 && special <= 1 && !File.read_uint16(p)) {
            tracker = "UNMO3 <= 2.4"; p += 2
        }
        var hasMIDIC = (flags & fEmbedMIDIC) || (special & sEmbedMIDIC);
        if(hasMIDIC) p += 0x1320; if(p > File.getSize()) return false;
//_log("chunks expected @"+Hex(p));
        var isBeroTracker = hasPluginChunks = hasMPTM = false;
        while(p+9 < File.getSize()) {
            hkhd = File.read_ansiString(p,4); hksz = File.read_uint32(p+4,_LE);
//_log(hkhd+":"+Hex(p));
            if(hkhd === "MODU") isBeroTracker = true;
            else if(hkhd === "CNAM") { ch = hksz/20; u = p+8; hasMPTM = true;
                for(i = 0; i < ch; i++) {
                    t = File.read_ansiString(u,20).trim(); u += 20;
                    if(t != "") chnm.push(t);
                }
            }
            else if(hkhd === "PNAM") hasMPTM = true;
            else if(hkhd === "CHFX" || /F[X0-9]\d\d/.test(hkhd)) hasPluginChunks = true;
            if(["IMPI","IMPS","XTPM","STPM"].indexOf(hkhd) >= 0
              || p+8+hksz > File.getSize() || p >= msgofs)
                break;
            //PNAM may have fun info but that's for when someone feels less tedium otherwise
            else p += 8+hksz
        }
        if(cwtv === 0x217 && cmwt === 0x200 && !nreserved && !isBeroTracker) {
            if(hasMPTM || (ord > 0 && lastord == 0xFFFF) || FFchnpan) {
                mVlsw = "1.16.00.00"; tracker = "ModPlug Tracker 1.09-16";
            } else {
                mVlsw = "1.17.00.00"; tracker = "OpenMPT 1.17 (compat.export)";
            }
            interpretModPlugMade = true
        }

        var lastsmpofs = (smp > 0 ? smpp[smp-1]+ITSampleSz : p); var lastsmpco = false;

// Deriving the size of just the last sample (it might already be tasking indeed)
        const/*for IT sample flags*/ s16b=2,sSt=4,sCo=8;
        const/*for cvt flags*/ cSS=1,cBE=2,cD=4,cTo16=8,cOPL=0x40,cExt=0x80,cPCM=0xFF;
         //Possible codec values:
         //"sPCM" "uPCM" "dPCM" "fPCM" "IT214" "IT215" "AMS" "DMF" "MDL" "8d16"
         //"ADPCM" "MT2" "fPCM15" "fPCM23" "fPCMn" "sPCMn" "uLaw" "aLaw"
        extsmp = []; var possibleXMconv = false;
        Ss = []; //for gathering all info
        maxsmpofs = -1;
        for(i=0; i < smp; i++) {
            if(!smpp[i]) continue;
            if(smpp[i] > File.getSize()) { bad = bad.addIfNone("!short"); continue }
            if(!File.compare("'IMPS'",smpp[i])) bad = bad.addIfNone("!badsmp");
//_log(File.read_ansiString(smpp[i]+4,13)+": "+decAnsi(smpp[i]+0x14,0x1A,CP437,false).trim()+" @"+Hex(smpp[i]))
            var sf = File.read_uint8(smpp[i]+0x12); var cvt = File.read_uint8(smpp[i]+0x2E);
            var slen = File.read_uint32(smpp[i]+0x30); var smpofs = File.read_uint32(smpp[i]+0x48);
            S = { "ofs":0, "slen":0,
                "bits":(sf & s16b)? 16 : 8, "chn":1,// 1 channel if mono, 2 if joint or split stereo
                "en":"LE","codec":"sPCM","bps":0,"length":0 //length is calculated from length in samples
            };
            p = smpofs; S["ofs"] = smpofs; S["slen"] = slen;
            if(cvt === cOPL) p += 12;
            else if(cvt === cExt) {
                t = readVarUInt(p); extsmp.push(File.read_ansiString(p+1,t[1]));
                p += t[0]+t[1]; }
            else if(File.compare("'fLaC'",smpofs)) { S["codec"] = "FLAC" }
            else if(File.compare("'OggS'",smpofs)) { S["codec"] = "Ogg" }
            else { //GetSampleFormat
                S["codec"] = (cvt & cSS) ? "sPCM" : "uPCM";
                if((sf & sSt) && cwtv >= 0x214) S["chn"] = 2;
                if(sf & sCo)
                    S["codec"] = (cvt & cD) ? "IT215" : "IT214"; //IT2.14 packed sample
                else {
                    if(!(sf & s16b) && cvt == cPCM) S["codec"] = "ADPCM";
                    else {
                        if(cvt & cBE) S["en"] = "BE";
                        if(cvt & cD) S["codec"] = "dPCM";
                        if(cvt & cTo16) S["codec"] = "8d16"
                    }
                }
                switch(S["codec"]) {
                case "sPCM": case "uPCM": case "dPCM": case "fPCM": case "MT2": case "fPCM15":
                case "fPCM23": case "fPCMn": case "sPCMn": S["bps"] = S["bits"]; break;
                case "8d16": S["bps"] = 16; break;
                case "ADPCM": S["bps"] = 4; break; //encoded header size for this one is 16, 0 otherwise
                case "uLaw": case "aLaw": S["bps"] = 8; break;
                //case "IT214": case "IT215": case "AMS": case "DMF": case "MDL": S["bps"] = 0; //and
                default: S["bps"] = 0 //variable length-encoded
                }
                if(!S["bps"]) lastsmpco = true; //...and we'll decompress it further down
                else {
                    S["length"] = slen*S["chn"]*(S["bps"]>>3);
                    p += S["length"];
                }
                Ss[i] = S;
            } //GetSampleFormat
            if(lastsmpofs < p) lastsmpofs = p; if(sz < lastsmpofs) sz = lastsmpofs;
            if(S["codec"] === "uPCM" && slen) possibleXMconv = true;
//_log(File.read_ansiString(smpp[i]+0x14,0x1A)+" @"+Hex(smpofs)+": sf:"+Hex(sf)+" ["+S["codec"]+"] slen:"+slen+" ch:"+S["chn"]+" bps:"+S["bps"]+" bits:"+S["bits"]+", ends @"+Hex(smpofs+S["length"]));
        }
// Load_it.cpp:845
        if(possibleXMconv && cwtv === 0x204 && cmwt === 0x200 && !special && !nreserved
          && (flags & (~8)) === 0x15 && gvol === 128 && mvol === 48
          && sep === 128 && !pwd && !msglen) {
            for(i=0; i < 64; i++) if([0x20,0x40].indexOf(File.read_uint8(0x40+i)) < 0) {
                possibleXMconv = false; break } //testing pans
            if(possibleXMconv) for(i=0; i < 64; i++) if(File.read_uint8(0x80+i) != 0x40) {
                possibleXMconv = false; break } //testing vols
            if(possibleXMconv) for(i=20; i < 26; i++) if(File.read_uint8(4+i)) {
                possibleXMconv = false; break } //testing songname
            if(possibleXMconv) tracker = "XM Conversion"
        }
// Load_it.cpp:885 :: Scan all of the patterns to see how many channels we need
        for(i=0; i < ptn; i++) { t = ptnp[i];
            if(!t || t > File.getSize()) continue;
            var len = File.read_uint16(t,_LE), rows = File.read_uint16(t+2,_LE); t += 4;
            if(!rows || rows > 0x400 || t+4 > File.getSize()) continue; p = t+4;
            var chnMask = [], row = 0;
            while(row < rows && p < File.getSize()) {
                var y = File.read_uint8(p++); if(!y) { row++; continue }
                var c = y&0x7F; if(c) c--;
                if(y&0x80) chnMask[c] = File.read_uint8(p++);
                if(chnMask[c] & 0x0F) if(ch <= c && c < 0x7F) ch = c+1;
                if(chnMask[c] & 1) p++;  if(chnMask[c] & 2) p++;
                if(chnMask[c] & 4) p++;  if(chnMask[c] & 8) p += 2;
            }
            if(lastsmpofs < p) lastsmpofs = p; if(sz < lastsmpofs) sz = lastsmpofs
        }
//find the expected length of the final compressed sample
        if(lastsmpofs) { p = lastsmpofs;
            if(lastsmpco) {
                S = Ss[Ss.length-1];
                //smp length calc
                if(S["bps"])
                    if(S["bps"]%8) S["length"] = (S["codec"] == "ADPCM"?16:0)
                            +Math.ceil((slen+1)/2*S["ch"]);
                    else S["length"] = (S["codec"] == "ADPCM"?16:0)
                            +S["slen"]*S["bps"]/8*S["chn"];
                else { //let the crazy shit begin.
                    // ref https://github.com/nicolasgramlich/AndEngineMODPlayerExtension/blob/master/jni/loaders/itsex.c
                    // but mainly OpenMPT's SampleIO::ReadSample :142
                    if(S["bps"] == 4 && S["chn"] == 1 && S["en"] == "LE" && S["codec"] == "ADPCM") {
                        S["length"] = 16+(S["slen"]+1)/2; p += S["length"]
                    } else if(S["slen"] > 1 && (S["codec"] === "IT214" || S["codec"] === "IT215")) { //:165
                        // Let's try a quick-n-dirty first.
                        var u = p, ulen = 0, quickndirty = 0;
                        while(u < File.getSize() && ulen < S["slen"] /*packers have their limits*/) {
                            if(File.compare("'XTPM'",u) || File.compare("'STPM'",u) || File.compare("'228'04'mptm'",u))
                                { quickndirty = 1; p = u; break }
                            ucsz = File.read_uint16(u,_LE); ulen += ucsz; u += 2+ucsz;
                        }
                        if(quickndirty) { p = u } else {
                        //orrrr if no MPTX block is present, we gotta do it the hard way I guess :<
                        //ITDecompression(file,sample,getencoding=it215)
                        var bit_n = bit_buf = cend = readdef = 0;
                        function readBits(b) {
                            while(bit_n < b) { bit_buf |= (File.read_uint8(p++) << bit_n); bit_n += 8 }
                            var v = bit_buf & ((1 << b) - 1); bit_buf >>= b; bit_n -= b;
                            //if(p <= File.getSize()) return v; else return 0 }
                            return v }

                        if(S["bits"] == 16) { const fetchA=4, lowerB=-8, upperB=7, defW=17 }
                        else { const fetchA=3, lowerB=-4, upperB=3, defW=9 }
                        for(c = 0; c < S["chn"]; c++) { //ITCompression.cpp:322
                            var rs = S["slen"], v = csz = clen = topbit = 0;
                            cend = p;
                            while(rs && p < File.getSize()) { //:325
                                csz = File.read_uint16(p,_LE); p += 2; cend = p+csz;
                                if(!csz) { readdef = 10; _log("@"+Hex(p)+" malformed sample?"); continue }
                                if(cend > File.getSize()) { c = 0xE0F; bad = bad.addIfNone("!short"); break }
                                clen = Math.min(rs, 0x8000>>(S["bits"]>>4));
                                var w = defW; bit_buf = bit_n = 0;
//_log("@"+Hex(p)+" block ["+Hex(p)+"-"+Hex(cend-1)+"]="+Hex(csz)+" clen:"+Hex(clen)+" rs:"+(rs)+" v:"+Hex(v)+" w:"+w+" topbit:"+Hex(topbit)+" bnum:"+(~~bit_n).toString(16)+" bbuf:"+Hex(bit_buf));
                                while(clen && p < File.getSize()) { //:357
                                    if(w > defW) {
                                        _log("! @"+Hex(p)+" w="+Hex(w)+" > "+defW+" rs:"+rs+" clen:"+Hex(clen)+" cend:"+Hex(cend));
                                        bad = bad.addIfNone("!badITsmp"); rs = 0; break }
                                    v = readBits(w); topbit = 1<<(w-1);
                                    if(w < 7) { //mode A
                                        if(v == topbit) {
                                            v = readBits(fetchA)+1; if(v >= w) v++; w = v; continue
                                        }
                                    } else if(w < defW) { //mode B
                                        if(v >= topbit+lowerB && v <= topbit+upperB) {
                                            v -= topbit-1+lowerB; if(v >= w) v++; w = v;
                                            continue
                                        }
                                    } else //mode C: w = defW
                                        if(v & topbit) { w = (v&0xFF)+1; continue }
                                    clen--; rs--
                                }
                            }
                            if(p > File.getSize()) bad = bad.addIfNone("!short");
                        } } //if quickndirty else
                    } //if IT215||IT214
                    else if(S["codec"] === "FLAC") {
                        //TODO, or maybe not
                    } else if(S["codec"] === "Ogg") {
                        //TODO, or maybe not
                    } else if(S["codec"] === "AMS" && S["chn"] == 1) { //SampleIO:170
                        //TODO test it at least
                        sOption("\n!!! Please send this file to the detector dev!!! Poor fella hasn't any to test on")
                        var srcsz = File.read_uint32(p+4,_LE), pc = File.read_uint8(p+8);
                        S["length"] = srcsz+9
                    } else if(S["codec"] === "8d16" && S["chn"] == 1 && S["bps"] == 16) {
                        sOption("\n!!! Please send this file to the detector dev!!! Poor fella hasn't any to test on")
                        //TODO CopyMonoSample<SC::DecodeInt16Delta8>(sample, source, fsize)
                    } else if(S["codec"] === "MDL" && S["chn"] == 1 && S["bps"] <= 16) {
                        var srcsz = File.read_uint32(p); S["length"] = 4+srcsz
                    } else if(S["codec"] === "DMF" && S["chn"] == 1 && S["bps"] <= 16) {
                        sOption("\n!!! Please send this file to the detector dev!!! Poor fella hasn't any to test on")
                        //TODO DMFUnpack, or not to do
                    } else if((S["codec"] === "uLaw" || S["codec"] === "aLaw") /*TODO && mono or stereoInterleaved */ && S["bps"] == 16) {
                        S["length"] = S["slen"]*S["chn"]
                    }
                } //actual length teller
                if(sz < p) sz = p
                //The following used to be Load_it.cpp:952 -- it's a BAD idea for .IT without extra settings
                //that's within a huge file! We might even catch another .IT within.
                // while(p+4 < File.getSize()) { if(File.compare("'XTPM'",p) || File.compare("'STPM'",p)) {
                //         it = File.read_uint32(p+4,_LE); if(!(it&0x80808080) && (it&0x60606060)) break }
                //     p += 2+File.read_uint16(p,_LE); }
            }
        }
        var bLoadXIProps = false;
        if(File.compare("'XTPM'",p)) { // 'MPTX'
            p += 4;
            while(p+6 < File.getSize()) {
                t = File.read_ansiString(p,4); it = File.read_uint32(p);
                if(t === "STPM" || t === "228\x04" || (it&0x80808080) || !(it&0x60606060)) break;
                var prsz = File.read_uint16(p+4,_LE); p += 6;
                for(i=0; i < ins; i++) p += prsz
            }
            bLoadXIProps = true;
            if(sz < p) sz = p;
        }
// Load_it.cpp:978
        interpretModPlugMade |= bLoadXIProps;
// Load_it.cpp:985
        if(File.compare("'STPM'",p)) {
            p += 4;
            while(p+7 < File.getSize()) {
                t = File.read_ansiString(p,4); it = File.read_uint32(p);
                prsz = File.read_uint16(p+4,_LE);
                if(t === "228\x04" || t == "228\x02") { mptp = p; break; } //TODO check for 228\x02TC!
                if((it&0x80808080) || !(it&0x60606060) || p+6+prsz > File.getSize()) break;
                p += 6;
                switch(t) {
                case "...C": var chn = 0;
                    switch(prsz) {
                    case 2: chn = File.read_uint16(p,_LE); break; case 3: chn = File.read_uint24(p,_LE); break;
                    case 4: chn = File.read_uint32(p,_LE); break; default: chn = File.read_uint8(p) }
                    if(chn > ch) ch = chn; break;
                case ".VWC": var v = 0;
                    switch(prsz) {
                    case 1: v = File.read_uint8(p); break; case 2: v = File.read_uint16(p,_LE); break;
                    case 3: v = File.read_uint24(p,_LE); break; case 8: v = File.read_uint64(p,_LE); break;
                    default: v = File.read_uint32(p,_LE) }
                    if(v) mVcw = itV(v.toString(16).toUpperCase().padStart(7,'0')); break;
                case "VWSL": var v = 0;
                    switch(prsz) {
                    case 1: v = File.read_uint8(p); break; case 2: v = File.read_uint16(p,_LE); break;
                    case 3: v = File.read_uint24(p,_LE); break; case 8: v = File.read_uint64(p,_LE); break;
                    default: v = File.read_uint32(p,_LE) }
                    if(v) mVlsw = itV(v.toString(16).toUpperCase().padStart(7,'0')); break;
                case "AUTH":
                    auth = File.read_codePageString(p,prsz,"UTF8"); break;
                }
                p += prsz;
            }
            if(sz < p) sz = p;
        }

        tunings = 0;
        if(File.compare("'HSCT'",p)) { //the first-ever way of storing the tunings in an mptm
//tuningCollection.cpp
            var skip = false; var v = File.read_int32(p+4,_LE); p += 8;
            if(v == 1) { //can't find any files to test  this
                sOption("Please send this file to the detection author!!! This is ultra-rare, how did you make it?!");
                p += File.read_uint32(p); p += 4+Math.min(256,l)
            } else if( v == 2 ) {
//_log("tunings text: "+File.read_ucsdString(p));
                p += 1+File.read_uint8(p);
            } else { skip = true; bad = bad.addIfNone("!badtuningver") }
            if(!skip) {
                tunings = File.read_uint32(p+2); p += 6;
                t = File.findSignature(p,0x1000,"'FSCT'"); if(t >= p) p = t+4;
            }
            if(sz < p) sz = p;
        }
        else if(File.compare("'228'04'mptm'",p)) { //TODO there can be old-ass tunings instead here
            if(!mptp) mptp = p;
// Load_it.cpp:1299
            function readaintle(bits) { //ReadAdaptiveInt32LE et al.
                var b = 0, add = 0, r = 0, oldp = p;
                if(p+1 < File.getSize()) {  b = File.read_uint8(p++);
                    if(bits == 16) add = b & 1;  else if(bits == 32) add = b & 3;
                    else if(bits == 64) add = (1<<(b&3))-1;  else return 0;
                    if(bits == 16) r = b >> 1;  else r = b >> 2;
                    for(var i=0; i < add; i++) {  b = 0;
                        if(p+1 < File.getSize()) { b = File.read_uint8(p++); r |= b << ((i+1)*8-2);
                        } else return 0 }
                } else return 0;
                return r
            }
// serialization_utils.cpp:399
            function mptBeginRead() {
                if(!File.compare("'228'",p)) return false; posstart = p; p += 3;
                var l = File.read_uint8(p++); h = File.read_ansiString(p,l); p += l;
                flagbyte = 0; hd = File.read_uint8(p++);
                idbytes = ((hd & 3) === 3) ? 4 : (hd & 3); twochar = hd&(1<<6);
                hsz = readaintle(32);
                if(hsz > 1) { t = File.read_uint8(p++); if(!t) flagbyte = File.read_uint8(p); p += hsz-1 }
                if(hd & (1<<4)) t = readaintle(64); var srlV = itV(t.toString(16).toUpperCase().padStart(7,'0'));
                if(mVlsw === "?") mVlsw = srlV+"[mptinfo]";
                if(hd & (1<<5)) p += File.read_uint8(p++); //:477
                if(flagbyte & 1) { t = File.read_uint8(p++);
                    if(t&1) idbytes = 65535; else idbytes = t >> 1 }
                fes = 0; if(flagbyte & (1<<1)) fes = readaintle(32); //:496
                hasstartpos=hd&(1<<2); hassz=hd&(1<<3); hasid=idbytes; hasdesc=hd&(1<<7);
                hasmap = hasid || hasstartpos || hassz || hasdesc;
                if(flagbyte&(1<<2)) { t = readaintle(16); //:512
//_log("  @"+Hex(p)+" desc["+t+"]:"+(twochar ? File.read_codePageString(p,t,"UTF8") : File.read_ansiString(p,t)));
                   p += t*(twochar ? 2 : 1) }
                if(flagbyte&(1<<3)) p += 5; //:517
                entries = readaintle(64); //if(entries > 16000) break;
                if(hasmap) t = readaintle(64); if(p > File.getSize()) return false; //:533
                //endOfHdrData = rposEndOfHdrData = p-posstart;
                rposMapBegin = hasmap ? t : p-posstart;
                return true
            }

            if(mptBeginRead()) {
// serialization_utils.cpp:558
//I've left the pieces of map code intact in case there's fun in finding out what MPT-only tweaks there are
                if(hasmap || fes) {
                    p = posstart+rposMapBegin; if(p > File.getSize()) break;
                    //var idArray = [], mapdata = [];
                    for(i=0; i < entries; i++) {
                        var idsz = idbytes;
                        if(idsz == 65535) idsz = readaintle(16);
                        // md = {"idlen":idsz, "idpos":oldEnd, "id:":"", "rposst":0, "rposend":0, "sz":-1 }
                        // var oldEnd = mapdata.length; if(idsz) md["id"] = File.read_ansiString(p,idsz);
                        p += idsz;
                        if(hasstartpos) /*md["rposst"] =*/ readaintle(64);
                        if(!fes) { if(hassz) /*md["sz"] =*/ readaintle(64) } //else md["sz"] = fes
                        // if(md["sz"] >= 0 && !hasstartpos)
                        //     md["rposst"] = !i ? 0 : mapdata[i-1]["rposst"] + mapdata[i-1]["sz"];
                        if(hasdesc) { t = readaintle(16); p += t*(twochar ? 2 : 1) }
                        //mapdata.push(md)
                    }
                } //posmapend = p
            }
            if(sz < p+4) sz = p+4; if(File.read_uint32(p,_LE) != mptp) bad = bad.addIfNone("!badmptptr");
        } //if 228.mptm

// Load_it.cpp:1158
        if(mVlsw === "?" && cwtv === 0x888) mVlsw = "1.17.00.00";
        if(mVlsw != "?" && tracker === "") {
            tracker = "OpenMPT";
            //not sure how many people care about these but...
            isCompatX = (cwtv&0xF000) === 0x5000;
            if(mVlsw === "1.17.00.00" && sreserved != "OMPT") isCompatX = false;
            if(isCompatX) tracker += " (compat. export)";
            else if((mVlsw > "1.17.02.54" && mVlsw < "1.18.02.00" && mVlsw != "1.18.00.00")
              || (mVlsw > "1.18.02.00" && mVlsw.slice(mVlsw.length-2,mVlsw.length) != "00"))
                tracker += " (test build)"
        }
        else {
            var STdv = 734016 + (cwtv === 0x1FFF ? nreserved : cwtv-0x1050);
            switch(cwtv >> 12) {
            case 0:
                if(isBeroTracker) tracker = "BeRoTracker";
                else if(cwtv === 0x214 && cmwt === 0x200 && flags === 9 && !special
                  && !File.read_uint16(0x3E) && !ins && ptn+1 === ord && gvol === 128 && mvol === 100
                  && spd === 1 && sep === 128 && !pwd && !msglen && !msgofs && !nreserved)
                    tracker = "OpenSPC conversion";
                else if(cwtv === 0x214 && cmwt === 0x200 && !File.read_uint16(0x3E) && !nreserved) {
                    mVlsw = "1.00.00.A5"; tracker = "ModPlug Tracker 1.00a5"; interpretModPlugMade = true
                } else if(cwtv === 0x214 && cmwt === 0x214 && sreserved === "CHBI")
                    tracker = "ChibiTracker";
                else if(cwtv === 0x214 && cmwt === 0x214 && special <= 1 && !pwd && !nreserved
                  && (flags&0x10A6) === 4 && smp && File.compare("'XXXXXXXX.YYY'",smpp[1]+4))
                    tracker = "CheeseTracker";
                else if(!cwtv && tracker === "") tracker = "Unknown";
                else if(cmwt < 0x300 && tracker === "") {
                    if(cmwt > 0x214) tracker = "Impulse Tracker 2.15";
                    else if(cwtv > 0x214) tracker = "Impulse Tracker 2.14p"+(cwtv-0x214)
                    else tracker = "Impulse Tracker "+((cwtv&0xF00)>>8)+"."+(cwtv&0xFF).toString(16);
                    //a good place to find the total edit time if you care
                }
                break;
            case 1:
                // function SchismEpoch(y,m,d) { var mm = (m+9)%12; var yy = y-mm/10;
                //     return yy*365 + yy/4 - yy/100 + yy/400 + (mm*306+5)/10 + d-1 }
                var mk = cwtv & 0xFFF;
                if(mk <= 0x50) tracker = "Schism Tracker 0."+mk.toString(16);
                else {
                    var dt = 734016 + (mk < 0xFFF ? mk-0x050 : nreserved);
                    var y = div(dt*10000+14780, 3652425);
                    var ddd = dt - (365*y + div(y,4) - div(y,100) + div(y,400));
                    if(ddd < 0) { y--; ddd = dt - (365*y + div(y,4) - div(y,100) + div(y,400)) }
                    var mi = div(100*ddd+52, 3060);
                    tracker = "Schism Tracker "+(y+div(mi+2,12)).padStart(4,'0')+"-"
                        +((mi+2)%12+1).padStart(2,'0')
                        +"-"+(ddd - div(mi*306+5,10) + 1).padStart(2,'0')
                } break;
            case 4: tracker = "pyIT "+((cwtv&0xF00)>>8)+"."+((cwtv&0xFF).toString(16)); break;
            case 6: tracker = "BeRoTracker"; break;
            case 7:
                if(cwtv === 0x7FFF && cmwt === 0x215) tracker = "munch.py";
                else tracker = "ITMCK "+((cwtv>>8)&0xF)+"."+((cwtv>>4)&0xF)+"."+(cwtv&0xF);
                break;
            case 0xD: tracker = "spc2it";
            }
        }
    } //if isDeepScan
    if(mVlsw != "?") { charset = "CP1252"; if(mVcw == "?") sV = mVlsw; else if(mVcw != mVlsw) sV = "cw:"+mVcw+"/lsw:"+mVlsw; else sV = mVcw }
    else { charset = "CP437"; if(mVcw != "?") sV = mVcw }
    var badc = 0; for(i=0;i<bad.length;i++) if(bad[i] == "!") badc++;
    if(badc < 3) return true
}
if(!bDetected && isIT()) {
    if(type === "it") sName = "Impulse Tracker module (.IT)";
    else /*if(type === "mpt")*/ sName = "OpenMPT module (.MPTM)";
    //else if(type === "alike") sName = "Impulse Tracker-alike ["+File.read_ansiString(0,4)+"] module (.IT?)";
    bDetected = true;
    if(sV != "?") sVersion = "v."+sV; if(bad != "") sVersion += "/malformed"+bad;
    if(File.isVerbose()) {
        sOptionT(decAnsi(4,25,charset));
        if(File.isDeepScan()) {
            sOption(tracker,"in: ");
            sOptionT(auth,"by: ")
            if(extsmp.length)
                sOption("ext.smp: \""+addEllipsis(extsmp.join(", "),0x40,0x30)+"\"");
            if(chnm.length)
                sOption("ch.names: \""+addEllipsis(chnm.join(", "),0x40,0x30)+"\"")
        }
        if(special & 1)
            sOptionT(addEllipsis(File.read_codePageString(msgofs,Math.min(msglen,0x100),"CP437"),0x100),"msg: ");
        else if(smpnlst.length)
            sOption("smps: \""+addEllipsis(smpnlst.join(" "),0x40,0x30)+"\"");
        if(File.isDeepScan())
            sOption("ch:"+ch+" ord:"+ord+"("+cord+") ptn:"+ptn+" ins:"+ins+" smp:"+smp+" spd0:"+spd0+" tmp0:"+tmp0+(tunings?"tunings:"+tunings:"")+" sz:"+outSz(sz))
        else
            sOption("ch:"+ch+" ord:"+ord+" ptn:"+ptn+" ins:"+ins+" smp:"+smp+" spd0:"+spd0+" tmp0:"+tmp0)
    }
}

function isASC1() { //basically ASC0 + loop pos
    //ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundASC1
    if(File.getSize() < 9) return false;
    ptntp = File.read_uint16(2,_LE); ord = File.read_uint8(8); //patterns table ptr & num of positions
    if(ptntp-ord != 9 && ptntp-ord != 72) return false;
    smptp = File.read_uint16(4,_LE); if(smptp > File.getSize()) return false; //samples table ptr
    orntp = File.read_uint16(6,_LE); if(orntp > File.getSize()) return false; //ornaments table ptr
    if(File.read_uint16(smptp,_LE) != 0x40 || File.read_uint16(orntp,_LE) != 0x40) return false;
    j3 = 0;
    for(j1=0; j1 < ord; j1++) {
        p = File.read_uint8(9+j1); if(j3 < p) j3 = p; }
    if(File.read_uint16(ptntp,_LE) != (j3+1)*6) return false;
    j = File.read_uint16(orntp+0x40-2,_LE)+orntp;
    while((j < File.getSize()) && (j < 65535) && !(File.read_uint8(j)&0x40))  j += 2;
    bad = "";
    if(j>65534 || j>=File.getSize())
      if(File.isHeuristicScan()) bad = "/malformed";
      else return false;
    sz = j+2; delay = File.read_uint8(0); loop = File.read_uint8(1);
    return true
}
if(!bDetected && File.isDeepScan() && isASC1()) {
    bDetected = true; sVersion = "v1.x-2.x"+bad;
    sName = "ASC Sound Master module (.ASC)";
    if(File.isVerbose()) {
        pt = File.findSignature(8,0x80,"'ASM COMPILATION OF '")+0x13;
        if(pt > 8) {
            pa = File.findSignature(pt+0x13,0x20,"' BY '");
            t = File.read_ansiString(pt,pa-pt); a = File.read_ansiString(pa+4,0x12);
            sOptionT(t); sOptionT(a,"by: ")
        }
        sOption("delay:"+delay+" loop:"+loop+" len:"+sz)
    }
}

function isASC0() {
    //ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundASC0
    if(File.getSize() < 8) return false;
    ptntp = File.read_uint16(1,_LE); ord = File.read_uint8(7); //patterns table ptr & num of positions
    if(ptntp-ord != 8 && ptntp-ord != 71) return false;
    smptp = File.read_uint16(3,_LE); if(smptp > File.getSize()) return false; //samples table ptr
    orntp = File.read_uint16(5,_LE); if(orntp > File.getSize()) return false; //ornaments table ptr
    if(File.read_uint16(smptp,_LE) != 0x40 || File.read_uint16(orntp,_LE) != 0x40) return false;
    j3 = 0;
    for(j1=0; j1 < ord; j1++) {
        p = File.read_uint8(8+j1); if(j3 < p) j3 = p; }
    if(File.read_uint16(ptntp,_LE) != (j3+1)*6) return false;
    j = File.read_uint16(orntp+0x40-2,_LE)+orntp;
    while((j < File.getSize()) && (j < 65535) && !(File.read_uint8(j)&0x40))  j += 2;
    bad = "";
    if(j > 65534 || j >= File.getSize())
        if(File.isHeuristicScan()) bad = "/malformed";
        else return false;
    sz = j+2; delay = File.read_uint8(0);
    return true
}
if(!bDetected && File.isDeepScan() && isASC0()) {
    bDetected = true; sVersion = "v0.x"+bad;
    sName = "ASC Sound Master module (.AS0)";
    if(File.isVerbose()) {
        pt = File.findSignature(7,0x80,"'ASM COMPILATION OF '")+0x13;
        if(pt > 7) {
            pa = File.findSignature(pt+0x13,0x20,"' BY '");
            t = File.read_ansiString(pt,pa-pt); a = File.read_ansiString(pa+4,0x12);
            sOptionT(t); sOptionT(a,"by: ")
        }
        sOption("delay:"+delay+" sz:"+outSz(sz))
    }
}

function isBPSoundMon() {
    //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/BP-Soundmonitor.c
    if(File.getSize() < 0x200) return false;
    if(!File.compare("'V.'3.",0x1A)) return false;
    for(j=0; j < 15; j++) {
        if(File.read_uint8(32+j*32) == 0xFF) continue;
        if(File.read_uint8(63+j*32) > 0x40) return false
    }
    allsmpsz = 0;
    for(k=0; k < 15; k++)
        if(File.read_uint8(32+32*k) < 0xFF)
            allsmpsz += (File.read_uint16(56+32*k,_BE))*2;
    j = File.read_uint8(29);
    sz = allsmpsz + j*64;
    j = 0; l = File.read_uint16(30,_BE);
    if(525+l*16 > File.getSize()) return false; //might want to add a few more checks...
    for(k=0; k < l; k++) {
        p = File.read_uint16(512+16*k,_BE); if(p>j) j = p;
        p = File.read_uint16(516+16*k,_BE); if(p>j) j = p;
        p = File.read_uint16(520+16*k,_BE); if(p>j) j = p;
        p = File.read_uint16(524+16*k,_BE); if(p>j) j = p;
    }
    sz += 512 + j*48 + l*16;
    return true
}
if(!bDetected && File.isDeepScan() && isBPSoundMon()) {
    bDetected = true;
    sName = "BP SoundMon module (.BP)";
    sVersion = "v"+File.read_ansiString(0x1C,1); if(File.getSize() < sz) sVersion += "/malformed!short";
    if(File.isVerbose()) {
        sOptionT(File.read_ansiString(0,0x19));
        sOption(outSz(sz),"sz:");
    }
}

function isFuchs() {
    //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/FuchsTracker.c
    if(File.getSize() < 0xC0) return false;
    allsmpsz = File.read_uint32(0x0A,_BE);
    if((allsmpsz<=2) || (allsmpsz>=65535*16)) return false;
    smpdescs = 0;
    for(k=0; k<16; k++) {
        sz = File.read_uint16(0x0E+k*2,_BE);
        lpst = File.read_uint16(0x4E+k*2,_BE);
        if(sz < lpst) return false;
        if(File.read_uint8(0x2E+k*2) > 0x40) return false; //vol
        smpdescs += sz
    }
    if((smpdescs<=2) || (smpdescs>allsmpsz)) return false;
    k = 0; //last pattern
    for(j=0; j<40; j++) {
        pt = File.read_uint8(113+j*2);
        if(pt>40) return false;
        if(pt>k) k = pt
    }
    k+=1;
    k*=1024;
    if(k+200 > File.getSize()) return false;
    sz = smpdescs+k+204;
    return true
}
if(!bDetected && File.isDeepScan() && isFuchs()) {
    sName = "FuchsTracker module (.FUCHS)"; if(File.getSize() < sz) sVersion = "malformed!short";
    bDetected = true;
    if(File.isVerbose()) sOption(outSz(sz),"sz:")
}

function isGMC() {
    //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/GMC.c
    // & https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_gmc.cpp
    debug = 0; if(File.getSize() < 444 || !File.compare("000000",0xF0)) return false;
    //smp descriptions
    allsmpsz = 0; j = 0; smps = [];
    for(k=0; k < 15; k++) {
        o = File.read_uint32(16*k,_BE); if(o > 0x7FFFFF || (o&1)) { //ofs
            if(debug)_log("Fault @"+Hex(16*k)+": ofs="+Hex(o)); return false }
        l = File.read_uint16(4+16*k,_BE)<<1; //length
        if(l > 0xFFFF) { if(debug)_log("Fault: smp too long "+l); return false }
        if(File.read_uint8(6+16*k,_BE)) { //zero byte
            if(debug)_log("Fault @"+Hex(6+16*k)+": non-zero"); return false }
        if(File.read_uint8(7+16*k) > 0x40) { //vol
            if(debug)_log("Fault @"+Hex(7+16*k)+": smpvol error"); return false }
        a = File.read_uint32(8+16*k,_BE); if(a > 0x7FFFFF || (a&1)) { //address
            if(debug)_log("Fault @"+Hex(8+16*k)+": adr="+Hex(a)); return false }
        n = File.read_uint16(0xC+16*k,_BE); //looplen
        if(n > 2 && n > l) {
            if(debug)_log("Fault @"+Hex(0xC+16*k)+": looplen "+Hex(n)+" > sz "+Hex(l)); return false }
        if(File.read_uint16(0xE+16*k,_BE)&1) { //datastart
            if(debug)_log("Fault @"+Hex(0xE+16*k)+": odd datastart"); return false }
        allsmpsz += l;
        if(l) smps.push([o,l]);
    }
    if(allsmpsz <= 4) return false;
    smps.sort(function(a,b){return a[0]-b[0]});
if(debug)smps.forEach(function(k){_log("smp ofs:"+Hex(k[0])+" len:"+Hex(k[1])+" -> "+Hex(k[0]+k[1]))})
    for(k=0; k < smps.length-1; k++)  if(smps[k][0]+smps[k][1] > smps[k+1][0]) return false;
    ord = File.read_uint8(0xF3); //ptn table size
    if(ord > 0x64 || !ord) return false;
    ptn = 0; ords = [];
if(debug)_log("testing order list...")
    for (i=0; i < 100; i++) { //ptn order table
        p = File.read_uint16(0xF4+i*2,_BE);
        if (p & 0x3FF) { if(debug)_log("Fault @"+Hex(0xF2+i*2)+": bad ptn "+Hex(p)); return false }
        if(i < ord) { p >>= 10; if(p > ptn && p != 63) ptn = p }
    } ptn++;
if(debug)_log("ptns:"+ptn+" > 64?")
    if(ptn > 64) return false;
if(debug)_log("testing patterns...")
    m = notes = 0; p = 0x1BC; nps = [];
    for (i=0; i < ptn; i++) { //test pattern data
        badnotes = badled = 0;
        for (j = 0; j < 256; j++) {
            d = readBytes(p,4);
            np = (d[0]<<8)+d[1];
            if(np && np != 0xFFFE)
                if(np < 113 || np > 856) badnotes++; else { if(nps.indexOf(np) < 0) nps.push(np); notes++ }
            d[2] &= 0xF;
            if(d[2] == 3 && d[3] > 0x40) {
                if(debug) _log("  @"+Hex(p)+": vol.fx error "+d[3]+" at ptn "+i+" row "+(j>>2)+" chn "+(j%4)); badnotes++ } //vol effect
            if(d[2] == 4 && d[3] > 0x63) {
                if(debug) _log("  Fault @"+Hex(p)+": ptn.break error "+d[3]+" vs. 99 at ptn "+i+" row "+(j>>2)+" chn "+(j%4)); return false }
            if(d[2] == 5 && d[3] > ord+1) {
                if(debug) _log("  Fault @"+Hex(p)+": pos.jump error "+d[3]+" vs. "+ord+" at ptn "+i+" row "+(j>>2)+" chn "+(j%4)); return false }
            p += 4; if(p > 0xFFFC || p > File.getSize()) return false
        }
    if(badnotes > 16) { if(debug)_log("Fault: over 16 bad notes"); return false }
    }
    if(!notes && !File.isHeuristicScan()) return false;
    nps.sort(function(a,b){return a-b});
if(debug)_log(nps.length+" different nps used: "+nps)
    sz = allsmpsz + ptn*1024 + 0x1BC;
    return true
}
if(!bDetected && File.isDeepScan() && isGMC()) {
    bDetected = true;
    sName = "Game Music Creator module (.GMC)";
    if(File.isVerbose()) {
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smps.length+" notes:"+notes+" sz:"+outSz(sz)); if(File.getSize() < sz) sVersion = "malformed!short"
    }
}


function isProTracker1() {
    //ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/protracker1.cpp
    // & https://bulba.untergrund.net/Ay_Emul29.src.7z/Formats.pas -> FoundPT1
    if(File.getSize() < 0x66) return false;
    tempo = File.read_uint8(0); if((tempo < 2) || (tempo > 15)) return false; //delay
    ptntp = File.read_uint16(0x43,_LE); if(ptntp >= File.getSize()) return false; //patterns table pointer
    numofpos = File.read_uint8(1);// if(len == 0) return false; //numofpositions, can be corrupted?
    loop = File.read_uint8(2);
    j = 0; j1 = 65535;
    for(i=0; i < 0x10; i++) { //samples pointers
        sp = File.read_uint16(3+i*2,_LE); if(sp > File.getSize()) return false;
        if(j < sp) j=sp;
        op = File.read_uint16(0x23+i*2,_LE); if(op > File.getSize()) return false;
        if(op != 0 && j1 > op) j1 = op
    }
    if((j1 < 0x67) || (j < 0x67) || (j > 65534) || (j > File.getSize())
      || (j + File.read_uint8(j) * 3 + 2 != j1)) return false;
    j = 0;
    for(i=0; i < 0x10; i++) { //ornaments pointers
        op = File.read_uint16(0x23+i*2,_LE); if(op > File.getSize()) return false;
        if(j < op) j = op
    }
    if(j<0x67) return false;
    len = j+0x40; if(len>65536) return false; if(len>File.getSize()+1) return false;
    j = 0x63;
    while(j <= ptntp && File.read_uint8(j) < 0xFF) j++;
    if(j+1 != ptntp) return false;
    ord = j-0x63; if(loop > ord) return false;
    return true
}
if(!bDetected && File.isDeepScan() && isProTracker1()) {
    sName = "ProTracker module (.PT1)"; sVersion = "v1.x"; bDetected = true;
    if(File.isVerbose()) {
        sOptionT(File.read_ansiString(0x45,0x1E)); //musicname
        sOption("tempo:"+tempo+" ord:"+ord+" len:"+len)
    }
}

function isProTracker2() {
    //ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/protracker2.cpp
    // & https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundPT2
    if(File.getSize() < 132) return false; //the smallest actual mod in all collections is 521 long
    tmp = File.read_uint8(0); if(tmp<2 || tmp>15) return false;
    ord = File.read_uint8(1); if(!ord) return false;
    loop = File.read_uint8(2); if(loop > ord) return false;
    smp0 = File.read_uint16(3,_LE); orn0 = File.read_uint16(0x43,_LE);
    if(orn0-smp0>File.getSize()+2 || orn0<smp0) return false;
    ptnp = File.read_uint16(0x63,_LE); if(ptnp<0x65 || ptnp>File.getSize()) return false;
    for(i=0; i < 0x30; i++) { //sample pointers and ornament pointers
        p = File.read_uint16(3+i*2,_LE);
        if(!p && (/*p < 0x65 ||*/ p > File.getSize()-2)) return false; // Ashnar/HBKSA2.pt2 .___.
    }
    if(File.read_uint8(0x64) > 1) return false;
    for(ptn=0; ptn <= 0xFF; ptn++) {
        if(0x83+ptn > File.getSize()-2) return false;
        posn = File.read_uint8(0x83+ptn);
        if(posn == 0xFF) break;
    }
    if(!ptn || ptn>0xFF) return false; //how can it be >0xFF...
    return (ord == ptn)
}
if(!bDetected && File.isDeepScan() && isProTracker2()) {
    sName = "ProTracker module (.PT2)"; sVersion = "v2.x"; bDetected = true;
    if(File.isVerbose()) {
        if(smp0>0x82 && orn0>0x82 && ptn>0x82)
            sOptionT(File.read_ansiString(0x65,0x1E));
        sOption("tmp:"+tmp+" ord:"+ord+" ptn:"+ptn)
    }
}

function isProTracker3() { //PT3's detection, ALSO sanity checks because SOME people -_-
    //ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/protracker3_compiled.cpp
    ftn = File.read_uint8(0x63); //0=Pro Tracker, 1=Sound Tracker, 2=ASM or PSC, 3=RealSound.
    tmp = File.read_uint8(0x64); if((tmp<3)||(tmp>63)) return false;
    len = File.read_uint8(0x65); if(len>0x55) return false;
    loop = File.read_uint8(0x66); if(loop>63) return false;
    pptn = File.read_uint16(0x67,_LE); if(pptn>File.getSize()-2) return false;
    for(i=0;i<0x30;i++) if(File.read_uint16(0x69+i*2,_LE)>File.getSize()-2) return false;
    for(ord=0;ord<=0xFF;ord++) {
        if(0xC9+ord>File.getSize()-2) return false;
        posn=File.read_uint8(0xC9+ord);
        if(posn==0xFF) break;
    }
    if((ord==0) || (ord>0xFF)) return false;
    return (ord==len)
}
if(!bDetected && File.isDeepScan() && isProTracker3()) {
    sName = "ProTracker module (.PT3)"; sVersion = "v3."+File.read_ansiString(0x0D,1);
    bDetected = true;
    if(File.isVerbose()) {
        sOptionT(File.read_ansiString(0x1E,0x20));
        sOptionT(File.read_ansiString(0x42,0x20),"by: ")
        sOption("tmp:"+tmp+" ord:"+len)
    }
    switch(ftn) {
        case 0: sOption("Pro Tracker tuning"); break;
        case 1: sOption("Sound Tracker tuning"); break;
        case 2: sOption("ASM/PSC tuning"); break;
        case 3: sOption("RealSound tuning"); break;
        default: sOption("Unknown tuning")
    }
}

function isSoundTrackerProUnp() {
    //ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/aym/soundtracker.cpp
    // & https://bulba.untergrund.net/STPDocs.7z -> Xplay79f.txt
    if(File.getSize()<3009+576) //header + patterns
        return false;
    const stpsmpnum=15; p=0;
    for(smp=0;smp<15;smp++) {
        for(i=0;i<32;i++) { j = File.read_uint8(p); if(j>0x0F) return false; p++ } //levels
        for(i=0;i<32;i++) { j = File.read_uint8(p); if(j&0x20) return false; p++ } //noises
        for(i=0;i<32;i++) { p++; j = File.read_uint8(p); if(j>0x1F) return false; p++ } //additions
        for(i=0;i<2;i++) { j = File.read_uint8(p); if(j>0x1F) return false; p++ } //loops
    }
    for(i=0;i<256;i++) { j = File.read_uint8(p); if((j<1) || (j>0x20)) return false; p+=2 } //positions
    // at this point, p should be 15*0x82
    if(File.read_uint8(p)&0x80) return false;
    p++;  p+=32*17; //ornaments
    dly=File.read_uint8(p); if((dly<1) || (dly>0x0F)) return false; p++;
    pts=File.read_uint8(p); if((pts<1) || (pts>0x40)) return false;
    return true;
}
if(!bDetected && File.isDeepScan() && isSoundTrackerProUnp()) {
    sName = "Sound Tracker Pro module (.STF)"; sVersion  = "uncompiled/unpacked"; bDetected = true;
}

function isSoundTrackerPro() {
    // taken from https://bulba.untergrund.net/STPDocs.7z -> stp.txt
    tmp = File.read_uint8(0); if((tmp == 0) || (tmp > 50)) return false;
    tabl1 = File.read_uint16(1,_LE); if(File.getSize()-2 < tabl1) return false;
    tabl2 = File.read_uint16(3,_LE); if(File.getSize()-2 < tabl2) return false; //pattern descriptors
    tabl3 = File.read_uint16(5,_LE); if(File.getSize()-2 < tabl3) return false; //ornament offsets
    tabl4 = File.read_uint16(7,_LE); if(File.getSize()-2 < tabl4) return false; //sample offsets
    len = File.read_uint8(tabl1); if(tabl2 != tabl1+2+len*2) return false;
    for(i=0;i<len;i++)  if(File.getSize() < File.read_uint16(tabl2+i*2,_LE)) return false;
    if(File.getSize() < tabl4+30) return false;
    for(i=0;i<15;i++)  if(File.getSize() < File.read_uint16(tabl4+i*2,_LE)) return false;

    return true;
}
if(!bDetected && File.isDeepScan() && isSoundTrackerPro()) {
    sName = "Sound Tracker Pro module (.STP)"; sVersion  = "uncompiled"; bDetected = true;
    sOption("tmp:"+tmp+" ord:"+len)
}

function isSQTracker() {
    //ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundSQT
    if(File.getSize()<17 || File.getSize()>0x3600) return false;
    //sz = File.read_uint16(0,_LE); if(File.getSize() != sz) return false; //nobody seems to check this!
    smptp = File.read_uint16(0x02,_LE); if(smptp < 10) return false; //samples table ptr
    orntp = File.read_uint16(0x04,_LE); ptntp = File.read_uint16(0x06,_LE); //ornaments & patterns table ptrs
    postp = File.read_uint16(0x08,_LE); lpp = File.read_uint16(0x0A,_LE); //positions table ptr & loop ptr
    if(orntp <= smptp+1) return false; if(ptntp < orntp) return false;
    if(postp <= ptntp) return false; if(lpp < postp) return false;
    membase = smptp-10; //to eventually realign to file, not memory
    if(lpp-membase >= File.getSize()) return false;
    j1 = postp-membase; b = File.read_uint8(j1); if(b==0) return false; //can't have 0 patterns
    ord = 0; j2 = 0;
    while(b) { //find the highest pattern #
        if(j1+7 >= File.getSize()) return false;
        ord++;
        if(j2 < (b&0x7F)) j2 = b&0x7F; j1 += 2; b = File.read_uint8(j1);
        if(j2 < (b&0x7F)) j2 = b&0x7F; j1 += 2; b = File.read_uint8(j1);
        if(j2 < (b&0x7F)) j2 = b&0x7F; j1 += 3; b = File.read_uint8(j1)
    }
    ptn = j2;
    p = File.read_uint16(smptp-membase+2,_LE);
    if(p-ptntp-2 != j2*2) return false;
    p = 12; //len = j1+7;
    j2 = File.read_uint16(12,_LE);
    for(j1 = 1; j1 <= ((orntp-smptp)>>1); j1++) {
        p += 2; j3 = File.read_uint16(p,_LE);
        if(j3-j2 != 0x62) return false;
        j2 = j3;
    }
    for(j1 = 1; j1 <= ((ptntp-orntp)>>1); j1++) {
        p += 2; j3 = File.read_uint16(p,_LE);
        if(j3-j2 != 0x22) return false;
        j2 = j3;
    }

    return true;
}
if(!bDetected && File.isDeepScan() && isSQTracker()) {
    sName = "Scalex Qjeta Tracker module (.SQT)"; sVersion  = "compiled"; bDetected = true;
    sOption("ord:"+ord+" ptn:"+ptn)//+" len:"+len)
}

function isST() {
    //ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundST
    if(File.getSize() < 6) return false;
    tempo = File.read_uint8(0); if(tempo == 0 || tempo > 0x20) return false; //delay
    postp = File.read_uint16(1,_LE); if(postp<0x7E || postp>File.getSize()) return false; //positions ptr
    orntp = File.read_uint16(3,_LE); if(orntp<0x7E || orntp>File.getSize()) return false; //ornaments ptr
    ptntp = File.read_uint16(5,_LE); if(ptntp<0x7E || ptntp>File.getSize()) return false; //patterns ptr
    ord = File.read_uint8(postp);
    j2 = ptntp - orntp; if(j2 == 0) return false; //j2<0: ST, >0: S_SONIC
    fID = false;
    if(j2 > 0) { if((j2%0x21) > 0) return false; }
    else if((j2%0x21) > 0) {
        if((j1<55) || (((j1-55)%0x21) > 0)) return false;
        fID = true; //we'll check for a preset ID at the end
    }
    j = File.read_uint8(postp)*2+3;
    if(j2 < 0) { if(j+j2 != 0) return false; }
    else if(j+postp-orntp != 0) {
        if((ptntp < 82) || (j+postp-ptntp+55 != 0)) return false;
        fID = true;
    }
    j = orntp+0x21;
    if(j > 65535 || j > File.getSize()) return false;
    while(j) { j--; if(File.read_uint8(j) > 0) return false; if(j == orntp) break }
    j = ptntp; j1 = 0; j2 = 0; ptn = 0;
    while((j+6 <= File.getSize()) && (j+6 < 65536) && (File.read_uint8(j) < 0xFF)) {
        //find the highest channel data pointer
        j++; //ptn #
        j2 = File.read_uint16(j,_LE); if(j1 < j2) j1 = j2; j += 2; //channel A data ofs
        j2 = File.read_uint16(j,_LE); if(j1 < j2) j1 = j2; j += 2; //ditto B
        j2 = File.read_uint16(j,_LE); if(j1 < j2) j1 = j2; j += 2; //ditto C
        ptn++
    }
    if((File.read_uint8(j) < 0xFF) ) return false;
    if(j1 > File.getSize()) return false;
    if(File.read_uint8(j1-1) < 0xFF) return false;
    bad = 0; //some modules are a bit more broken! Like "- unknown/ay5.stc" which lost some of its final bytes
    while(1) { //scan through the channel data at the end of file
        if(0x83 <= File.read_uint8(j1) <= 0x8E) j1++; //I don't get what this does, no docs either
        j1++;
        if(j1 > 65535) return false;
        if(j1 > File.getSize())
            if(File.isHeuristicScan()) { bad = 1; break }
            else return false;
        if((File.read_uint8(j1) == 0xFF) || (j1 == File.getSize())) break
    }
    if(File.read_uint8(j1) == 0xFF) len = j1+1;
    // len = File.read_uint16(0x19,_LE); // because ~somebody~ used it for names
    if(fID)
      if( (File.read_ansiString(ptntp-55,29) != 'SOUND TRACKER COMPILATION OF ') &&
        (File.read_ansiString(ptntp-55,28) != 'KSA SOFTWARE COMPILATION OF ') ) return false;

    return true;
}
if(!bDetected && File.isDeepScan() && isST()) {
    sName = "Sound Tracker module (.STC)"; sVersion  = "v1.x"; bDetected = true;
    if(bad) sVersion += "/malformed"+bad;
    sz = File.read_uint16(0x19,_LE); i_d = File.read_ansiString(7,18);
    if(['SONG BY ST COMPILE','SONG BY MB COMPILE','SONG BY ST-COMPILE',
      'SOUND TRACKER v1.1','S.T.FULL EDITION  ','SOUND TRACKER v1.3'].indexOf(i_d) >= 0)
        msg = '';
    else
        msg = i_d;
    if(sz != File.getSize()) { //the message must've hit the size field, HAPPENS
        if(File.isVerbose()) sOption("ord:"+ord+" ptn:"+ptn+" sz:"+outSz(len));
        if(32 <= (sz&0xFF) <= 0x7F) {
            msg += String.fromCharCode(sz&0xFF);
            if(32 <= (sz>>8) <= 0x7F) msg += String.fromCharCode(sz>>8)
        }
    } else
        //if(sz == len)
            if(File.isVerbose()) sOption("ord:"+ord+" ptn:"+ptn+" sz:"+sz); /*else;
        else if(File.isVerbose())
          sOption("ord:"+ord+" ptn:"+ptn+" sz:"+sz+" calc.sz:"+len); //keeping len in: debug
          */
    if(File.isVerbose()) sOptionT(msg,"msg: ");
}

function isST3() {
    //ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundST3
    if(File.getSize() < 8) return false;
    orntp = File.read_uint16(5,_LE); if(orntp > File.getSize()-6) return false; //ST3 ornaments/STC patterns table ptr
    ptntp = File.read_uint16(7,_LE); //ST3 patterns table ptr
    j1 = ptntp-orntp; if(j1 <= 0) return false;
    smptp = File.read_uint16(3,_LE); //samples table ptr
    j2 = orntp-smptp; if(j2 <= 0) return false;
    postp = File.read_uint16(1,_LE); //positions table ptr
    j3 = smptp-postp; if(j3 > File.getSize()) return false;
    j4 = postp-9; if(j4<=0) return false;
    fID = false;
    if(j4%130) {
        if(j4 < 55 || ((j4-55)%130)) return false;
        fID = true
    }
    smp = File.read_uint8(smptp); if(!smp || smp>16) return false; // smp # , j5
    j = smp*130+9; if(fID) j += 55;
    if(posptr != j) return false;
    ord = File.read_uint8(j); if(!ord) return false; //pos # , j6
    j += ord*2+1;
    if(smptp != j && smptp != j+2) return false;
    orn = File.read_uint8(orntp); if(!orn || orn>16) return false; // orn # , j7
    j = smptp+smp*2+1+orn*32; if(orntp != j) return false;
    j += orn*2+1; if(File.read_uint16(ptntp,_LE) != j) return false;
    base = File.read_uint16(smptp+1,_LE)-9;
    if(fID) base -= 55; if(base < 0) return false;
    j = File.read_uint16(File.read_uint8(orntp+1),_LE)-base;
    if(j != smptp+j5*2+1) return false;
    for(i=j; i < j+32; i++) if(File.read_uint8(i)) return false;
    j8 = -1; j = postp+2;
    for(i=0; i<ord; i++) {
        b = File.read_uint8(j); if(b%6) return false;
        if(j8 < b) j8 = b; j += 2
    }
    len = ptntp+j8+6;
    if(len-1 < File.getSize()) return false;
    if(base+j > 65536) return false;
    if(fID && !File.compare("''KSA SOFTWARE COMPILATION OF ''",9)) return false;
    delay = File.read_uint8(0) //delay
    return true;
}
if(!bDetected && File.isDeepScan() && isST()) {
    sName = "Sound Tracker module (.ST3)"; sVersion  = "v3.0"; bDetected = true;
    if(bad) sVersion += "/malformed"+bad;
    if(File.isVerbose()) {
        sOptionT(FIle.read_ansiString(9,55));
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" delay:"+delay+" @"+Hex(base)+" sz:"+outSz(len));
    }
}

function isSidMon1() {
    //ref http://asle.free.fr/prowiz/prowiz.src.zip / r/sidmon1.c
    bad = false; n = 0;
    for(j=0;j<100;j++) {
        p = File.read_uint16(j+2,_BE);
        if(File.compare("' SID-MON BY R.v.VLIET'",p+2+j)) return true; //not for eg. "beat to the pulp"
    }
    if((p+2 > File.getSize()) || (p < 16)) return false;
    if((File.read_uint8(6) != 0xFF) || (File.read_uint8(7) != 0xD4)) return false;
    n = File.read_uint16(2+p-2,_BE);
    o = File.read_uint16(2+p-6,_BE);
    j = File.read_uint16(2+p-10,_BE);
    if((p+n+6  > File.getSize()) || (p+o+6 > File.getSize())) return false;
    if((n <= o) && (n != 1)) return false;
    if(n == 1) {
        pt = p+2+o; if(pt+4 > File.getSize()) return true;
        pt += 4; l = 1; n = o-j;
        while(l < n) {
            l = File.read_uint32(pt,_BE);
            if((pt+4 > File.getSize()) || (!l)) return true;
            pt += 4;
        }
        pt -= 4
    }
    else {
        n += p+2;
        p = File.read_uint16(n+2,_BE); if((n+p > File.getSize()) || (p%32)) return false;
        if(!p) {
            for(;;p+=32) {
                if(p+36 > File.getSize()) return false;
                j = File.read_uint32(n+p+4,_BE);
                k = File.read_uint32(n+p+8,_BE);
                l = File.read_uint32(n+p+12,_BE);
                if((j > k) || (k >= l)) break
            }
        }
        o = 0;
        for(j=0; j < p/32; j++) {
            k = File.read_uint16(p+14+j*32,_BE);
            if(k > o) o = k
        }
        n += o+4;
        if(n > File.getSize()) bad = true;
    }
    return true;
}
if(!bDetected && File.isDeepScan() && isSidMon1()) {
    sName = "SIDMON module (.SID)"; sVersion  = "v1"; bDetected = true;
    if(bad) sVersion += "/malformed";
    if(File.isVerbose()) {
        if(n) sOption(outSz(n),"sz:");
        else sOption("at pos. "+Hex(j))
    }
}

function isSoundtrackerMOD() {
    //ref http://asle.free.fr/prowiz/prowiz.src.zip / r/SoundTracker.c
    // & https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_mod.cpp
    debug = 0; //verbosity up to 3, where every note/command is shown
    if(File.getSize() < 555) return false;
    function invalidChars(ofs,len) {
        var c = 0, s; for(var i = 0; i < len; i++) {
            s = File.read_int8(ofs+i); if(s && s < 0x20 && [-1,0xA,0xD,0xE].indexOf(s) < 0) c++;
        } return c
    }
    function npTest(n) { const valid = [0, 856,808,762,720,678,640,604,570,538,508,480,453,
          428,404,381,360,339,320,302,285,269,254,240,226,
          214,202,190,180,170,160,151,143,135,127,120,113];
        return valid.indexOf(n) >= 0 || valid.indexOf(n-1) >= 0 || valid.indexOf(n+1) >= 0
    }
    const UST1_00=0,UST1_80=1,ST2_00_Exterminator=2,ST_III=3,ST_IX=4,MST1_00=5,ST2_00=6;
    var ic = 0, hasDiskNames = true, minV = UST1_00;
    ic = invalidChars(0,20);
    bad = ""; allsmpsz = allvols = smp = minV = 0; snames = [];
    for(k=0; k < 15; k++) {
        sname = decAnsi(20+k*30,22,CPAmiga).trim();
        if(File.compare("'st-'....':'",20+k*30) || File.compare("'ST-'....':'",20+k*30)) ic -= 22;
        else snames.push(sname);
        ic += invalidChars(20+k*30,22);
        if(ic > 48) { if(debug)_log("Fault: title or sample names too broken"); return false; }
        if(File.read_uint8(20+k*30) && !/^st-[0-9a-f]\d:/i.test(sname))
            hasDiskNames = false;
        slen = File.read_uint16(42+k*30,_BE)*2;
        if(slen > 0xFFFF) { if(debug)_log("Fault: sample #"+k+" too large"); return false; }
        if(File.read_uint8(44+k*30) > 15) { if(debug)_log("Fault: bad finetune"); return false; } //finetune
        svol = File.read_uint8(45+k*30); if(svol > 0x40) return false;
        sls = File.read_uint16(46+k*30,_BE), sll = File.read_uint16(48+k*30,_BE);
        if(slen > 4999 || sls > 4999) minV = Math.max(minV,MST1_00);
        if(slen) smp++;
        allsmpsz += slen; allvols += svol;
//if(debug>1)_log("smp["+k+"] <"+File.read_ansiString(20+k*30,22)+"> ["+slen+"] @"+Hex(20+k*30)+" -> vol:"+svol+" loopstart:"+sls+" looplen:"+sll);
        if(sll > slen+2) {
            if(debug) _log("!smp "+k+" LpLen "+sll+" > sSz "+slen); bad = bad.addIfNone("!sLpLen>sSz") }
        if(slen && sls >= slen) {
            if(debug) _log("!smp "+k+" LpStart "+sls+" >= sSz "+slen); bad = bad.addIfNone("!sLpStart>=sSz") }
        if(sls && !sll) {
            if(debug) _log("!smp "+k+" LpStart "+sls+" LpSz 0"); bad = bad.addIfNone("!sLpStart0LpSz") }
        if(sls && !slen) {
            if(debug) _log("!smp "+k+" LpStart "+sls+" sSz 0"); bad = bad.addIfNone("!sLpStart0SSz") }
    }
//_log("5:allsmpsz="+allsmpsz+" allvols="+allvols);
    if(allsmpsz < 8 || !allvols) { if(debug)_log("Fault: smps too small or no smpvols"); return false; }
    ord = File.read_uint8(0x1D6);
    if(ord > 128) return false;
    restartp = File.read_uint8(0x1D7);
    if(restartp > 220) return false;
    if(!restartp || /jjk55/.test(File.read_ansiString(0,6))) restartp = 0x78;
    bpm0 = 125;
    if(restartp != 0x78) {
        bpm0 = ((709379*125/50)/((240-restartp)*122)).toFixed(2);
        if(minV > UST1_80) minV = Math.max(minV, hasDiskNames?ST_IX:MST1_00);
        else minV = Math.max(minV, hasDiskNames?UST1_80:ST2_00_Exterminator);
    }
    ptn = offptn = badptn = ord_ = 0; usedptns = []; usedsmps = [];
    for(o=0; o < 128; o++) {
        x = File.read_uint8(0x1D8+o); if(x > 63) { if(debug)_log("Fault: impossible ptn #"+x); return false; }
        if(ptn <= x) {
            ptn = x+1;
            if(o < ord) { offptn = ptn; if(usedptns.indexOf(x) < 0) usedptns.push(x) }
        }
        if(x >= badptn) badptn = ptn+1;
        if(x) ord_++; //a heuristic to count out suspiciously many zeroes
    } ord_++;
//if(debug)_log("ptn:"+ptn+" off.ptn:"+offptn+" badptn:"+badptn+" used ptns:"+usedptns)
    if(!restartp && !ord && !ptn) { if(debug)_log("Fault: no bpm, no ord, no ptn"); return false; }
    songszoffptn = 0x258+0x400*offptn;
    if(songszoffptn > File.getSize()) { if(debug)_log("Fault: patterns cut below "+songszoffptn); return false; }
    ic = tnDxx = notes = 0;
    for(i=0; i < ptn; i++) {
        emptycmd = nDxx = ptnic = ptnotes = 0;
        for(row=0; row < 64; row++) for (chn=0; chn < 4; chn++) {
            var ofs = 0x258+(i<<10)+(row<<4)+(chn<<2);
            var x = readBytes(ofs, 4);
            if(File.isDeepScan()) {
                if(!emptycmd && !x[0] && !x[1] && !x[2] && !x[3]) {
                    emptycmd++; if(emptycmd > 32) minV = ST2_00
                } else emptycmd = 0 }
            var smpn = (x[0]&0xF0) | ((x[2]&0xF0) >> 4), np = ((x[0]&0xF) << 8) + x[1], fx = x[2]&0xF;
//if(debug>2)_log("@"+Hex(ofs)+" ptn "+i+": "+Hex(x[0])+" "+Hex(x[1])+" "+Hex(x[2])+" "+Hex(x[3])+": smp#"+smpn+" np:"+Hex(np)+" fx:"+Hex(fx));
            if(usedptns.indexOf(i) >= 0)
                if(smpn > 15) { ptnic++;
                    if(debug>1)_log("   bad smp "+np+" @"+Hex(ofs)+" ptn"+i+" row"+row+" chn"+chn)
                }
                else if(usedsmps.indexOf(smpn) < 0) usedsmps.push(smpn);
            if((File.isDeepScan() || notes < 100) && !npTest(np)) { ptnic += 2; bad = bad.addIfNone("!badnote");
                if(debug>1)_log("   bad note "+np+" @"+Hex(ofs)+" ptn"+i+" row"+row+" chn"+chn)
            } else if(np) ptnotes++;
            if(File.isDeepScan()) switch(fx) {
            case 1: case 2:
                if(x[3] > 0x1F && minV == UST1_80) minV = hasDiskNames ? UST1_80 : UST1_00;
                else if(fx == 1 && x[3] > 0 && x[3] < 3) minV = Math.max(minV,ST2_00_Exterminator);
                else if(fx == 1 && (x[3] == 0x37 || x[3] == 0x47) && minV <= ST2_00_Exterminator)
                    minV = hasDiskNames ? UST1_80 : UST1_00;
                break;
            case 0xB: minV = ST2_00; break;
            case 0xC: case 0xD: case 0xE: minV = Math.max(minV,ST2_00_Exterminator);
                if(fx == 0xD) { emptycmd = 1; if(!x[3] && !row) break; nDxx++ } break;
            case 0x0F: if(minV < ST_III) minV = ST_III; break;
            }
        }
        if(ptn >= offptn && ptnic > 64) ptn = offptn;
        else { ic += ptnic; notes += ptnotes }
        if(ic > 512) { if(debug)_log("Fault: "+ic+" invalid characters"); return false }
        if(nDxx && nDxx < 3) minV = ST2_00; tnDxx += nDxx;
    }
    if(tnDxx > ptn+32 && minV == ST2_00) minV = MST1_00;
    if(File.isDeepScan()) switch(minV) {
    case UST1_00: tracker = "Ultimate ST 1.0~21"; break;
    case UST1_80: tracker = "Ultimate ST 1.8~2.0"; break;
    case ST2_00_Exterminator: tracker = "ST 2.0 / D.O.C. ST II"; break;
    case ST_III: tracker = "Defjam ST III / Alpha Flight ST IV / D.O.C. ST IV / VI"; break;
    case ST_IX: tracker = "D.O.C. ST IX"; break;
    case MST1_00: tracker = "Master ST 1.0"; break;
    case ST2_00: tracker = "ST 2.0~2"; break;
    default: tracker = "???"
    }
    sz = 0x258+0x400*ptn+allsmpsz;
    var sizediff = Math.abs(sz, File.getSize()), orddiff = Math.abs(ord-ord_);
    var nzord = ord?ord:0.0001, badscore = 0;
//if(debug)_log("ord diff:"+orddiff+" ord:"+nzord.toFixed(0)+" / ="+(orddiff/nzord).toFixed(2));
    if(ic > 20) badscore++; if(smp < 3 || smp == 16) badscore++;
    if(sizediff > 2048) badscore++; if(sz > File.getSize()) badscore++;
    if(!ord || !offptn) badscore++; if(orddiff/nzord > 0.8) badscore++;
    if(badscore >= 5) { //one free strike
        // If there are enough bad notes, the samples are kinda few or kinda many,
        //the size is more than two patterns different (especially smaller),
        //either no length or no "official" patterns are there, and the orderlist is mostly zeroes...
        if(debug)_log("Fault: too suspicious"); return false }
    if(ic) bad = bad.addIfNone("!baddata="+ic);
    if(sz > File.getSize()) bad = bad.addIfNone("!short");
    return true;
}
if(!bDetected && isSoundtrackerMOD()) {
        sName = "SoundTracker module (.STK,.MOD)"; bDetected = true;
        if(bad != "") sVersion = "malformed"+bad;
        if(File.isVerbose()) {
            sOptionT(File.read_ansiString(0,0x14));
            if(File.isDeepScan()) sOption(tracker,"in:");
            sOption(snames.join(" "),"smpn/msg:");
            sOption(" bpm0:"+bpm0+" ord:"+ord+(ord_!=ord?"/"+ord_:"")+" ptn:"+ptn
                +(offptn != ptn ? "/"+offptn : "")+" smp:"+smp+(File.isDeepScan() ? " notes:"+notes : "")
                +" sz:"+outSz(sz))
        }
    }

function isADXAHX() { //non-module! sanity check a bit because 8000 is too broad
    if(!File.compare("8000")) return false;
    fmt = File.read_uint8(4);
    if([2,3,4,0x10,0x11].indexOf(fmt)<0) return false;
    if(File.read_uint8(6)!=4) return false; //4 bit per sample only
    if([1,2].indexOf(File.read_uint8(7))<0) return false; //1/2 ch only
    nV = File.read_uint8(0x12);
    if([3,4,5].indexOf(nV)<0) return false;
    return true;
}
if(!bDetected && File.isHeuristicScan() && isADXAHX()) {
    //from https://wiki.multimedia.cx/index.php/CRI_ADX_file
    bDetected = true;
    sVersion = "";
    if([2,3,4].indexOf(fmt) >= 0)
        sName = "CRI ADX 4-bit ADPCM audio (.ADX)";
    else if([0x10,0x11].indexOf(fmt) >= 0)
        sName = "CRI AHX 4-bit ADPCM audio (.AHX)";
    switch(nV) {
        case 3: sVersion = "v3"; break;
        case 4: sVersion = "v4"; break;
        case 5: sVersion = "v4NL"
    }
    switch(fmt) {
        case 2: sVersion+=" PPC"; break;
        case 3: sVersion+=" std"; break;
        case 4: sVersion+=" exSc"; break;
        case 0x11: sVersion+=" AHX" }
    if(File.read_uint8(0x13) > 0) sVersion += " encrypted";
    if(File.isVerbose()) {
        sOption(File.read_ansiString(pcprt,6));
        pcprt = File.read_uint16(2,_BE)-2;
        sOption("blk: "+File.read_uint8(5));
        sOption("ch: "+File.read_uint8(7));
        sOption("sr: "+File.read_uint32(8,_BE)+"Hz");
        sOption("smp: "+File.read_uint32(0x0C,_BE));
    }
}

function isDigiIllu() {
    //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/DigitalIllusion.c
    if(File.getSize()<17) return false;
    smp = File.read_uint16(0,_BE); if(smp > 31) return false; //num of samples
    ptntp = File.read_uint32(2,_BE); if(ptntp < smp*8+2) return false; //pattern table ptr
    ptndp = File.read_uint32(6,_BE);  if(ptndp-ptntp > 0x80) return false; //pattern data ptr
    smpdp = File.read_uint32(10,_BE); if(smpdp > 0xFFFF) return false; //sample data ptr
    if(ptndp <= ptntp || smpdp <= ptntp || smpdp <= ptndp) return false;
    if(ptntp > File.getSize() || ptndp > File.getSize() || smpdp > File.getSize()) return false;
    allsmpsz = 0; l = 0;
    for(j=0; j < smp; j++) {
        smpsz = File.read_uint16(j*8+14,_BE)<<1; if(smpsz > 0xFFFF) return false;
        lstart = File.read_uint16(j*8+18,_BE)<<1; if(lstart > 0xFFFF || lstart > smpsz) return false;
        lsz = File.read_uint16(j*8+20,_BE)<<1;
        if(lsz > 0xFFFF || lsz > smpsz+2 || lstart+lsz > smpsz+2 || (lstart && lsz <= 2)) return false;
        if((lstart || lsz > 2) && !smpsz) return false;
        if(File.read_uint8(16+j*8) > 0xF || File.read_uint8(17+j*8) > 0x40) return false; //fine & vol checks
        allsmpsz += smpsz
    }
    if(allsmpsz <= 2) return false;
    for(i=ptntp; i < ptndp-1; i++)
        if(File.read_uint8(i) > 0x80) return false;
    if(File.read_uint8(ptndp-1) < 0xFF) return false;
    sz = allsmpsz + File.read_uint32(10,_BE);
    return true
}
if(!bDetected && File.isDeepScan() && isDigiIllu()) {
    sName = "Digital Illusions packed module (.DI)"; bDetected = true;
    if(File.isVerbose()) {
        sOption(outSz(sz),"sz:"); if(sz > File.getSize()) sVersion = "malformed!short"
    }
}

function isPMD() {
    // ref https://raw.githubusercontent.com/ValleyBell/MidiConverters/master/pmd_SeqFormat.txt
    // & https://web.archive.org/web/20220928062748/https://lithcore.cn/2318/
    if(File.getSize() < 37) return false;
    nV = File.read_uint8(0); if(nV < 0xFF) if(nV > 0x0F) return false;
    for(i=0; i < 13; i++) {
        p = File.read_uint16(1+i*2,_LE);
        if((p && p < 0x1A)  || p+1 > File.getSize()) return false
    }
    extrap = File.read_uint16(0x19,_LE)-3; if(File.read_uint8(extrap+3) != 0xFE) return false;
    extra_type = File.read_uint8(extrap+2); if(extra_type < 0x40 || extra_type > 0x4F) return false;
    if(extra_type < 0x42) ttype = "PCP/P86";
    else if(extra_type<0x48) ttype = "PPS";
    else if(nV==0xFF) ttype = "PPZ"; else ttype = "FM Towns";
    extra = File.read_uint16(extrap,_LE)+1; if((extra > 0 && extra < extrap) || extra > File.getSize()) return false;
    for(i=0; i < 4; i++) {
        p = File.read_uint16(extra+i*2,_LE);
        if(p < 27 || p+1 > File.getSize()) return false
    }
    return true
}
if(/*!bDetected && */File.isDeepScan() && isPMD() ) {
    bDetected = true;
    sName = "Professional Music Driver module (.M,.M2)";
    sVersion = "v"+nV+" t"+Hex(extra_type).substr(0,2)+":"+ttype+" tagptrs:"+Hex(extra);
    if(File.isVerbose()) {
        fnames = [];
        if(extra_type >= 0x48) {
            n = File.read_codePageString(File.read_uint16(extra,_LE)+1,0x100,"Shift_JIS"); extra+=2;
            if(n != "") fnames.push("PPZ:"+n)
        }
        if(extra_type >= 0x42) {
            n = File.read_codePageString(File.read_uint16(extra,_LE)+1,0x100,"Shift_JIS"); extra+=2;
            if(n != "") fnames.push("PPS:"+n)
        }
        n = File.read_codePageString(File.read_uint16(extra,_LE)+1,0x100,"Shift_JIS"); extra+=2;
        if(n != "") fnames.push("PPC/P86:"+n);
        title = File.read_codePageString(File.read_uint16(extra,_LE)+1,0x100,"Shift_JIS");
        if(title == "\x1A") title = "";
        artist = File.read_codePageString(File.read_uint16(extra+2,_LE)+1,0x100,"Shift_JIS");
        if(artist == "\x1A") artist = "";
        arenji = File.read_codePageString(File.read_uint16(extra+4,_LE)+1,0x100,"Shift_JIS");
        if(arenji == "\x1A") arenji = "";
        rem = File.read_codePageString(File.read_uint16(extra+6,_LE)+1,0x100,"Shift_JIS");
        if(rem == "\x1A") rem = "";
        sOption(title);
        sOption(artist,"by: ");
        sOption(arenji,"mixed by: ");
        sOption(rem);
        sOption(fnames.join(", "),"fn: ")
    }
}

function isRobHubbardRH() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/RobHubbard/src/RobHubbard_v7.asm
    //the FX files not included -- TODO
    if(!File.compare("6000.... 6000.... 6000.... 6000.... 6000....  41FA.... ........ 4E7541FA"))
        return false; //Check2
    a2 = 0x40; d4 = 8;
    do { //loop2
        if(File.read_uint16(a2,_BE) == 0x2418) break;
        a2 += 2; d4--
    } while(d4);
    if(!d4) return false; //found2
    smp = d3 = File.read_uint8(a2-1)+1;
    a2 = 0x36; d4 = 5;
    do { //loop3
        if(File.read_uint16(a2,_BE) == 0x41FA) break;
        a2 += 2; d4--
    } while(d4);
    if(!d4) return false; //found3
    a2 += 2;
    a4 = a2;
    d4 = a2+File.read_uint16(a2,_BE)+2; //minus a3 = moduleptr, 0 in our case
    if(File.read_uint16(a4+2,_BE) == 0xD1FC) d4 += 0x40;
    a3 = d4-2; //again +moduleptr; then moduleptr[a6] = a3 (puts in sampleptr); a6 += 4;
    d5 = 0; a2 = a3;
    do { //loop4
        d1 = File.read_uint32(a3,_BE);
        if(d1 > 0x10000) return false;
        d1 += 6;
        d5 += d1;
        a3 += d1;
        d3--
    } while(d3);
    if(File.read_uint16(a3,_BE) != 0x4E71) return false; //at the EOF now
    a3 = 0; a0 = 130+a3;
    d0 = 10;
    do { //loop
        if(File.read_uint16(a0,_BE) == 0x41EB) { a0 += 2; break }
        a0 += 2; d0--;
    } while(d0);
    if(!d0) return false; //else found
    d1 = 0;
    d2 = File.read_uint16(a0,_BE);
    a3 += d2;
    do { //hop
        a3 += 18; d1++;
    } while(File.read_uint16(a3,_BE));
    d2 = a2-a3;
    do { //petla
        b = File.read_uint8(a3,_BE);
        if(b != 0x84)
            if(b != 0x85)
                { a3++; d2--; if(d2 < 0) break; else continue }
        d0++; a3++; d2--
    } while(d2 >= 0);
    songsz = d4;
    sz = d4+d5;
    steps = d0;
    x = d1;
    return true;
}
if(!bDetected && File.isDeepScan() && isRobHubbardRH()) {
    sName = "Rob Hubbard's module (.RH)"; sVersion = "v1.4"; bDetected = true;
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("smp:"+smp+" steps:"+steps+" songsz:"+songsz+" sz:"+outSz(sz))
    }
}

function isPuma() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/PumaTracker/src/PumaTracker_v2.asm
    //ref http://lclevy.free.fr/amiga/exotic/puma_str.txt
    if(File.read_uint8(12)) return false;
    ord = File.read_uint8(13)+1;
    a0 = 0x50+ord*14; //patterns table
    if(a0 >= File.getSize()) return false;
    if(!File.compare("'patt'",a0)) return false; a0 += 4;
    d1 = 32;
    while(a0 < File.getSize()) {
        t = File.read_uint8(a0+3); if(!t) return false;
        d1 -= t; if(d1 < 0) return false; if(!d1) break;
        a0 += 4
    }
    if (!File.compare("'patt'",a0+4)) return false; //Check2 finishes here
    a0 = smp = d0 = d4 = 0;
    ptn = File.read_uint16(0xE,_BE);
    synsmp = File.read_uint16(0x10,_BE); //"number of sound data"
    loop = File.read_uint16(0x12,_BE);
    a1 = 0x14; //+moduleptr = smp start offsets
    for(d2 = 0; d2 < 10; d2++) { //NextSamp
        smpp = File.read_uint32(a1,_BE); a1 += 4;
        if(!smpp) continue; //-> NoSamp
        else smp++;
        if(!d4 || smpp >= d4) d4 = smpp; //d4 <- maxsmpp
    } //NoSamp
    a1 = 0; bad = false; //moduleptr
    if(!d4) { //jump to .Samples it if d4 != 0
        d1 = synsmp;
        a1 += 80;
        while (d1 >= 0) { // .FindSample
            if(a1 > File.getSize())
                if(File.isHeuristicScan())
                    { sz = -1; bad = true; return true } //too short
                else return false;
            t = File.compare("'in'",a1); a1 += 2; if(!t) continue;
            t = File.compare("'st'",a1); a1 += 2; if(!t) continue;
            d1--;
        }
        sz = a1
    } else { // .Samples
        a2 = a1+0x3C; //0x3C is the ofs of samples lengths in words
        a1 += 0x14;
        sz = 0; //max smp end
        for(d2 = 0; d2 < 10; d2++) {// .FindSize
            smpsz = File.read_uint32(a1,_BE); a1 += 4;
            d1 = File.read_uint16(a2,_BE); a2 += 2;
            if(d1&1) d1 --;
            d1 += d1+smpsz;
            if(sz < d1) sz = d1;
        }
    }
    if(sz > File.getSize())
        if(File.isHeuristicScan()) { bad = true; return true } //too short
        else return false;
    songsz = d4;
    return true;
}
if(!bDetected && File.isDeepScan() && isPuma()) {
    sName = "PumaTracker module (.PUMA)"; sVersion = "v1.1"; bDetected = true;
    if(bad) sVersion += "/malformed";
    if(File.isVerbose()) {
        sOption(File.read_ansiString(0,0x0C).trim());
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" synsmp:"+synsmp
            +" lp:"+loop+" sz:"+outSz(sz))
    }
}

function isSeanConran() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/SeanConran/src/Sean Conran_v3.asm
    //Check2
    if(File.compare("0FFF0FE2") || File.compare("10000FE2")) {
        if(!File.compare("0FC40FA7 0F8B0F6E",4)) return false; //test1
        p = 0x124; //start searching for initp from here
        a0 = 8;
    }
    else
        if(File.compare("0F1C0F0E 0F000EF2 0EE40ED6")) {
            p = 0x1C4;
            a0 = 0xA8; //skip to LastCheck
        }
        else return false;
    do { //InitPlayer.FindInit
        t = File.read_uint32(p,_BE); if(!t) return false;
        p += 2; if(p > File.getSize()) return false
    } while([0x45FA,0x43F9,0x41F9].indexOf(t>>16) < 0);
    initp = p-2;
    //LastCheck
    a0 += 0x11C;
    for(d1 = 0; d1 < 0x80; d1++) { //CheckSFX
        if(File.compare("7F7F7F7F",a0) || File.compare("FFFF",a0)) return false;
        a0 += 2
    }
    // a0 = GetListData(0);
    x = IntAddress = 0; Twin = false;
    do { //Next
        t = File.read_uint16(p,_BE);
        p += 2; if(p > File.getSize()) return false
    } while([0x7000,0x7200].indexOf(t) < 0);
    if(t === 0x7000) { //OneSub
        x = 1;
        // do { //FindSongs
        //     t = File.read_uint16(p,_BE); p += 2; if(p > File.getSize()) return false
        // } while(t != 0x41FA);
        // FirstPos = p+File.read_int16(p,_BE);
    }
    else { //t=0x7200
        while(!x && p < File.getSize()) {
            if(File.compare("00000000",p)) return false;
            if(File.compare("21C80070",p)) { //OK4
                // t = p-4; IntAddress = t+File.read_int16(t,_BE);
                p += 2;
                if(File.compare("00003B76",0x24C)) Twin = true;
            } else {
                if(File.compare("43FA",p)) {
                    t = p+2+File.read_int16(p+2,_BE); //OK0
                    // songsp = t;
                    x = (File.read_int16(t,_BE)-t) >> 3;
                }
                else if(File.compare("43EA",p)) {
                    t = File.read_int16(p+2,_BE); //OK2
                    // songsp = t;
                    x = (File.read_int16(t,_BE)-t) >> 3;
                }
                else p += 2
            }
        }
        if(!x || p >= File.getSize()) return false
    }
    // there are more checks... although they aren't really necessary here
    return true
}
if(!bDetected && File.isDeepScan() && isSeanConran()) {
    sName = "Sean Conran module (.SCR)"; sVersion = "v1.2"; bDetected = true;
    if(Twin) sVersion += "/Megatwins"
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
    }
}

function isBenDaglish() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/BennDaglish/Benn Daglishv3.asm
    if(!File.compare("6000....6000.... ....6000")) return false; //Check2
    d1 = File.read_int16(0x02,_BE); if(d1 <= 0 || (d1&1) || d1 > File.getSize()) return false;
    a0 = a1 = a3 = d1+2;
    d1 = File.read_int16(0x06,_BE); if(d1 <= 0 || (d1&1) || d1 > File.getSize()) return false;
    d1 = File.read_int16(0x0C,_BE); if(d1 <= 0 || (d1&1) || d1 > File.getSize()) return false;
    if(!File.compare("3F006100",a1)  || !File.compare("3D7C",a1+6)
      || !File.compare("41FA",a1+12)) return false;

    d0 = 0x7F;
    while(d0) //.l6
        if(File.compare("D040D040 D04041FA",a0)) {
            a0 += 8;
            a1 = a0+File.read_int16(a0,_BE); //note the value's signed
            break;
        }
        else {
            a0 += 2; d0--; if(!d0 || a0 > File.getSize()) return false
        }
    x = 0;
    BD_L7: while(1) {
        d2 = 4;
        while(d2) {
            d0 = File.read_uint16(a1,_BE) & 0xFC00;
            a1 += 2; if(a1 > File.getSize()) return false;
            if(d0) { // .not_subsong
                x--; //last subsong
                if(x < 0) x == 0;
                break BD_L7
            }
            else d2--
        }
        x++
    }
    x++;

    //size calc
    // incorrect on some files like 3d galax.bd or super cars.bd
    // but that's what EaglePlayer has, UADE debugger-tested
    d5 = d6 = 0;
    d0 = 0x80;
    while(d0) { //.l9
        t = File.read_uint16(a0,_BE);
        a0 += 2; if(a0 > File.getSize()) return false;
        if(t === 0x41FA) break; else d0--
    } if(!d0) return false;
    smpi1 = a0+File.read_uint16(a0,_BE); //.ok6 .. SampleInfo1
    a0 = 12+File.read_int16(12,_BE);
    d0 = 0x80; //.l10
    while(d0) { d0--;
        if(File.compare("D040D040 41FA",a0)) break;
        else a0 += 2;
        if(a0 > File.getSize()) return false
    }
    if(d0) { //.ok7
        a0 += 6;
        d0 = File.read_int16(a0,_BE);
        if(d0&1) a0 = 0;
        else {
            a0 += d0;
            if(File.read_int16(a0,_BE)) a0 = 0
        }
    } else a0 = 0;
    smpi2 = a0; //.ok8

    a0 = smpi1;
    while(1) { //.com1
        d0 = File.read_uint32(a0,_BE);
        a0 += 4; if(a0 > File.getSize()) return false;
        // Interestingly, the UAE emu sets flags on "move"!
        // So ↓ is a d0 check, would've probably been a0 on a real machine
        if(!d0) break;
        if(d0 >> 16) { a0 -= 4; break }
    }
    a0 -= 8; //.ok9
    smp1 = (a0 - smpi1) >> 2;
    d0 = smpi2;
    if(d0) {
        a0 = d0;
        while(1) { //.com2
            d0 = File.read_uint32(a0,_BE);
            a0 += 4; if(a0 > File.getSize()) return false;
            if(!d0) break;
            if(d0 >> 16){ a0 += 4; break }
        }
        a0 -= 8; //.ok10
        smp2 = (a0 - smpi2) >> 2;
    }
    else smp2 = 0;
    //.one_smp_info

    // calc size #1
    d3 = smp1;
    a2 = smpi1+File.read_int32(smpi1,_BE); //rel.ptr to smpinfo1 table #0?
    d1 = File.read_int32(a2,_BE);
    d2 = File.read_uint16(a2+8,_BE);
    i = d4 = 0;
    do { //.l11
        i++; if(i === smp1) break;
        a2 = smpi1+File.read_int32(smpi1+(i<<2),_BE);
          if(a2 < 20 || a2 > File.getSize()) return false;
        d4 = File.read_int32(a2,_BE);
        if(d1 > d4) continue;
        if(d1 != d4) {
            if(d4 != File.read_int32(a2+4,_BE))
                d6 = File.read_uint16(a2+10,_BE);
            else d6 = 0;
            d5 = File.read_uint16(a2+8,_BE);
        } else {
            d5 = File.read_uint16(a2+8,_BE);
            if(d2 > d5) continue;
        }
        d1 = d4; d2 = d5
    } while(i < smp1);

    //.ok11
    d2 += d6;
    sz = smpi1+d1+(d2<<1);

    // calc size #2
    if(smpi2) {
        d3 = smp2 << 2;
        a2 = smpi2+File.read_int32(smpi2,_BE);
        d1 = File.read_int32(a2,_BE);
        d2 = File.read_uint16(a2+8,_BE);
        i = d6 = 0;
        do {
            i += 4; if(i === d3) break;
            a2 = smpi2+File.read_int32(smpi2+i,_BE);
            if(a2 < 20 || a2 > File.getSize()) return false;
            d4 = File.read_int32(a2,_BE);
            if(d1 > d4) continue;
            if(d1 != d4) {
                if(d4 != File.read_int32(a2+4,_BE))
                    d6 = File.read_uint16(a2+10,_BE);
                else d6 = 0;
                d5 = File.read_uint16(a2+8,_BE);
            } else {
                d5 = File.read_uint16(a2+8,_BE);
                if(d2 > d5) continue;
            }
            d1 = d4; d2 = d5
        } while(i < d3);

        d2 += d6;
        a1 = smpi1 + d1 + (d2<<1);
        if(sz < a1) sz = a1;
    }

    return true
}
if(!bDetected && File.isDeepScan() && isBenDaglish()) {
    sName = "Ben Daglish's module (.BD)"; sVersion = "v1.2"; bDetected = true;
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
            sOption("smp:"+smp1+"+"+smp2+" sz:"+outSz(sz));
    }
}

function isAndrewParton() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Andrew Parton/SRC_AndrewParton/Andrew Parton_v2.asm
    if(!File.compare("'BANK'")) return false;
    for(i=0; i < 20; i++)
        if(File.read_uint32(4+(i<<2),_BE) >= 0x200000) return false;
    for(i=0; i < 40; i++)
        if(File.read_uint32(0x54+(i<<2),_BE) >= 0x10000) return false;
    p = 0x54; q = 4; smp = 0;
    smpt = [];
    s = 0x1E4;
    for(i=0; i < 20; i++) {
        t = File.read_uint32(q,_BE); q += 4;
        if(t) {
            smpt.push(File.read_ansiString(s,16).trim());
            smp++; s += 16 + File.read_uint32(p,_BE);
        }
        p += 4
    }
    smpsz = s;
    bad = false;
    while(s < File.getSize()) {
        t = File.read_uint8(s); s += 1;
        if(t === 0xFF) break;
    }
    if(s > File.getSize())
        if(File.isHeuristicScan()) bad = true;
        else return false;
    if(File.read_uint8(s) === 0x2F) { sz = s+1; songsz = sz-smpsz }
    else {sz = s; bad = true }
    return true
}
if(!bDetected && File.isDeepScan() && isAndrewParton()) {
    sName = "Andrew Parton's module (.BYE)"; sVersion = "v1.2"; bDetected = true;
    if(bad) sVersion += "/malformed";
    if(File.isVerbose())
        sOption("smp:"+smp+" songsz:"+songsz+" smpsz:"+smpsz+" sz:"+outSz(sz))
}

function isAshleyHogg() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Ashley Hogg/SRC_AshleyHogg/Ashley Hogg_v1.asm
    p = 0;
    for(i=0; i < 4; i++) {
        if(!File.compare("6000",p)) return false; p += 2;
        d2 = File.read_int16(p,_BE); if(d2 <= 0 || (d2&1)) return false; p += 2
    }
    if(File.compare("6000",p)) { //new check
        p += 2;
        d2 = File.read_int16(p,_BE); if(d2 <= 0 || (d2&1)) return false; p += 2;
        if(!File.compare("6000",p)) return false; p += 2;
        d2 = File.read_int16(p,_BE); if(d2 <= 0 || (d2&1)) return false;
        p += d2;
        if(!File.compare("48E7FFFE 6100",p)) return false; p += 6;
        p += File.read_int16(p,_BE);
        if(!File.compare("4DF9 00DFF000",p)) return false;
        sV = "new";
    } else //old check
        if(File.compare("303C0000 662233C0",p))
            sV = "old";
        else return false;

    if(sV === "new") { //new format
        a2 = special = 0x1C;
        a0 = File.read_int16(2,_BE);
        title = File.read_ansiString(special,File.findString(special,a0-a2,"  ")-a2).trim();
        //skipped a cycle that replaces twin spaces with enters in the info
        do {
            t = File.read_uint16(a0,_BE); a0 += 2;
        } while(t != 0x45FA || a0 > File.getSize());
        a2 = a0; a0 += File.read_int16(a0,_BE);
        if(a0 > File.getSize()-2)
            if(File.isHeuristicScan()) bad = true; else return false;
        d0 = File.read_int16(a0,_BE);
        x = d0 >> 2;
        a0 += d0 + File.read_int16(a0-2,_BE);
        do {
            if(a0 > File.getSize()-2)
                if(File.isHeuristicScan()) bad = true; else return false;
            t = File.read_int16(a0,_BE); a0 += 2;
        } while(t != 0x03F2);
        sz = a0;
        info = 'title: "'+title+'" sz:'+outSz(sz)
    }
    else { //old format
        special = 0;
        a2 = 16;
        a0 = 2; a0 += File.read_uint16(a0,_BE);
        do { t = File.read_int16(a0,_BE); a0 += 2; } while(t != 0x1970 || a0 > File.getSize());
        a1 = a0-4; a1 += File.read_int16(a1,_BE);
_log("2: a0:"+Hex(a0)+" a1:"+Hex(a1));
        do { t = File.read_int16(a0,_BE); a0 += 2; } while(t != 0x41FA || a0 > File.getSize());
        a0 += File.read_int16(a0,_BE);
_log("3: a0:"+Hex(a0));
        x = (a1-a0) >> 4;
        do { t = File.read_uint16(a2,_BE); a2 += 2; } while(t != 0xC2FC || a0 > File.getSize());
        a2 += 4; smpip = a2+File.read_uint16(a2,_BE);
        do { t = File.read_uint16(a2,_BE); a2 += 2; } while(t != 0x47FA || a0 > File.getSize());
        a0 = a2; a2 += File.read_uint16(a2,_BE);
        songsz = a2;
        do { t = File.read_uint16(a0,_BE); a0 += 2; } while(t != 0x49FA || a0 > File.getSize());
        do { t = File.read_uint16(a0,_BE); a0 += 2; } while(t != 0x49FA || a0 > File.getSize());
        a0 += File.read_uint16(a0,_BE);
        smp = (a0-smpip)/0x2C;
        d1 = 0; a1 = smpip;
        a3 = 0x54F3; //value copied from a debugging session ¯\(。⊿°)/¯
        do { //NextInfo
            d2 = File.read_int32(a1+0x20,_BE);
            if(d2 >= 0 && d1 <= d2) { d1 = d2; a3 = a1 }
            a1 += 0x2C
        } while(a0 > a1);
        d0 = File.read_uint16(a3+0x28,_BE);
        d1 += d0+d0;
        smpsz = d1;
        sz = songsz+smpsz;
        info = "smp:"+smp+" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz)
    }
    return true
}
if(!bDetected && File.isDeepScan() && isAshleyHogg()) {
    sName = "Ashley Hogg's module (.ASH)"; sVersion = sV; bDetected = true;
    if(bad) sVersion += "/malformed";
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption(info)
     }
}

function isCinemaware() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Cinemaware/Cinemaware.asm
    if(!File.compare("'IBLK'")) return false;
    d2 = File.read_uint8(4);
    if(!d2 || d2 > 0x80) return false;
    a1 = 0x16+0x8A*d2;
    aseq = File.findSignature(a1,0x104,"'ASEQ'");
    if(aseq < 0 || (aseq&1)) return false;
    p = aseq+4; sz = 0; bad = false;
    do {
        sz += 5;
        if(sz+p > File.getSize())
            if(File.isHeuristicScan()) { bad = true; break }
            else return false;
    } while(!File.compare("102F00",sz+p-3));
    sz += p; ord = Math.floor((sz-p)/100);
    return true
}
if(!bDetected && File.isDeepScan() && isCinemaware()) {
    sName = "Cinemaware module (.CIN)"; bDetected = true;
    if(bad) sVersion = "malformed";
    if(File.isVerbose()) {
        sOption("ord:"+ord+" sz:"+outSz(sz)+ " (sans ext.samples)")
    }
}

function isCustomMade() {
    //ref https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/CustomMade/CustomMade_v1.asm
    if(File.getSize() < 3000) return false;
    if(File.compare("6000.... 6000") || File.compare("4EF9.... ....4EF9")
      || File.compare("4EB9.... ....4EF9")) {
        a1 = 8; var found = false;
        do {
            if(File.compare("42280030 42280031 42280032",a1)) found = true;
            a1 += 2
        } while(!found && a1 < 0x198);
    }
    if(!found) return false;
    else if(!File.isVerbose()) return true;
    //otherwise let's dig in for smp, synthsmp, and subsongs!
    a0 = d5 = 0; d7 = 0x800; org = 0; smpi = 0; songst = 0;
    bad = false;
    L = "lp"; //it's even more mind-boggling if rewritten in if..else
    i_lp: while(d7) {
        switch(L) {
        case "lp": // @d366
            if(!File.compare("D04149FA",a0)) { L = "2"; break }
            d0 = File.read_int16(a0+4,_BE); a1 = a0+d0+4;
            Table = a1; L = "lp_c"; break;
        case "2": // play / ptntab  @d37e
            if(File.compare("48E7F8FC",a0)) { L = "2.0"; break }
            if(!File.compare("48E7FFFE",a0)) { L = "3"; break }
        case "2.0": // @d38e
            d6 = a3;
            if(!d6 || org) d6 = 20;
            else {
                a1 = a0-a3; /*play-oldplay*/ d5 -= a1; /*nowadr-dx*/ org = d5
            }
            while(d6 >= 0) { // 2.lp
                t = File.read_uint16(a0,_BE); a0 += 2;
                if(t === 0x41FA) {
                    d0 = File.read_int16(a0,_BE); // 2.2: @d3ae
                    songst = a1 = a0+d0; break // from 2.lp
                }
                d6--
            }
            L = "lp_c"; break;
        case "3": // @d3bc
            if(!File.compare("E94847F0",a0)) { L = "4"; break } //ptoff
            d1 = File.read_int16(a0+4,_BE); L = "lp_c"; break;
        case "4": // @ d3cc
            if(!File.compare("00BFD500",a0)) { L = "6"; break} //oldplay
            tmrval = (File.read_uint8(a0-1) << 8) + File.read_uint8(a0+7);
            _log("tmrval:"+Hex(tmrval));
            //tmrval = d0;
            if(File.compare("4E71",a0+20)) { L = "lp_c"; break }
            if(File.compare("21FC",a0+28)) a3 = File.read_uint32(a0+30,_BE);
            else if(File.compare("C000",a0+32)) a3 = File.read_uint32(a0+32,_BE);
            else a3 = File.read_uint32(a0+22,_BE);
            L = "lp_c"; break;
        case "6": // @ d410
            if(!File.compare("42A8001C",a0)) { L = "7"; break } //smpinfo
            d0 = File.read_int16(a0+6,_BE); smpi = a1 = a0+d0+6;
            L = "lp_c"; break;
        case "7":
            if(File.compare("E44843FA",a0)) { L = "7.1"; break } //oldvoc1
            if(!File.compare("E448207B",a0)) { L = "8"; break } //oldvoc1
        case "7.1": // @d438
            d6 = a4; if(!d6) { L = "lp_c"; break }
            d0 = File.read_int16(a0+4,_BE); // @d43c
            a4 -= File.read_int32(a0+d0+4,_BE); //voc0-oldvoc0
            if(!org) { d5 -= a4; org = d5 }
            L = "lp_c"; break;
        case "8": // @d44e
            if(File.compare("48E700F0",a0)) { //voc0
                d0 = File.read_int16(a0+6,_BE); // @d456
                a4 = a0+d0+6;
            }
        case "lp_c": // @d45e
            a0 += 2; if(File.compare("1AC01940",a0)) break; // -> ex
            d7--; L = "lp"
        }
    }
    songst += d1; // ex @d46c
    a1 = songst;
    x = 0;
    while(1) { //Find
        if(File.compare("DFF0A0",a1+1)) break;
        if(a1 > File.getSize()) { bad = true; return true }
        x++; a1 += 16
    }

    a1 = smpi; smp = synsmp = 0;
    while(!File.read_uint32(a1+28,_BE)) {
        a2 = File.read_uint32(a1,_BE)-org;
        a24 = File.read_uint16(a2+4,_BE);
        if(a24 === 2 || a24 === 16) smp++; else synsmp++;
        a1 += 32
    }
    return true
}

if(!bDetected && File.isDeepScan() && isCustomMade()) {
    sName = "Ivo Zoer & Ron Klaren's CustomMade module (.CM)"; bDetected = true;
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("smp+syn:"+smp+"+"+synsmp+" timer:"+tmrval)
        //" Origin:"+Hex(org)+" SongsTab:"+Hex(songst)+" SamplesInfo:"+Hex(smpi)+" Table:"+Hex(Table))
    }
}

function isDaveLoweNew() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/DaveLoweNew/src/Dave Lowe New.s
    // ref Dave Lowe New_v2.asm
    a1 = 0; bad = ""; d1 = File.read_int16(0,_BE);
    if(d1 === 4) { if(!File.read_uint32(0x18,_BE))  a1 += 4 }
    else if(d1 === 8)  a1 += 4;
    else return false;
    a1 += 4; a2 = a1;
    for(d2=0; d2 < 4; d2++) { //FirstCheck
        if(File.read_int16(a1,_BE)) return false; a1 += 2;
        d1 = File.read_int16(a1,_BE); a1 += 2; if(d1 <= 0 || (d1&1)) return false;
    }
    for(d0=0; d0 < 4; d0++) { //SecondCheck
        a1 = d1 = File.read_int32(a2,_BE); a2 += 4;
        d2 = File.read_int32(a1,_BE);
        if(File.read_int16(a1,_BE)) return false; a1 += 2;
        d1 = File.read_int16(a1,_BE); a1 += 2;
        if(d1 <= 0 || (d1&1)) return false;
        a1 = d2;
        t = File.read_int16(a1,_BE);
        if(t === 0x30) { a1 += 2; t = File.read_int16(a1,_BE); }
        if(t === 12) {
            a1 += 6;
            t = File.read_int16(a1,_BE); a1 += 2;
            if(t === 4) break;
        }
    }
    d2 = File.read_int32(a1,_BE); if(File.read_int16(d2,_BE) != 1) return false;
    a1 = a2 = d2 >> 16; if(a1) return false;
    fmt = 1;
    if(!File.read_uint32(0x18,_BE)) {
        fmt = 0;
        x = (File.read_int16(2,_BE)-8) >> 5
    }
    else {
        x = 0; p = File.read_uint16(0,_BE);
        while(p < File.getSize()) {
            t = File.read_int16(p,_BE); p += 2; if(t) break;
            t = File.read_int16(p,_BE); p += 2; if(!t || (t&1)) break;
            x++
        }
        x >>= 2
    }
    bad = 0; sz = 0; a1 = d2;
    while(a1 < File.getSize()) {
        a2 = a1; a1 -= 14;
        if(File.read_int16(a1,_BE) != 1) break
    }
    //smpi = a2;
    t = File.read_int16(a2-2,_BE);
    if(t != 8 && t != 0) a2 += 14;
    sz = 0; smp = 0;
    while(a2 < File.getSize()) { //CheckInfo
        t = File.read_int16(a2,_BE); if(t != 1) break; else smp++;
        a2 += 2;
        d2 = File.read_int32(a2,_BE); a2 += 6; t = File.read_int32(a2,_BE);
        if(t > d2) d2 = t;
        if(d2 > sz) sz = d2;
        a2 += 6
    }
    if(sz > File.getSize()) bad = bad.addIfNone("!short");
    if(a2 >= File.getSize()) { bad = bad.addIfNone("!badsmpinfo"); return true }
    sz += 0xFE;
    return true
}
if(!bDetected && File.isDeepScan() && isDaveLoweNew()) {
    sName = 'Dave "Uncle Art" Lowe New module (.DLN)'; bDetected = true;
    sVersion = "f."+fmt; if(bad != "") sVersion += "/malformed"+bad;
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("smp:"+smp+" sz:"+outSz(sz));
    }
}

function isDesire() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Desire/src/Desire_v1.asm
    if(File.getSize() <= 2500) return false;
    a2 = 0; a0 = 8;
    for(d1=0; d1 < 4; d1++) { if(!File.compare("00010101",a0)) return false; a0 += 16 }
    a1 = 400+a0;
    do {
        if(a0 === a1) return false;
        t = File.read_int16(a0,_BE); a0 += 2;
    } while(t != 0x49FA);
    a0 += 2;
    if(!File.compare("45F900DF F000357C 00FF009E 41FA",a0)) return false; a0 += 14;
    a0 += File.read_int16(a0,_BE);
    if(a0 != a2) return false;
    a1 = 0xF0;
    do { t = File.read_uint16(a1,_BE); a1 += 2
    } while(t != 0x49FA && a1 < File.getSize()); a1 -=2; //FindPlay
    do { t = File.read_uint16(a1,_BE); a1 += 2 //FindSongs
    } while(t != 0x47FA && a1 < File.getSize());
    a2 = a1+20; a1 += File.read_int16(a1,_BE); x = 0; a1++;
    do { //CheckSongs
        d1 = File.read_uint8(a1); a1++; if(d1) x++; else break;
    } while(d1 != File.read_uint8(a1) && a1 < File.getSize());
    do { t = File.read_uint16(a2,_BE); a2 += 2 //Find1
    } while(t != 0xE341 && a2 < File.getSize());
    do { t = File.read_uint16(a2,_BE); a2 += 2 //Find2
    } while(t != 0x47FA && a2 < File.getSize());
    smptsz = a1 = a2+File.read_int16(a2,_BE);
    do { t = File.read_uint16(a2,_BE); a2 += 2 //Find3
    } while(t != 0x47FA && a2 < File.getSize());
    a3 = a2; a2 += File.read_int16(a2,_BE); smpp = a2;
    a0 = a2; d4 = a1-a2; a2 = a1+d4; smp = 0;
    do { t = File.read_int16(a1,_BE); a1 += 2; if(t) smp++; } while(a1 < a2); //NextS,NoSamp
    do { t = File.read_uint16(a3,_BE); a3 += 2 //Find4
    } while(t != 0x47FA && a3 < File.getSize());
    a3 += 2;
    d6 = File.read_uint16(a3,_BE) & 0x0E00;
    if(d6) d6 >>= 9; else d6 = 8;
    ruch = d6; //Skip8
    d1 = File.read_int16(a0,_BE); songsz = d2 = d1 << d6;
    t= d1;d1=d2;d2 =t; d5 = 0;
    do { //NextOff
        d0 = File.read_int16(a0,_BE); a0 += 2;
        if(d0 > d2) { d2 = d0; d5 = File.read_uint16(a0+d4-2,_BE) }
    } while(a0 < smptsz);
    d2 <<= d6; d5 <<= 1; d2 += d5;
    sz = d2; smpsz = sz-songsz
    return true
}
if(!bDetected && File.isDeepScan() && isDesire()) {
    sName = "Desire player module (.DSR)"; bDetected = true;
    sVersion = "v1.0";
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("smp:"+smp+" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz))
        if(sz < File.getSize()) sVersion += "/malformed!short"
    }
}

function isDavidWhittaker() {
    // from https://github.com/neumatho/NostalgicPlayer/blob/main/Source/Agents/Players/DavidWhittaker/DavidWhittakerWorker.cs
    // <space>sfx.dw not detected, -sfx.dw may be detected sometimes
    if(File.getSize() < 2048) return false;
    const sl = Math.min(File.getSize(), 0x4000); //search buffer length
    //and now for the NP routine ExtractInfoFromInitFunction
    sz = x = ch = smp = sqwfsmp = 0;
    for(i = 0; i < sl; i += 2) if(File.compare("47FA",i) && (File.read_uint8(i+2)&0xF0) == 0xF0) break;
    if(i >= sl-6) return false;
    sofs = i+2+File.read_int16(i+2,_BE);
    for(; i < sl; i += 2) if(File.compare("6100",i)) break; if(i >= sl-6) return false;
    sinit = i; ssmpsinit = i;
//_log("ssmpsinit:"+Hex(ssmpsinit))
    if(File.compare("6100",i+4)) ssmpsinit = i+6+File.read_int16(i+6,_BE);
    for(i = ssmpsinit; i < sl; i += 2) if(File.compare("4A2B",i)) break;
    if(i >= sl-36) return false;
    if(!File.compare("66",i+4)) {
        for(i = sinit; i < sl; i += 2) if(File.compare("41EB",i)) break;
        if(i >= sl-36) return false;
        smpdp = sofs+File.read_int16(i+2,_BE);
        i += 4; if(File.read_uint8(i+4) != 0x72) return false;
        smp = File.read_uint8(i+5)+1;
        for(; i < sl-4; i += 2) if(File.compare("41EB....E34F",i)) break;
        if(i >= sl-4) return false;
        chvolp = sofs+File.read_int16(i+2,_BE);
//_log("@"+Hex(i)+" smpdatap:"+Hex(smpdp))
        //extract subsong info:
        for(i = sinit; i < sl; i += 2) if(File.compare("41EB....17",i)) break;
        if(i >= sl-4) return false;
        sstp = sofs+File.read_int16(i+2,_BE);
        is32bp = true; old = true;
    }
    else {
        old = false;
        if(!File.read_uint8(i+5)) i += 2;
        if(!File.compare("41FA",i+6)) return false;
        smpdp = File.read_int16(i+8,_BE)+i+8;
        i += 10; if(File.compare("2748....D0FC",i)) {
            smpdp += File.read_uint16(i+6,_BE);
            i += 12; if(!File.compare("D0FC",i)) return false;
            smpdp += File.read_uint16(i+2,_BE); i += 4
        }
//_log("@"+Hex(i)+" smpdatap:"+Hex(smpdp))
        if(!File.compare("4BFA....72",i)) return false;
        smpip = File.read_int16(i+2,_BE)+i+2;
        smp = File.read_uint8(i+5)+1;
        for(i = sinit; i < sl-4; i += 2) if(File.compare("41FA",i) && !File.compare("4B",i+4)) break;
        if(i >= sl-4) return false;
        if(!File.compare("1230",i+4) && !File.compare("3770",i+4)) return false;
        sstp = i+2+File.read_int16(i+2,_BE);
        i += 4; for(; i < sl-8; i += 2) if(File.compare("41FA",i) && !File.compare("23",i+4)) break;
        if(i >= sl-8) return false;
        if(File.compare("2070",i+4)) is32bp = true;
        else if(File.compare("3070",i+4)) is32bp = false;
        else return false;
    }
//_log("sstp:"+Hex(sstp))
    //load sample info, get size:
    p = smpdp;
    for(i = 0; i < smp; i++) {
        ssz = File.read_uint32(p,_BE); p += 6;
//_log("smpd["+i+"] @"+Hex(p-6)+" sz:"+outSz(ssz));
        if(p+ssz > File.getSize()) { bad = bad.addIfNone("!short"); return true }
        p += ssz; sz = p;
    }
    for(i = 0; i < sl; i += 2) if(File.compare("47FA",i)) {
        if(i >= sl-10) return false;
        if (File.compare("4A2B....67",i+4)) {
            if(File.compare("33FC",i+10) || File.compare("177C",i+10) || File.compare("08B9",i+10))
                continue;
            break
        }
    }
    splay = i;
//_log("splay:"+Hex(i))
    ch = 0;
    for(i = splay; i < splay + 200; i += 2) if(File.read_uint8(i) == 0x7E) {
        ch = File.read_uint8(i+1);
        if(!ch) {
            for(; i < splay + 500; i += 2)
                if(File.compare("BE7C",i) || File.compare("BE3C",i)) { ch = File.read_uint8(i+3); break }
        } else ch++;
        break
    }
    if(!ch) return false;
//_log("ch:"+ch)
    //square waveforms?
    for(i = splay; i < splay+100; i += 2)
        if(File.compare("207A.... 303A",i)) {
            sqwfsmp = (File.read_int16(i+2,_BE)+i+2 - smpip)/12;
            if(!File.compare("31BC",i+14) && !File.compare("11BC",i+14)) return false;
            if((File.read_uint8(i+20) & 0xF0) != 0x50 || File.read_uint8(i+21) != 0x6B) return false
            if(!File.compare("0C6B",i+24)) return false;
            if(!File.compare("31BC",i+38) && !File.compare("11BC",i+38)) return false;
            if(!File.compare("0C6B",i+48)) return false;
        }
    //find diff.player parts, just the sanity checks
    if(old) {
        for(i = splay; i < sl; i += 2) if(File.compare("7000",i)) break;
        if(File.read_uint8(i+2) != 0x10) return false;
        var rdtrkcmdp = i
    } else {
        for(i = splay; i < sl; i += 2) if(File.compare("5368",i)) break;
        if(i >= sl-16) return false; if(File.read_uint8(i+4) != 0x67) return false;
        var rdtrkcmdp = File.read_uint8(i+5)+i+6;
        if(File.read_uint8(i+12) != 0x66) return false;
        for(i = rdtrkcmdp; i < sl; i += 2) if(File.compare("45FA.... 322D",i)) break;
        if( i >= sl-6) return false;
        i = File.read_uint16(i+2,_BE)+i+2;
        if(i >= sl-72*2) return false;
        if(!File.compare("1000",i) && !File.compare("2000",i)) return false
    }
    //checks for transposes:
    for(i = rdtrkcmdp; i < sl; i += 2) if(File.compare("6B00",i)) break; if(i >= sl-6) return false;
    var endlyc = endlym = false; //:667
    for(i = splay; i < splay + 100; i += 2) if(File.compare("103A",i)) {
        endlyc = true; if(File.compare("C0FC",i+6)) endlym = true; break }
//_log("@"+Hex(i)+" dlyc?"+endlyc+" dlym?"+endlym)
    x = 0; p = sstp; minpp = 0xFFFFFFFF; //:966
    sngspd = dlyspd = 0; bad = "";
    while(p+8 < minpp) {
        if(endlyc) { sngspd = File.read_uint8(p++); dlyspd = File.read_uint8(p++) }
        else { sngspd = File.read_uint16(p,_BE); p += 2; dlyspd = 0 }
        if(sngspd > 0xFF) break;
        for(i = 0; i < ch; i++) {
            if(is32bp) { t = File.read_uint32(p,_BE); p += 4 }
            else { t = File.read_uint16(p,_BE); p += 2 }
            if(minpp > sofs+t) minpp = sofs+t;
//_log(x+" ordp:"+Hex(t))
        }
        if(p > File.getSize()) { bad = bad.addIfNone("!short"); break }
        x++
    }
    return true
}
if(!bDetected && File.isDeepScan() && isDavidWhittaker()) {
    sName = "David Whittaker's module (.DW)";
    if(old) sVersion = "old"; else sVersion = "new"; bDetected = true;
    if(bad != "") sVersion += "/malformed"+bad;
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("ch:"+ch+" smp:"+smp+(sqwfsmp ? " sqwf.smp:"+sqwfsmp : "")
            +" spd:"+sngspd+" sz:"+outSz(sz))
    }
}

function isFashionTracker() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/FashionTracker-v1.0/FashionTracker.asm
    if(!File.compare("13FC0040 ........ 4E710439 0001")
      || !File.compare("66F44E75 48E7FFFE",18)) return false;
    a2 = 0; bad = "";
    while(!File.compare("2379",a2) && a2 <= 1000) a2 += 2; //FindOri
    if(a2 > 1000) //corrupt module
        if(File.isHeuristicScan()) bad = "!badorigin"; else return false;
    org = File.read_uint32(a2-4,_BE);
    a2 = 0; d1 = 1;
    do { //GetValues:
        // a3 = smplen    a3+4 = smpp      a3+8 = smplp
        // a3+12 = smpn+vol      a3+16 = ptn     a3+20 = songpositions
        if(File.compare("23D1",a2)) { // L???
            smplen = File.read_int32(a2+8,_BE)-org;
            smpp = File.read_int32(a2-6,_BE)-org;
            smplp = File.read_int32(a2+24,_BE)-org;
            smpnvol = File.read_int32(a2-30,_BE)-org;
            d1 -= 6;
        }
        else if(File.compare("C0FC0400",a2)) { //NoL
            ptn = File.read_int32(a2+6,_BE)-org;
            d1 += 3;
        }
        else if(File.compare("0C790400",a2)) { //NoPa
            songpos = File.read_int32(a2+12,_BE)-org;
            len = File.read_int32(a2+34,_BE);
            d1 += 2;
        }
        a2 += 2 //NextW
    } while(a2 < 1000);
    if(d1)
        if(File.isHeuristicScan()) bad = bad.addIfNone("!badval"); else return false;
    a1 = smpp; songsz = File.read_int32(smpp,_BE)-org;
    d0 = File.read_int32(a1-4,_BE) << 1;
    a2 = smplen; d1 = a1-a2; a1 += d1; d1 >>= 2; smp = d1;
    d1 = File.read_int32(a1-4,_BE)-org;
    sz = d0+d1; smpsz = sz-songsz;
    a1 = songpos; a2 = a1+len; ptn = 0;
    do { t = File.read_uint8(a1); if(ptn < t) ptn = t; a1++ } while(a1 < a2); //GetPat
    ptn++;
    d1 = (d0-1)*6; d0 = d1; d1 *= 0x376B;
    dur = Math.floor(d1*64/(709379-3));
    return true
}
if(!bDetected && File.isDeepScan() && isFashionTracker()) {
    sName = "Fashion Tracker module (.EX)"; bDetected = true;
    sVersion = "v1.0"; if(bad) sVersion += "/malformed."+bad;
    if(File.isVerbose()) {
        sOption("ord:"+len+" ptn:"+ptn+" smp:"+smp+" songsz:"+songsz+" sz:"+outSz(sz))
    }
}

function isImagesMusicSystem() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/ImagesMusicSystem/src/Images Music System_v3.asm
    if(File.getSize() < 0x73C) return false;
    t = File.read_uint32(0x438,_BE); if(t >> 16) return false;
    t -= 0x43C; if(t <= 0) return false;
    d1h = t % 0x300; if(d1h) return false;
    ptn = d2 = Math.floor(t / 0x300);
    ord = d1 = File.read_uint8(0x3B6); if(d1&80) return false;
    a1 = 0x3B8; d3 = 0;
    do { d4 = File.read_uint8(a1); if(d4&0x80) return false; if (d3 < d4) d3 = d4; a1++;
    d1--} while(d1 >= 0);
    d2--;
    if(d2 != d3) return false;

    a1 = 42; d0 = 31; smp = d3 = 0;
    do { d1 = File.read_int16(a1,_BE); if(d1) { smp++; d3 += d1 } a1 += 30
    d0--} while(d0);
    smpsz = d3 << 1; songsz = File.read_int32(1080,_BE);
    sz = songsz+smpsz; bad = (sz < File.getSize());
    d2 = ord; x = a0 = d0 = 0; a1 = 0x3B8;
    if(songsz === 28732 && smpsz === 12898 && File.compare("beast-busters1.st") )
        x = 11;
    // There seem to be no files to test the subsong detection on! The algo should be OK tho
    else do { //NextPos
        d1 = File.read_uint8(a1)*0x300; a0 = d1+0x43C; a3 = a0+0x300; a0++; a1++;
        do { //NextPatPos
            if(File.read_uint8(a0)&0x0F === 0x0B) { x++; break } //SubFound
            else a0 += 3
        } while(a0 < a3 && a0 < File.getSize()); d2--
    } while(d2);
    if(!x) x = 1;
    return true
}
if(!bDetected && File.isDeepScan() && isImagesMusicSystem()) {
    sName = "Images Music System module (.IMS)"; bDetected = true;
    sVersion = "v1.0"; if(bad) sVersion += "/malformed!short";
    if(File.isVerbose()) {
        sOption(File.read_ansiString(0,20)); if(x > 1) sOption(x,"×");
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz))
    }
}

function isSMUS() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/SonixMusicDriver/Sonix Music Driver_v1.asm
    a0 = a1 = 0; title = "";
    if(File.compare("'FORM'")) {
        if(!File.compare("'SMUSSHDR'",8) || !File.read_uint8(0x17) || !File.compare("'NAME'",0x18))
            return false;
        d1 = File.read_uint32(0x1C,_BE); if(d1 >> 31) return false; t_ = d1;
        d1 = (d1+1) & 0xFFFFFFFE; a1 = 0x20+d1;
        if(!File.compare("'SNX1'",a1)) return false; a1 += 4;
        d1 = File.read_uint32(a1,_BE); if(d1 >> 31) return false; a1 += 4;
        d1 = (d1+1) & 0xFFFFFFFE; a1 += d1;
        insinfp = a1;
        realsmp = []; ins = 0;
        do { //MoreIns
            if(!File.compare("'INS1'",a1)) return false; a1 += 4;
            d1 = File.read_uint32(a1,_BE); if(d1 >> 31) return false; a1 += 4;
            d1 = (d1+1) & 0xFFFFFFFE; if((d1 >> 24) > 0x3F) return false;
            if(File.read_uint8(a1+1)) return false;
            realsmp.push(File.read_uint8(a1));
            a1 += d1; ins++
        } while(!File.compare("'TRAK'",a1));
        title = File.read_ansiString(0x20,t_)
        fmt = 2; sV = "smus"
    }
    else {
        if(!(File.read_uint16(0,_BE) & 0xF0)) {
            d3 = 20; d1 = 4;
            while(d1) {
                d2 = File.read_int32(a0,_BE); if(d2 <= 0 || (d2&1)) return false;
                a0 += 4; d3 += d2; d1--
            }
            if(d3 >= File.getSize()) return false;
            a0 += 4; d1 = 4;
            while(d1) {
                t = File.read_uint16(a0,_BE); if(!(t & 8000)) return false;
                if(t != 0xFFFF) if((t >>16) > 0x84) return false;
                a0 += File.read_int32(a1,_BE); a1 += 4; d1--
            }
            if(!File.read_uint8(a0)) return false;
            fmt = 0; sV = "snx"
        }
        else { //TinyCheck
            if(File.getSize() < 333) return false;
            a1 = 0x30; if(File.read_int32(a1,_BE) != 0x140) return false;
            a1 += 4; d1 = 3;
            while(d1) { //NextPos2
                d2 = File.read_int32(a1,_BE); a1 += 4;
                if(d2 <= 0 || (d2&1) || d2 > File.getSize()) return false;
                if(File.read_int16(d2,_BE) != -1) {
                    if(File.read_int32(d2,_BE) || File.read_int16(d2+4,_BE)) return false;
                    t = File.read_uint8(d2+6,_BE); if(!(t&0x80) || t > 0x82) return false
                }
                d1--
            }
            fmt = 1; sV = "tiny"
        }
    }
    //TODO CalcSize
    return true
}
if(!bDetected && File.isDeepScan() && isSMUS()) {
    sName = " Sonix Music Driver module (.SMUS)"; sVersion = "f."+sV; bDetected = true;
    if(File.isVerbose()) {
        if(title != "") sOption(title)
    }
}

function isAMBK() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/ambk/AMOS.s
    if(File.compare("'AmBk'")) a0 = p = 4; else if(File.compare("C0")) a0 = p = 0; else return false;
    //if(!File.compare("80",p+4)) return false;
    _log(Hex(File.read_uint32(p+4,_BE)&((1<<28)-1)));
    if(!File.compare("'Music   '",p+8)) return false;
    p += 0x14; p += File.read_int32(p,_BE);
    if(File.read_int16(p,_BE) != 6) return false; p += 2; r = p;
    p += File.read_int16(p+2,_BE)-2;
    p = File.read_int16(p,_BE); if(p != -2) if(p) return false;

    title = File.read_ansiString(r+0x0C,0x20);
    p = r+0x1E+File.read_int16(r+0x1E,_BE); ord = -1;
    do { ord++; d1 = File.read_int16(p,_BE); p += 2; } while(p < File.getSize() && d1 >= 0);
    if(a0) d3 = 4+(File.read_int32(a0+4,_BE) & 0xFFFFFF);
    else d3 = File.read_int32(a0,_BE) & 0xFFFFFF; sz = d3+8;
    bad = ""; if(sz > File.getSize()) bad = bad.addIfNone("!short");
    p = r+File.read_int16(r+6,_BE);
    ptn = 0;
    while(p < File.getSize()) { //.such
        d1 = File.read_int16(p,_BE); p += 2; if(d1 < 0) break;
        if(d1 > ptn) ptn = d1
    }
    ptn++;
    if(!File.read_int16(p,_BE)) p += 2;
    if(File.read_int16(p,_BE) != ptn) bad = bad.addIfNone("!badptn");
    q = a0+0x20; smp = d1 = File.read_int16(q,_BE); q += 0x10;
    d7 = 0;
    while(d1 && q < File.getSize()) {
        d2 = File.read_int16(q,_BE);
        if(!d2) d2 = File.read_int16(q-6,_BE);
        d7 += d2 << 1; q += 0x20; d1--
    }
    smpsz = d7;
    songsz = sz-d7;
    return true
}
if(!bDetected && File.isDeepScan() && isAMBK()) {
    sName = " AMOS Music Bank module (.ABK)"; bDetected = true;
    if(bad != "") sVersion = "malformed"+bad;
    if(File.isVerbose()) {
        sOption(title);
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+/*" smpsz:"+Hex(smpsz)+" songsz:"+Hex(songsz)+*/" sz:"+outSz(sz))
    }
}

function isTuneFish4() {
    // taken from tunefish src\tunefish4player\tf4player.cpp
    ins = File.read_int16(0,_LE); if(ins > 32) return false;
    if(!File.compare("'INST'",4+ins*2)) return false; t = 8+ins*114;
    if(!File.compare("'SONG'",t)) return false;
    t += 4; songsz = 0; for(i=0; i<ins; i++) songsz += File.read_uint16(4+i*2,_LE);
    if(!File.compare("'ENDS'",t+songsz*4)) return false;
    sz = t+songsz*4+4;
    return true;
}
if(!bDetected && File.isDeepScan() && isTuneFish4()) {
    sName = "TuneFish module (.TF4)"; sVersion  = "v4"; bDetected = true;
    if(File.isVerbose())
        sOption("ins:"+ins+" tempo:"+File.read_uint16(2,_LE)+" songsz:"+Hex(songsz)+" sz:"+outSz(sz))
}

function isJH() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Jochen_Hippel_ST/Jochen Hippel_v1.asm
    a0 = 0; tfmx = 0;
    if(File.compare("48E7FFFE")) {
        fmt = -1; a0 += 4; d1 = 0;
        if(!File.compare("61",a0)) return false;
        d1 = File.read_uint8(a0+1); if(!d1 || (d1&1)) return false; a0 += d1+2;
        if(!File.compare("2F006100",a0)) return false; a0 += 4+File.read_int16(a0+4,_BE);
        if(!File.compare("41FA",a0)) return false;
        if(!File.compare("41FA",a0+0x12)) return false;
        a0 += 0x14; a0 += File.read_int16(a0,_BE);
        if(!File.compare("'MCMD'",a0)) return false;
        tfmx = a0
    } else { // NoMCMD
        fmt = 0;
        if(!File.compare("60",a0++)) return false;
        d1 = File.read_uint8(a0++);
        if(!d1) {
            fmt = 1; d1 = File.read_int16(a0,_BE); if(d1 < 0 || (d1&1)) return false;
            if(!File.compare("6000",a0+2)) return false;
            a0 += d1;
            if(!File.compare("48E7FFFE",a0)) return false; a0 += 4;
        } else { // No3
            if(d1&1) return false; a0 += d1;
            if(!File.compare("48E7FFFE",a0)) return false; a0 += 4;
            if(!File.compare("6100",a0)) return false; a0 += 2; //astaroth.hip, prince.hip fail this one and have more jumps, iono
            a0 += File.read_int16(a0,_BE);
            if(!File.compare("2F006100",a0)) return false; a0 += 4;
            a0 += File.read_int16(a0,_BE);
            if(!File.compare("41FA",a0)) return false; a0 += 0x14;
        } // Later:
        t = File.compare("41FA",a0); a0 += 2;
        if(!t) { t = File.compare("41FA",a0); a0 += 2; if(!t) return false }
        a0 += File.read_int16(a0,_BE);
        tfmx = a0;
        if(!File.compare("'TFMX'00",a0)) return false; a0 += 4;
        if(!File.read_int16(a0+0x0C,_BE)) { fmt = "sfx"; sz = 0; x = 1; return true }
        d1 = (2+File.read_int16(a0,_BE)+File.read_int16(a0+2,_BE)) << 6;
        d2 = (1+File.read_int16(a0+4,_BE))*File.read_int16(a0+8,_BE);
        d3 = (1+File.read_int16(a0+6,_BE))*12;
        d1 += d2+d3+(1+File.read_int16(a0+0x0C,_BE))*6+0x20;
        a0 += d1+0x0E;
        if(File.read_int32(a0,_BE)) return false; a0 += 4;
        d2 = File.read_int16(a0,_BE); if(!d2) return false;
        d2 *= 2; if(d2 != File.read_int32(a0+0x1A,_BE)) return false;
    }
    sz = a0 = 0; x = 1; a1 = a0+tfmx;
    if(fmt < 0) { d0 = 0; d1 = 0x12 } else { d0 = 2; d1 = 0x20 }
    d0 += File.read_int16(a1+4,_BE)+File.read_int16(a1+6,_BE); d0 <<= 6;
    d0 += d1; if(fmt < 0) d1 = 0; else d1 = 1;  d1 += File.read_int16(a1+8,_BE); ptn = d1;
    d1 *= File.read_int16(a1+0x0C,_BE); d0 += d1;  if(fmt < 0) d1 = 0; else d1 = 1;
    d1 += File.read_int16(a1+0x0A,_BE); d1 *= 12; d0 += d1;  a2 = subp = a1+d0; d1 = 1;
    if(fmt < 0) { d2 = File.read_int16(a1+0x0E,_BE); d1 = d2<<3 }
    else { d2 = File.read_int16(a1+0x10,_BE); d1 = (d1+d2)*6 }
    x = d2; d0 += d1; a2 = smpinfop = a1+d0;
    if(fmt < 0) { d1 = File.read_int16(a1+0x10,_BE); d2 = 0x1C }
    else { d1 = File.read_int16(a1+0x12,_BE); d2 = 0x1E }
    smp = d1; d1 *= d2; d0 += d1;
    smpp = a2 = a1+d0; a1 = smpp; if(fmt < 0) a1 += 2;
    smpsz = File.read_int16(a1-8,_BE)*2+File.read_int32(a1-0x0C,_BE);
    songsz = a2-a0; sz = songsz+smpsz;
    return true
}
if(!bDetected && File.isDeepScan() && isJH()) {
    if(fmt < 0) ext = ".MCMD"; else ext = ".SOG";
    sName = "Jochen Hippel's module ("+ext+",.HIP)"; sVersion = "f."+fmt; bDetected = true;
    if(File.getSize() < sz) sVersion += "!short";
    if(File.isVerbose()) {
        if(x>1) sOption(x,"×");
        sOption("ptn:"+ptn+" smp:"+smp+/*" songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+*/" sz:"+outSz(sz))
    }
}

function isJH7V() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/players/wanted_team/Jochen_Hippel_7V/Jochen Hippel 7V_v2.asm
    p = 0;
    if(File.compare("6000")) { p = 2;
        d1 = File.read_int16(p,_BE); if(d1 <= 0 || (d1&1)) return false; p = d1+2;
        p = 4+File.findSignature(p,20,"308141FA"); if(p < 4) return false;
        d1 = File.read_int16(p,_BE); if(d1 <= 0 || (d1&1)) return false;
        p += d1;
    }
    if(!File.compare("'TFMX'00",p)) return false;
    d0 = d1 = ( 2+File.read_int16(p+4,_BE) + File.read_int16(p+6,_BE) ) << 6;
    d0 += 0x20;
    ptn = d2 = File.read_int16(p+8,_BE)+1;
    d3 = (File.read_int16(p+0x0A,_BE)+1)*0x1C;
    d2 *= File.read_int16(p+0x0C,_BE);
    d0 += d2;
    d1 += d2 + d3;
    if(File.read_uint8(p+4+d0)) txt = File.read_ansiString(p+4+d0,24);
    else txt = "";
    x = File.read_int16(p+0x10,_BE);
    d2 = (x+1) << 3;
    ins = File.read_int16(p+0x12,_BE);
    insip = p+d1+d2+0x20;
    if(File.read_int32(insip+0x12,_BE)) return false; //1st smp ofs
    d2 = 2*File.read_int16(insip+0x16,_BE);
    if(!d2) return false;
    if(d2 != File.read_int32(insip+0x16+0x1E-4,_BE)) return false;
    var mofs = 0; var msz = 0; //find max ofs instead of one just from last smp, just in case
    for(i=0;i < ins;i++) {
        var sofs = File.read_int32(insip+i*0x1E+0x12,_BE);
        var ssz = File.read_uint16(insip+i*0x1E+0x16,_BE);
        if(sofs >= mofs) { mofs = sofs; msz = ssz } }
    smpp = insip+ins*0x1E;
    sz = smpp+mofs+msz*2;
    //_log("insip:"+Hex(insip)+" smpp:"+Hex(smpp)+" sz:"+outSz(sz));
    return true;
}
if(!bDetected && File.isDeepScan() && isJH7V()) {
    sName = "Jochen Hippel's module (.HIP7,.S7G)"; sVersion = "7V"; bDetected = true;
    if(!File.compare("'TFMX'")) sVersion += "+replayer";
    if(File.getSize() < sz) sVersion += "!short";
    if(File.isVerbose()) {
        if(txt != "") sOptionT(txt);
        if(x>1) sOption(x,"×");
        sOption("ptn:"+ptn+" ins:"+ins+" sz:"+outSz(sz))
    }
}

function isJHCoSo() {
    //from uade Jochen_Hippel_COSO player, check routine @ e476t
    //initplayer disassembly: d e5c0 500
    if(!File.compare("'COSO'")) return false;
    if(!File.compare("'TFMX'",0x20)) return false;
    if(!File.read_int16(0x30,_BE) || !File.read_int16(0x40,_BE)) return false;
    var songsz = File.read_int32(0x1C,_BE); if(songsz > File.getSize()) return false;
    //@e4ac:
    a2 = File.read_int32(4,_BE); d6 = d7 = 0;
    d0 = File.read_int16(0x24,_BE); //ord?
    do { if(a1 > File.getSize()) return false;
        a1 = File.read_int16(a2,_BE); a2 += 2;
        if(File.read_uint8(a1) === 0xE2)
            if(File.read_uint8(a1+1)&0x80) d7++; else d6++;
    d0--} while(d0 >= 0);
    if(d7 >= d6) return false;
    ptn = File.read_int16(0x28,_BE)+1; smp = File.read_int16(0x32,_BE);
    a0 = File.read_int32(0x18,_BE)+smp*10; //_log("a0:"+Hex(a0));
    smpsz = File.read_int32(a0-10,_BE) + File.read_int16(a0-6,_BE)*2;
    sz = songsz+smpsz;
    x = File.read_int16(0x30,_BE);
    return true
}
if(!bDetected && File.isDeepScan() && isJHCoSo()) {
    sName = "Jochen Hippel's module (.HIPC)"; sVersion = "COSO"; bDetected = true;
    if(File.isVerbose()) {
        if(x > 1) sOption(x,"×");
        sOption("ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
    }
}

function isJHST() {
    // Not supported: MMME-MMME latest, which uses $EE (SID) command

    //from http://wt.exotica.org.uk/files/sources/SRC_JochenHippelST.lzx
    //Test routine @e3fc, init @e57e
    // in init, A4 is the module pointer and module info is @e438!
    a0 = a1 = a4 = d4 = 0; d1 = 0x80; d0 = File.getSize(); bad = "";
    //func @e84e, returns d0 = 2 if this:
    while(d1) {
        if(a0 > File.getSize() || a1 > File.getSize()) return false;
        if(d1 != 0x80) {
            t = File.read_int16(a1,_BE); a1 += 2;
            if(t === 0x41FA) {
                d2 = File.read_int16(a1,_BE); if(d2 >= 0 && !(d2&1)) a0 = a1+d2 }
        }
        if(File.compare("'MMME'",a0)) {
            if(File.compare("'MMME'",a0+0x20)) d0 = 0;
            else d0 = 2;
            break; }
        else if(File.compare("'TFMX'",a0)) {
            if(File.read_int16(a0+4,_BE) >= 0x200) d0 = 0;
            else if(!File.read_int16(a0+0x10,_BE)) d0 = 0;
            else { // function @e8b0
                //func.@e8b0~e8c8
                d0 = File.read_int16(a0+4,_BE); a1 = a0+0x20; d6 = d7 = 0;
                do { if(File.read_uint8(a1) === 0xE2) // in func.e928
                        if(File.read_uint8(a1+1)&0x80) d6++; else d7++;
                    a1 += 0x40;
                d0--} while(d0 >= 0);
                if(d6 > d7) d0 = 2; else d0 = 5; //@e8a8
            }
            break;
        }
        else if(File.compare("'COSO'",a0)) {
            if(!File.read_int16(a0+0x30,_BE)) return false; //FX check
            if(!File.read_int32(a0+0x18)) return false;
            if(File.compare("'TFMX'",a0+0x20)) { //@e984
                a2 = a0+File.read_int32(a0+4,_BE); d6 = d7 = 0;
                d0 = File.read_int16(a0+0x24,_BE);
                do {
                    if(File.read_int16(a0+0x40,_BE)) { a1 = a0+File.read_int16(a2,_BE); a2 += 2 }
                    else { a1 = a0+File.read_int32(a2,_BE); a2 += 4 }
                    if(a1 > File.getSize()) return false;
                    if(File.read_uint8(a1) === 0xE2) {
                        if(File.read_uint8(a1+1)&0x80) d7++; else d6++;
                    }
                d0--} while(d0 >= 0);
                if(d7 < d6) d0 = 4; else d0 = 2
            }
            else if(File.compare("'MMME'",a0+0x20)) d0 = 2; else d0 = 0;
            break;
        }
        d0 -= 2;
        d1--
    }
    if(d0 != 2) return false;
    if(File.compare("'LSMP'",a0+0x1C)) d1 = 1; else d1 = 0;
//    if(d1) _log("pc+@e16c (lsmp): "+d1); //it stores 'em'
    a0 -= a4;
//    if(a0) _log("pc+@e170 (mod ofs?): "+a0); //stores this too

    //size calc
    Special = songsz = smpsz = 0; sz = -1; d0 = File.getSize();
    packed = File.compare("'COSO'") || File.compare("'MMME'",0x20);
    //in-player: d0 = fsize; d2 = ffff; d3 = 376b; d4 = d5 = d6 = 0; d7 = 6;
    //  a0 = a2 = 11000; a1 = e17a; a3 = 11161; a4 = e438; a5 = 4ed2; a6 = e164; a7 = 84f8
    if(!packed) { sV = "unpacked";
        d6 = 0; d1 = 2; a2 = d4 = 0;
        d1 = (2+File.read_int16(4,_BE)+File.read_int16(6,_BE)) << 6;
        d2 = (1+File.read_int16(8,_BE)) * File.read_int16(0xC,_BE);
        d3 = (1+File.read_int16(0xA,_BE))*12;
        d1 += d2+d3; d2 = 2+File.read_int16(0x10,_BE);
        d1 += (d2+File.read_int16(0x12,_BE))*6+0x20;
        d2 = 0x20; d3 = d7 = 0;
        d0 -= d1; if(d0 < 0)
            if(a1+d0 > 0 && File.isHeuristicScan()) { bad = "!short"+d0; return true }
            else return false
        if(d0 > 0) {
            a1 = d1+a2; t = File.read_int16(a1,_BE);
            if(t === 0x80 || t === 0x100) d7 = a1;
            else  if(t) Special = a1;
            do { d3 = File.read_int16(a1,_BE); a1 += 8;
            } while(File.read_int16(a1,_BE) && a1 < File.getSize());
            d0 -= d3; if(d0 < 0)
                if(a1+d0 > 0 && File.isHeuristicScan()) { bad = "!short"+d0; return true }
                else return false
        }
        d0 = d1; smpsz = d3;
        // d1 += d4; //d4 is Size(PC), apparently unused
        songsz = d1; sz = songsz+smpsz;
    } else {
        sV = "packed";
        a1 = a0; d3 = 0;
        d3 = (1+File.read_int16(a0+0x32,_BE))*6; d2 = d3+File.read_int32(a0+0x18,_BE);
        d3 = 0; d0 -= d2;
//_log(Hex(File.getSize())+" - "+Hex(d2)+" = "+Hex(File.getSize()-d2))
        if(d0 < 0)
            if(a1+d0 > 0 && File.isHeuristicScan()) { bad = "!short"+d0; return true }
            else return false;
        if(d0 > 0) {
            a1 += d2; t = File.read_int16(a1,_BE);
            if(t && t != 0x80 && t != 0x100) Special = a1;
            do { d3 = File.read_int16(a1,_BE); a1 += 8;
            } while(File.read_int16(a1,_BE) && a1 < File.getSize());
            d0 -= d3; if(d0 < 0)
                if(a1+d0 > 0 && File.isHeuristicScan()) { bad = "!short"+d0; return true }
                else return false
        }
//_log("d2:"+Hex(d2)+"/"+d2+" d3:"+Hex(d3)+"/"+d3+" Special:"+Hex(a1))
        /*d2 += d4;*/ songsz = d2; smpsz = d3; sz = d3+d2
    }
    return true;
}
if(!bDetected && File.isDeepScan() && isJHST()) {
    sName = "Jochen Hippel's Atari ST module (.HST,.SOC,.SOG)"; bDetected = 1
    sVersion = sV; if(bad != "") sVersion+="/malformed"+bad;
    if(File.isVerbose()) {
        if(smpsz) sOption("songsz:"+Hex(songsz)+" smpsz:"+Hex(smpsz)+" sz:"+outSz(sz))
        else sOption(outSz(sz),"sz:")
    }
}

function isEMS() {
    //ref UADE's EMS player disassembly: run with -d then "f d3c2"
    if(File.getSize() <= 0x53A) return false; // fault is @d432
    if(File.read_uint8(0) != 1) return false;
    if(File.read_uint8(1) > 0x25) return false;
    t = File.read_int16(2,_BE); if(!t || t > 0x80) return false;
    d1 = File.read_uint16(4,_BE); if(d1 > 0x3F) return false; d = d1 & 0xFF;
    p = 6;
    while(p < 0x86) { t = File.read_uint8(p); p++; if(d < t) return false; }
    c = 0;
    for(p = 0x13A; p < 0x53A; p += 4) { //@d40c
        t = File.read_int16(p,_BE); if(t < 0) return false;
        if(t)
            if([
              0x6B0, 0x650, 0x5F4, 0x5A0, 0x54C, 0x500, 0x4B8, 0x474,
              0x434, 0x3F8, 0x3C0, 0x38A, 0x358, 0x328, 0x2FA, 0x2D0,
              0x2A6, 0x280, 0x25C, 0x23A, 0x21A, 0x1FC, 0x1E0, 0x1C5,
              0x1AC, 0x194, 0x17D, 0x168, 0x153, 0x140, 0x12E, 0x11D,
              0x10D, 0xFE, 0xF0, 0xE2, 0xD6, 0xCA, 0xBE, 0xB4,
              0xAA, 0xA0, 0x97, 0x8F, 0x97, 0x7F, 0x78, 0x71
              ].indexOf(t) < 0)
                return false;
            else c++;
    }
    if(!c) return false;
    return true
}
if(!bDetected && File.isDeepScan() && isEMS()) {
    sName = "Editeur Musical Sequentiel module (.EMS)"; bDetected = true;
}

function isDZ() {
    //from https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/amigasrc/playes/dz/DariusZendeh_mod.s
    if(File.getSize() < 0x500 || !File.compare("48E7..F0 41FA.... 4CD80600")) return false;
    if(File.compare("78",2) && File.compare("0C0000FF",0xC)) sversion = "strange";
    else
        if(!File.compare("00",2) || !File.findSignature(0xC,0x240,"700033FC 000F00DF F09641FA"))
            return false;
    soptions = "";
    if(File.compare("4A44",0xC)) {
        sversion = "type 1";
        if(File.isVerbose()) {
            p = File.findSignature(0,0x80,"0C04.... 66..41FA");
            if(p >= 0) soption = "×"+(File.read_uint16(p+2,_BE)+3)
        }
    }
    else if(File.compare("4A00",0xC)) sversion = "type 2";
    else if(File.compare("0C00",0xC)) sversion = "type 3";
    return true
}
if(!bDetected && isDZ()) {
    sName = 'Darius "Mark II" Zendeh module (.DZ)'; sVersion = sversion; sOptions = soptions; bDetected = 1
}

function isMkII() {
    //ref reversing of the UADE player
    // & http://old.exotica.org.uk/tunes/formats/mk2/mk2_v1.zip
    if(!File.compare("41FA",4) || File.getSize() < 0x500) return false;
    //if(File.findSignature(0x200,0x200,"'.ZADS89.'")>=0) ← Some zero-fill it--not used by the replayer
    p = 8; i = 0x120;
    do { t = File.compare("E742",p); p += 2; if(t) break; i-- } while(i); if(!i) return false;
    do { t = File.compare("41FA",p); p += 2; if(t) break; i-- } while(i); if(!i) return false;
//_log("mkII 41FA search: i="+i+" @"+p)
    a3 = p; p += File.read_int16(p,_BE); //p goes to sampleinfo
    d5 = File.read_int16(a3+2,_BE); //d5 is maybe (or not) "move.w (a0+d2+?), (a3+?)"
    i = 0x18; do { t = File.compare("D1FA",a3); a3 += 2; if(t) break; i-- } while(i);
//_log("mkII D1FA search: i="+i+" @"+a3)
    if(!i) {
        if(d5 == 0x3770) return false; d5 = 0; a5 = a3-0x1C; i = 0x10;
    } else { a5 = a3; a3 += File.read_int16(a3,_BE) }
//_log("mkII: 3770 and a5 + a3skip")
    do { t = File.compare("41FA",a5); a5 -= 2; if(t) break; i-- } while(i);
//_log("mkII: 5, i="+i+" @"+Hex(a5))
    return i
}
if(isMkII()) {
    //DZ and MkII may detect some modules together, meaning a player of either format will play them
    _setResult("audio","Mark II Sound System module (.MK2)","","","")
    //sName = "Mark II Sound System module (.MK2)"; sVersion = ""; bDetected = true;
}


// BELOW THIS POINT ARE THE DETECTIONS THAT ARE IFFIER THAN THE REST

// like too loose and hits other known formats, or like potentially taking up unknown cycle#

// They all are in dire need of amending, and they're set to go after the other detections,
// so you won't miss the good matches because of these


function isHSC() {
    //ref https://sourceforge.net/p/xmp/libxmp/ci/master/tree/src/loaders/hsc_load.c
    if(File.getSize() < 1587+1152 || File.getSize() > 59187+1) return false;
    p = 128*12; ptn = 0; ord = -1;
    for(i=0; i < 0x33; i++) {
        bt = File.read_uint8(p+i);
        if(bt == 0xFF) break;
        if(ptn < bt) ptn = bt; ord++ }
    if(!i || !ptn || i > 0x32 || ptn > 0x30) return false; //test number of patterns
    //_log("1: i="+i+" ptn="+ptn);
    p += 0x33;
    for(i=0; i < ptn; i++) {
        for(j=0; j < 0x40; j++) {
            for(k=0; k < 9; k++) {
                n = File.read_uint8(p+j*18+k*2);
                m = File.read_uint8(p+j*18+k*2+1);
                if(m > 6 && m < 0x10 && n != 0x80) return false; //test effects 07..0F
                msn = (m&0xF0)>>4;
                if(6 < msn && msn < 10) return false; //test effects 7x..9x
                //_log("  3: i="+i+" j="+j+" k="+k+" n["+Hex(p+j*9+k*2)+"]="+Hex(n)+" m="+Hex(m)+" msn="+msn);
            }
        }
        p += 64*9*2
    }
    return true
}
if(!bDetected && File.isHeuristicScan() && isHSC()) {
    sName = "HSC Adlib Composer/HSC-Tracker module (.HSC)"; bDetected = true;
    if(File.isVerbose()) sOption = "ord:"+ord+" ptn:"+ptn;
}

function isJamCracker() {
    //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/JamCracker.c
    if(!File.isHeuristicScan() && !File.compare("'BeEp'")) return false;
    smp = File.read_uint8(5);
    if(!smp || (smp > 0x1F)) return false;
    if(File.read_uint8(6+smp*40) > 0) return false;
    allsmpsz = 0;
    for(k=0; k < smp; k++) {
        l = File.read_uint32(38+k*40,_BE);
        if(!l || (l > File.getSize())) return false;
        allsmpsz += l;
    }
    ord = File.read_uint16(6+smp*40,_BE);
    if(ord > 0xFF) return false;
    ptns = 6+smp*40+2;
    ptn = File.read_uint16(ptns+ord*6,_BE);
    if(ptn > 0xFF) return false;

    ptn0 = File.read_uint32(ptns+2,_BE);
    ptns += ord*6;
    trkdtsz = File.read_uint32(ptns-4,_BE)-ptn0;
    addlns = File.read_uint8(ptns-5)*4*8;
    sz = ptns + 2 + ptn*2 + allsmpsz + trkdtsz + addlns; //doesn't include the Xag tags
    return true
}
if(!bDetected && isJamCracker()) {
    sName = "JamCracker/Pro module (.JAM,.JC)"; bDetected = true; bad = "";
    if(File.isHeuristicScan()) {
        if(sz+19 == File.getSize()) { //obviously not for rippers
            sVersion = "v1.0a (Xag)";
            if(File.isVerbose()) sOptionT(File.read_ansiString(sz,19)); //this version adds these
            sz += 19;
        }
        if(!File.compare("'BeEp'")) bad = bad.addIfNone("!badsig");
    }
    if(sz > File.getSize()) bad = bad.addIfNone("!short");
    if(bad != "") sVersion = appendS("malformed"+bad,"/");
    if(File.isVerbose())
        sOption("ord:"+ord+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
}

function isJBM() {
    //ref https://github.com/adplug/adplug/blob/master/src/jbm.cpp
    //TODO for ex. demo3.jbm has a pointer to outside of the file,
    //  and it seems to work in a player but these are sanity checks ffs... Dunno this format
    if(File.read_uint16(0,_LE) != 2) return false;
    if(!File.compare("00FF01FF02FF03FF04FF05FF06FF07FF08FF09FF0AFFFD00", 0x36
)) return false; //just from the files modland has, 'cause the detection's' wonky
    seqt = File.read_uint16(4,_LE); if(!seqt || seqt < 30 || seqt >= File.getSize()) return false;
    inst = File.read_uint16(6,_LE); if(!inst || inst <= seqt || inst >= File.getSize()) return false;
    ins = (File.getSize()-inst) >> 4;
    //_log("fs="+Hex(File.getSize())+", inst="+Hex(inst)+", ins="+ins);
    mptn = 0xFFFF; ch = 0; trk=[];
    for(i=0; i < 11; i++) {
        p = File.read_uint16(10+i*2,_LE); trk[i] = p;
        if(p) { ch++;
            if(p <= seqt || p >= inst) return false;
            if(p < mptn) mptn = p
        }
    }
    //_log(trk);
    ptn = (mptn-seqt) >> 1;
    //_log("("+mptn+"-"+seqt+")/2="+ptn);
    for(i=0; i < 5/*ptn*/; i++) { //TODO make a full check when the format is more clear!
        if(trk[i]) {
            p = File.read_uint16(seqt+i*2,_LE);
            //_log(seqt+"["+i+"*2]="+Hex(p));
            if(File.read_uint16(p-1,_LE) != 0xFDFF) return false;
            if(p <= seqt || p >= inst) return false;
        }
    }
    i = File.read_uint16(2,_LE); tmr = (1193810.0 / (i ? i : 0xFFFF)).toFixed(2);
    return true
}
if(!bDetected && File.isHeuristicScan() && isJBM()) {
    sName = "Johannes Bjerregaard Adlib module (.JBM)"; bDetected = true;
    if(File.isVerbose())
        sOption("tmr:"+tmr+" ch:"+ch+" ptn:"+ptn+" ins:"+ins)
}

function isKefrensSoundMachine() {
    //ref https://github.com/tonioni/WinUAE/blob/master/prowizard/rippers/KefrensSoundMachine.c
    if(File.getSize()<1537) return false;
    if(File.read_uint8(15) != 0x61) return false;
    for(k=0; k<15; k++)
        if(File.read_uint8(54+k*32) > 0x40) return false;
    trks = 0;
    for(k=0; k<1024; k++) {
        k_ = File.read_uint8(k+512);
        if(k_ == 0xFF) break;
        if(k_ > trks) trks = k_;
    }
    if(k == 1024) return false;
    if(trks == 0) return false;
    if(1536 + trks*192 + 64*3 > File.getSize()) return false;
    for(k=0; k<=trks; k++)
        for(l=0; l<64; l++)
            if(File.read_uint8(1536 + k*192 + l*3) > 0x24) return false;

    allsmpsz = 0;
    for(k=0; k<15; k++)
        allsmpsz += File.read_uint16(52+k*32,_BE);
    sz = (trks+1)*192 + allsmpsz + 1536;
    return true
}
if(!bDetected && File.isHeuristicScan() && isKefrensSoundMachine()) {
    sName = "Kefrens Sound Machine module (.KSM)"; bDetected = true;
    if(File.isVerbose()) {
        sOptionT(File.read_ansiString(2,13));
        sOption("trk:"+trks+" ptn:"+ptn+" smp:"+smp+" sz:"+outSz(sz));
        if(sz > File.getSize()) sVersion = "malformed!short"
    }
}

function isMXDRV() {
    // fmt https://www.vector.co.jp/download/file/dos/art/fh003454.html / DOC/MDXFORM.DOC
    // ref ditto / SRC/MDX2MUS.ASM
    // useful links: https://gorry.haun.org/mx/index_e.html
    if(File.getSize() < 4+2+9*2 || File.getSize() > 0xFFFF) return false; //minimum header
    da1 = File.findSignature(0,Math.min(0x400,File.getSize()),"0D0A1A"); //the longest name encountered on modland was 99 bytes long
    if(da1 < 0) return false;
    for(i = 0; i < da1; i++) { c = File.read_uint8(i); if(c < 0x20 && c != 9 && c != 0x1B) return false }
    t = File.read_codePageString(0,da1,"SJIS");
    p = File.findSignature(da1+3,Math.min(15,File.getSize()-da1),"00"); if(p < 0) return false;
    if(da1+3 != p)
        pdxfn = File.read_ansiString(da1+3,p);
    else pdxfn = "";
    p++; ofs = p;
    comp = false;
    if(File.read_ansiString(ofs+4,4) == "LZX ") {
        lzxsz = File.read_uint32(ofs+0x12,_BE);
        //_log("ofs="+ofs+", lzxsz = "+lzxsz+" += "+(ofs+lzxsz));
        if(lzxsz) comp = true;
        return true; // stop right there because omg no
    }

    vd = File.read_uint16(ofs,_BE)+ofs;
    if(vd > File.getSize()) return false;
    p += 2;
    var chn = File.read_uint16(p,_BE)+ofs;
    if(chn < p+2 || chn > File.getSize()-2) return false;
    ch = (chn-p)>>1;
    if(ch != 16 && ch != 9) return false;
    oldchn = chn;
    for(k=1; k < ch; k++) { //check the rest of channels
        p += 2; if(p > File.getSize()-2) return false;
        chn = File.read_uint16(p,_BE)+ofs;
        if(chn < oldchn || chn > File.getSize()-2) return false;
        oldchn = chn
    }
    if(vd < chn) return false;
    return true;
}
if(!bDetected && File.isDeepScan() && isMXDRV()) {
    sName = "X68k MXDRV module (.MDX)"; bDetected = true;
    if(pdxfn != "") sVersion = "+ "+pdxfn;
    if(ch==16) sVersion += "#EX-PCM";
    if(comp) sVersion += "(compressed)";
    if(File.isVerbose()) {
        sOption(t);
    }
}

function isDMM() {
    // ref https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/digital/digitalmusicmaker.cpp
    for(i=0; i < 6; i++) if(!(0xC0 <= File.read_uint8(i*2+1) <= 0xFF)) return false;
    ptnsz = File.read_uint8(0x0C); if(ptnsz & 0x87) return false;
    ptn = -1; for(i=0; i < 0x32; i++) {
        j = File.read_uint8(0x0E+i); if(j > 0x17) return false;
        if(j > ptn) ptn = j;
    } ptn++;
    tempo = File.read_uint8(0x40); if(tempo < 3 || tempo > 30) return false;
    loop = File.read_uint8(0x41); if(loop > 0x32) return false;
    ord = File.read_uint8(0x43); if(!ord || ord > 0x32) return false;
    hss = File.read_uint8(0x44); if(hss < 2 || hss > 0x38) return false;
    bad = false; smp = 0;
    for(i=0; i < 16; i++) {
        smpst = File.read_uint16(0x5A+i*16+9,_LE);
        smplm = File.read_uint16(0x5A+i*16+12,_LE);
        smplp = File.read_uint16(0x5A+i*16+14,_LE);
        if(smpst > smplm || smpst < 49152 || smplm < 49152) return false;
        if(smplp < 49152 || smplp > smplm || smpst > smplp
          || (smplm > 49152 && smplm-smplp < 6)) {
            bad = true;
            if(!File.isHeuristicScan()) return false
        }
        if(smpst < smplm) smp++;
    }
    return true
}
if(!bDetected && File.isDeepScan() && isDMM()) {
    bDetected = true; sVersion = "v1.x"; if(bad) sVersion += "/malformed";
    sName = "Digital Music Maker module (.DMM)";
    if(File.isVerbose())
        sOption("tempo:"+tempo+" ord:"+ord+" ptn:"+ptn+" smp:"+smp+" loop:"+loop)
}

function isV2M() {
    //ref https://github.com/farbrausch/fr_public/blob/master/v2/v2mplayer.cpp ::InitBase
    //with kind commentary from Tammo "kb" Hinrichs
    notes = 0; chs = 0;
	// quick hack to avoid false positive detection for VHDX & VMDK
	if(Binary.compare("76 68 64 78 66 69 6C 65") || Binary.compare("'KDMV'")) return false;
    if(File.getSize() < 480) return false;
    timediv = File.read_uint32(0,_LE); if(!timediv) return false;
    maxtime = File.read_uint32(4,_LE); if(!maxtime) return false;
    gdnum = File.read_uint32(8,_LE); if(!gdnum) return false;
    p = 12+10*gdnum;
    for(ch=0; ch < 16; ch++) {
        notenum = File.read_uint32(p,_LE); p += 4; notes += notenum;
        if(notenum) { chs++;
            p += 5*notenum;
            if(p > File.getSize()) return false;
            pcnum = File.read_uint32(p,_LE); p += 4+4*pcnum; //pgm change events
            if(p > File.getSize()) return false;
            pbnum = File.read_uint32(p,_LE); p += 4+5*pbnum; //pitch bends
            if(p > File.getSize()) return false;
            for(cn=0; cn < 7; cn++) {
                ccnum = File.read_uint32(p,_LE); p += 4+4*ccnum; //control changes
                if(p > File.getSize()) return false;
            }
        }
    }
    size = File.read_uint32(p,_LE); if(size > 16384) return false; //uint32 so "|| size<0" removed
    p += 4+size; if(p > File.getSize()) return false;
    size = File.read_int32(p,_LE);
    if(size > 1048576 || size<0) return false;
    p += 4+size; if(p > File.getSize()) return false;
    return true;
}
if(!bDetected && File.isDeepScan() && isV2M()) {
    sName = "farbrausch V2 Synthesizer module (.V2M)"; bDetected = true;
    if(File.isVerbose())
        sOption = "ch:"+chs+"notes:"+notes+"timediv:"+timediv+"maxtime:"+maxtime;
    //TODO add the final tags
}

function isComposer670() {
    // from https://github.com/OpenMPT/openmpt/blob/master/soundlib/Load_c67.cpp
    if(File.getSize() < 1954+1024) return false; //7A2+400 = BA2
    spd = File.read_uint8(0);
    if(!spd || spd > 15) return false;
    ord = 0;
    for (i=0; i < 256; i++) { // check orders
        o = File.read_uint8(1698+i);
        if(o >= 128 && o != 0xFF) return false;
        if(o > ord && o != 0xFF) ord = o;
    }
    ord++;
    nonz = false;
    for (i=0; i < 32; i++) { // checking sample records
        _z = File.read_uint8(2+i*13+12); if(_z) return false;
        _t = File.read_uint32(0x1A2+i*16,_LE); if(_t) return false;
        _l = File.read_uint32(0x1A2+i*16+4,_LE); if(_l > 0xFFFFF) return false;
        _ls = File.read_uint32(0x1A2+i*16+8,_LE);
        _le = File.read_uint32(0x1A2+i*16+12,_LE);
        _z = File.read_uint8(0x3A2+i*13+12); if(_z) return false;
        if(File.read_uint8(0x542+i*11) & 0xF0) return false;
        if(File.read_uint8(0x542+i*11+5) & 0xFC) return false;
        if(File.read_uint8(0x542+i*11+10) & 0xFC) return false;
        if(_l && _le < 0xFFFFF) if(_le > _l || _ls > _le) return false;
        if(!nonz && (_l || !File.compare("00000000 00000000 000000",0x542+i*11)))
            nonz = true;
    }
    if(!nonz) return false;
    bad = false;
    for (i=0; i < 128; i++) { // checking patterns
        ptnofs = File.read_uint32(0x7A2+i*4,_LE);
        ptnlen = File.read_uint32(0x9A2+i*4,_LE);
        if(ptnofs > 0xFFFFFF || (File.getSize() < 0xBA2+ptnofs+ptnlen))
            return false;
    }
    if(!bad) bad = (ptnlen < 3 || ptnlen > 0x1000);
    return true
}
if(!bDetected && File.isDeepScan() && isComposer670()) {
    sName = "CDFM/Composer 670 module (.C67)"; bDetected = true;
    if(bad) sVersion = "malformed!badptn";
    if(File.isVerbose()) sOption("spd:"+spd+" ord:"+ord)
}

function isSeanConnolly() {
    //from UADE player check RE
    if(!File.compare("6000.... 6000.... 6000")) return false;
    a0 = 2+File.read_int16(2,_BE);
    if(File.compare("48E77FFE E98841FA",a0)) v = 2;
    else if(File.compare("48E740F0 4A006B0A",a0)) v = 3;
    else return false;
    return true
}
if(!bDetected && isSeanConnolly()) {
    sName = 'Sean "Odie" Connolly\'s module (.SCN)'; sVersion = "v"+v; bDetected = 1
}

function isAVP() {
    //from reversing the eagleplayer
    d1 = "48E7FCFE"; a0 = 0;
    if(File.compare(d1)) { // @d440 -> d47c
        if(File.compare("45FA",0xDC)) return false;
        if(!File.compare("E9417000 41FA",4)) return false;
        if(File.compare(d1,0x94) || File.compare(d1,0xA4) || File.compare(d1,0xA8)) {
            fmt = 0; return true }
        else return false
    } else if(File.compare("2F0841FA")) { //@d444 -> d4ac
        a0 += 4; a0 += File.read_int16(a0,_BE) + 0x1C;
        if(!File.compare("45FA",a0+0xDC)) return false;
        a0 += 4; if(!File.compare("E9417000 41FA",a0)) return false; a0 += 4;
        if(File.compare(d1,a0+0x8C) || File.compare(d1,a0+0x9C) || File.compare(d1,a0+0xA0)) {
            fmt = 0; base = a0; return true }
    } else if(File.compare(d1,0x1C)) { //@d44c -> d4b0
        a0 += 0x1C; if(!File.compare("45FA",a0+0xDC)) return false;
        a0 += 4; if(!File.compare("E9417000 41FA",a0)) return false; a0 += 4;
        if(File.compare(d1,a0+0x8C) || File.compare(d1,a0+0x9C) || File.compare(d1,a0+0xA0)) {
            fmt = 0; base = a0; return true }
    } else if(File.compare("6000")) { //@d452 -> d4d0
        if(!File.compare("6000.... 6000.... 6000.... 6000.... 6000.... 6000.... 6000",4)) return false;
        if(File.compare("6000",0x20) && !File.compare("6000",0x24)) return false;
        a0 = 0xE; a0 += File.read_int16(a0,_BE);
        if(File.compare(d1,a0)) { //@d50e -> d4be
            if(!File.compare("45FA",a0+0x10C) && !File.compare("E942",a0+0x112)) return false;
            a0 += 4; if(!File.compare("E9417000 41FA",a0)) return false; a0 += 4;
            if(File.compare(d1,a0+0x8C) || File.compare(d1,a0+0x9C) || File.compare(d1,a0+0xA0)) {
                fmt = 0; base = a0; return true }
        }
        a0 = 0x1A+File.read_int16(0x1A,_BE); if(!File.compare(d1+"43FA",a0)) return false;
        fmt = 1; base = a0; return true
    } else { //@d45a
        do { if(File.compare("2F0841FA",a0+0x1C)) break; a0 += 2 } while(a0 < 0xA);
        if(a0 == 0xA) return false; //else -> d46e
        i = 0x4C; do { if(File.compare(d1,a0)) break; a0 += 2; i-- } while(i);
        if(!i) return false; //else -> d4be
        if(!File.compare("45FA",a0+0x10C) && !File.compare("E942",a0+0x112)) return false;
        a0 += 4; if(!File.compare("E9417000 41FA",a0)) return false; a0 += 4;
        if(File.compare(d1,a0+0x8C) || File.compare(d1,a0+0x9C) || File.compare(d1,a0+0xA0)) {
            fmt = 0; base = a0; return true }
    }
    return false
}
if(!bDetected && isAVP()) {
    sName = "Activision Pro module (.AVP)"; sVersion = "f."+fmt; bDetected = true;
    if(File.isVerbose()) {
        sOption(Hex(base),"base:");
        //TODO: derive some info
    }
}
function isADL() {
    //ref https://github.com/adplug/adplug/blob/master/src/adl.cpp -> CadlPlayer::load
    if(File.getSize() < 720 || File.getSize() > 0xFFFF) return false; //v1 min. size
    p = 0; nV = 4; ofs = 500 /*0x1F4*/; //track entries
    for(i=0; i < 250; i++) { //500=1F4h is the trackentries buffer
        w = File.read_uint16(p,_LE); p += 2;
        if(500 <= w && w < 0xFFFF) { //in v4, all entries are within this range
            nV = 3; ofs = 120 /*0x78*/; break
        }
    }
    p = ofs; var z = true;
    if(nV < 4) {
        nprogofs = 150; //for v1
        for(i=0; i < nprogofs; i++) {
            w = File.read_uint16(p,_LE); p += 2;
            if(w) { z = false;
                if(w != 0xFFFF && w+ofs > File.getSize()) return false;
                if(w < 600) return false;
                if(w < 1000) { nV = 1; if(File.read_uint16(120,_LE) != 0x258) return false }
            }
            if(nV > 1) {
                if(File.getSize() < 1120) return false; //v2/3 min. size
                nprogofs = 250 /*0xFA*/;
                for(i=150; i < nprogofs; i++) { //check the rest of'em
                    w = File.read_uint16(p,_LE); p += 2;
                    if(w) { z = false;
                        if(w != 0xFFFF && (w+ofs > File.getSize() || ofs < nprogofs)) return false;
                        if(w && w < 1000) return false;
                    }}}}
        } else { //v4
        if(File.getSize() < 2500) return false;
        nprogofs = 500;
        for(i=0; i < nprogofs; i++) { //check the rest of'em
            w = File.read_uint16(p,_LE); p += 2;
            if(w) { z = false;
                if(w != 0xFFFF && (w+ofs > File.getSize() || ofs < nprogofs)) return false;
                if(w && w < 2000) return false;
            }}}
    return !z
    //whilst it'd be nice to have instruments (same len as prog ptr tbl) subsong detection...
    //... it's too much already
    //TODO false positive: modland/pmd/- unknown/Akai suishou no hitomi/ano-s01.m
}
if(!bDetected && File.isDeepScan() && isADL()) {
    sName = "Westwood ADL chiptune (.ADL)"; bDetected = true;
    switch(nV) {
    case 1: sVersion = "v1"; break;
    case 3: sVersion = "v2.x"; break;
    case 4: sVersion = "v3"
    }
}

function isFLS() {
    //this detection is based purely on sanity checks
    //  AND a tiiiiiny ~bruteforce~ of membase, which is why it comes in last
    //ref https://bulba.untergrund.net/Ay_Emul29.src.7z/Players.pas -> FoundFLS
    if(File.getSize() < 6) return false;
    //because it can get SO SLOW and the biggest module known is 6652 bytes long:
    if(File.getSize() > 8192) return false;
    postp = File.read_uint16(0x00,_LE); orntp = File.read_uint16(0x02,_LE); //positions & ornaments table ptrs
    smptp = File.read_uint16(0x04,_LE); ptnp = 6; //samples table ptr & patterns
    j = postp - smptp; if(j < 0 || (j&3)) return false;
    j = orntp-16; if(j < 0) return false;
    while(1) {
        j2 = smptp+2-j;
        if(j2 > File.getSize()+6) return false; // tentatively
        if(8 <= j2 < File.getSize()) {
            j3 = File.read_uint16(j2,_LE);
            j1 = j3 - j;
            if(8 <= j1 < File.getSize()) {
                j3 = File.read_uint16(j2 - 4);
                j2 = j3 - j;
                if(6 <= j2 < File.getSize())
                    if(j1-j2 == 0x20) {
                        j2 = File.read_uint16(ptnp+2,_LE)-j;
                        if(21 < j2 < File.getSize()) {
                            j1 = File.read_uint16(ptnp,_LE)-j;
                            if(20 < j1 < File.getSize())
                                if(!File.read_uint8(j1-1)) {
                                    while((j1 <= File.getSize()) && (File.read_uint8(j1) < 0xFF)) {
                                        while(1) {
                                            bt = File.read_uint8(j1);
                                            if((bt <= 0x5F) || (bt == 0x80) || (bt == 0x81)) { j1++; break }
                                            else if(0x82 <= bt <= 0x8E) j1++;
                                            if(j1>File.getSize()) break;
                                        }
                                    }
                                }
                                if(j1+1 == j2) break;
                        }
                    }
            }
        }
        j--; if(j < 0) break;
    }
    if(j < 0) return false;
    if((postp-j > File.getSize()) || (postp-j < 16)) return false;
    sz = File.read_uint16(postp-j-2,_LE)+0x60-j;
    if(sz <= postp-j) return false;
    if(sz > File.getSize()+1) return false;
    if((smptp-j)&1) return false;
    membase = j;
    return true;
}
if(!bDetected && File.isDeepScan() && isFLS()) {
    sName = "Flash Tracker module (.FLS)"; sVersion = "@"+Hex(j); bDetected = true;
    if(File.isVerbose()) sOption = "sz:"+outSz(sz)
}



//the following signature detectors seem shaky to me [Kae] so they're heuristic and in parallel
//_setResult(TYPE, NAME, VERSION, OPTIONS)

if(File.isHeuristicScan()) { //parallel block

    // Let's start with just judging by the extensions:
    if(extIs("bmf")) {//TODO
        _setResult("audio","The Brain's Easy Adlib 1.0 chiptune (.BMF)","","")
    }
    else if(extIs("mok")) { //TODO
        _setResult("audio","Silmarils module (.MOK)","","")
    }
    else if(extIs("imf") || extIs("wlf")) { //a detector is quite impossible to produce...
        if(extIs("imf"))
            freq = "560Hz (or 280Hz if Duke Nukem II)";
        else
            freq = "700Hz";
        _setResult("audio","id/Apogee Music Format chiptune (.IMF)","","freq: "+freq);
    }
    else if(extIs("svar") && File.compare("'PK'")) { //TODO detect in-zip
        _setResult("audio","SVArTracker module (.SVAR)","","");
    }
    else if(extIs("aks") && File.compare("1F8B")) { //TODO detect in-zip
        _setResult("audio","ArkosTracker module (.AKS)","","");
    }

    if(File.compare("0002",0x0E) && File.compare("0002",0x26)
        && File.compare("0002",0x32) && File.compare("0040",0x46)) {
        _setResult("audio","GT Game Systems module (.DUX)","","")
    }
    if(File.compare("28",3) && File.compare("21",0x20) && File.compare("FFFF",0x22)
      && File.compare("10",0x2B) && File.compare("32",0x33)) {
        _setResult("audio","Lionheart module (.LION)","","")
    }
    if(File.compare("2D58",0x134) && File.compare("183D7C",0x137)
      && File.compare("144DEE",0x13D) && File.compare("362D58",0x141)) {
        _setResult("audio","Mike Davies's module (.MD)","","")
    }

    if(File.compare("'SONG'", 0x3C)) {
        _setResult("audio","SoundFX module (.SFX)","","");
    }
    if(File.compare("00000114000001", 0x24)
      && File.compare("000001140000011400000114000001140000011400000114", 0x74)) {
        _setResult("audio","Sound Factory module (.PSF)","","")
    }
    else if(File.compare("'2a'16'I'FA",0x17)
      && File.compare("'2a'0C'I'FA",0x21) && File.compare("'Ff'EE'J@g'06')K'",0x45)) {
        _setResult("audio","Quartet PSG module (.SQT)","","")
    }

    //these are compares at 0, so they can be non-parallel, to optimise it all
    if(File.compare("8400")) {
        if(File.read_uint8(2) == 0xF0) sversion = "adv."; else sversion = "";
        _setResult("audio","Sierra Adlib chiptune (.SCI)",sversion,"")
    }
    else if(File.compare("AE")) { //TODO
        _setResult("audio","Polly Tracker module (.MOD)","","")
    }

    else if(File.compare("1C52")) {
        _setResult("audio","Sound Interface System module (.LEM)","","")
    }
    else if(File.compare("'NED'")) {
        _setResult("audio","Nerd Tracker ][ module (.NED)","","")
    }
    else if(File.compare("'MODU'")) {
        if(File.isVerbose())
            _setResult("audio","NovoTrade Packer module (.NTP)","",File.read_ansiString(4,16));
        else _setResult("audio","NovoTrade Packer module (.NTP)","","")
    }
    else if((File.compare("'Ice!'") || File.compare("'ICE!'"))) {
    //TODO find a sanity check to narrow it down, then move it to the other sanities
        dsize = File.read_uint32(8,_BE);
        _setResult("audio","Atari ST module (.SND,.SNDH)","compressed","orig.sz:"+dsize+" sz:"+outSz(File.read_uint32(4,_BE)))
    }
    else if( ([0x5941,0x4D59,0x7961,0x6D79].indexOf(File.read_uint16(0,_LE)) > 0) &&
        (1980 < File.read_uint16(0x0A,_LE) < 2050)) {
        //ref https://bulba.untergrund.net/Ay_Emul30alpha11.src.7z / Formats.pas
        fmode = 0;
        if(!File.read_uint8(2)) sversion = "1ch"; else sversion = "2ch";
        switch (File.read_uint16(0,_LE)) {
            case 0x5941: case 0x4D59: fmode = 1;
            case 0x5941: case 0x7961: sversion += " AY-3-8910/12"; break;
            case 0x4D59: case 0x6D79: sversion += " YM2149"; break;
            default: sversion += " unk.chip"
        }
        soption="";
        if(File.isVerbose()) {
            p=0x10;
            t = File.read_codePageString(p,Math.min(256,File.getSize()-p),"CP1251"); p+=t.length+1;
            soption = soption.append(t);
            a = File.read_codePageString(p,Math.min(256,File.getSize()-p),"CP1251"); p+=a.length+1;
            a=a.trim(); if(a.length) soption = soption.append("by: "+a);
            if(!fmode) {
                y = File.read_uint16(0x0A,_LE);
                if(y>0) soption.append("'"+y);
                pr = File.read_codePageString(p,Math.min(256,File.getSize()-p),"CP1251"); p+=pr.length+1;
                pr = pr.trim(); if(pr.length) soption = soption.append("for: "+pr);
                tn = File.read_ansiString(p,Math.min(256,File.getSize()-p)); p+=tn.length+1;
                tn = tn.trim(); if(tn.length) soption = soption.append("trk: "+tn);
                c = File.read_ansiString(p,Math.min(256,File.getSize()-p)); p+=c.length+1;
                c = c.trim(); if(c.length) soption = soption.append("in: "+c)
            }
            soption = soption.append("chip freq:"+File.read_uint32(5,_LE)+"Hz");
            soption = soption.append("int.freq:"+File.read_uint8(9)+"kHz")
        }
        _setResult("audio","Vortex Project audio (.VTX)",sversion,soption)
    }
    // everyone's fave let's-detect-by-Amiga-init-block ones grouped in here. TODO
    else if(File.compare("600000..6000....6000") && File.compare("177C00", 0xEA)
      && File.compare("2BFF", 0x1D9) && File.compare("000667", 0x4D4)) {
        bDetected = true; sName = "Sound Master II module (.SM3)"; sVersion = "v3"
    }

    function isMXDRVPDX() {
        // the format is very very very basic so .ADL overlaps happen
        //and it's not possible to test more
        if(File.getSize() < 0x300 || File.getSize() > 96*8+96*65535) return false;
        sz = 0;
        for(i=0; i<96; i++) {
            p = File.read_uint32(i*8,_BE);
            if(!p) continue;
            if(p < 0x300) return false;
            sz++;
            s = File.read_uint16(i*8+4,_BE);
            if(s > 0xFFFF || p+s > File.getSize()) return false;
        }
        if(!sz) return false;
        return true
    }
    if(isMXDRVPDX()) {
        _setResult("audio","X68k MXDRV PCM resource (.PDX)","","entries:"+sz)
    }

    // function isHIP() {
    //     //ref https://web.archive.org/web/20081006053424/http://amore68k.netfirms.com/amore_v1.01a_007.zip
    //     // の中のhippel.txt
    //     //TODO sz is always incorrect and much bigger
    //     a1_ = File.findSignature(7,TOEOF,"0071007100710071 007100710071");
    //     if(a1_ < 0) return false;
    //     a1 = File.findSignature(a1_+0x0C,0x100,"'TFMX'");
    //     if(a1 < 0) return false;
    //     if(File.read_ansiString(a1-0x20,4) == "COSO") return false;
    //     u1 = File.read_uint16(a1+4,_BE)+1; u2 = File.read_uint16(a1+6,_BE)+1;
    //     u3 = File.read_uint16(a1+8,_BE)+1; p1 = File.read_uint16(a1+0x0A,_BE)+1;
    //     s1 = File.read_uint16(a1+0x10,_BE)+1; i1 = File.read_uint16(a1+0x12,_BE);
    //     um = 0x40; pm = 0x0C; sm = 6; im = 0x1E;
    //     a2 = a1+0x20; a3 = a2+u1*um; a4 = a3+u2*um; a5 = a4+u3*um;
    //     a6 = a5+p1*pm; a7 = a6+s1*sm; a8 =a7+ i1*im;
    //     if(a8 > File.getSize()) return false;
    //     sz=0;
    //     //_log("a4:"+Hex(a4)+" a5:"+Hex(a5)+" a6:"+Hex(a6)+" a7:"+Hex(a7)+" a8:"+Hex(a8));
    //     for(i=0; i < i1; i++) {
    //         smpend = File.read_uint32(a7+i*im+0x12,_BE)+File.read_uint16(a7+i*im+0x16)*2;
    //         //_log("smp #"+i+"["+File.read_ansiString(a7+i*im,0x12)+"]>"+Hex(smpend));
    //         if(sz < smpend) sz = smpend;
    //     }
    //     sz += a8;
    //     if(File.read_uint8(0) != 0x60) return false; else { //asm tests
    //         if(!File.read_uint8(1)) { //b.short
    //             jinitplay = Math.max(File.read_uint8(1),File.read_uint8(3));
    //             if(jinitplay != 6) return false;
    //         } else { //b.long
    //             jinitplay = Math.max(File.read_uint16(3,_BE),File.read_uint16(7,_BE));
    //             if(jinitplay != 0xFE61 && jinitplay != 0x1A41) //only see the second one in two files
    //                 return false;
    //         }
    //     }
    //     //if(sz > File.getSize()) return false; //what a weird thing
    //     return true
    // }
    // if(File.isDeepScan() && isHIP()) {
    //     _setResult("audio","Jochen Hippel's module (.HIP)","","sz:"+outSz(sz))
    // }

    function isKDM() {
        // ref http://advsys.net/ken/kdmsongs.zip/KDMSRC.ZIP/kdmwin/KDMENG.C
        if(File.getSize() < 12) return false;
        if(File.read_uint32(0,_LE)) return false; //kdmversionum
        numnotes = File.read_uint32(4,_LE); if(!numnotes || numnotes > 8192) return false;
        numtracks = File.read_uint32(8,_LE); if(!numtracks || numtracks > 256) return false;
        sz = 12+numtracks*4+numnotes*11;
        if((sz != File.getSize()) && (sz != File.getSize()+numnotes)) return false;
        return true
    }
    if(isKDM()) {
        if(sz > File.getSize()) sversion = "no panning effects"; else sversion = "";
        if(File.isVerbose()) soption = "trk:"+numtracks+" notes:"+numnotes; else soption = "";
        _setResult("audio","Ken's Digital Music module (.KDM)",sversion,soption);
    }

    function isSQDigitalTracker() {
        debug = 0;
        //from https://bitbucket.org/zxtune/zxtune/src/develop/src/formats/chiptune/digital/sqdigitaltracker.cpp
        for(i=0; i < 8; i++) {
            if(File.read_uint8(0xC0+i*4)) return false;
            q = File.read_uint8(0xC0+i*4+1); if(q < 0x80 || q > 0xC0) return false;
            q = File.read_uint8(0xC0+i*4+2); if(q < 0x58 || q > 0x5F) return false;
            q = File.read_uint8(0xC0+i*4+3); if(!q || q > 0x80) return false;
        }
        for(i=0; i < 0x20; i++) {
            t_ = File.read_uint8(0x100+i);
            if(t_[i] < 0x20 || t_[i] > 0x7F) return false
        }
        t = File.read_ansiString(0x100,0x20);
        ptn = -1;
        for(i=0; i < 100; i++) {
            q = File.read_uint8(0x1A0+i); if(q > 0x1F) return false;
            if(ptn < q) ptn = q;
        } ptn++;
        if(File.read_uint8(0x204) != 0xFF) return false;
if(debug)_log(1);
        tempo = File.read_uint8(0x210);
if(debug)_log("tempo:"+tempo);
        if(tempo < 2 || tempo > 10) return false;
        len = File.read_uint8(0x212);
if(debug)_log("len:"+len);
        if(!len || len > 0x64) return false; len++;
        loop = File.read_uint8(0x211);
if(debug)_log("loop:"+loop);
        if(loop >= len) return false;
        return true
    }
    if(File.isDeepScan() && isSQDigitalTracker()) {
        soption = "";
        if(File.isVerbose()) {
            soption = soption.append(t.trim());
            soption = soption.append("tempo:"+tempo+" ord:"+len+" ptn:"+ptn+" loop:"+loop);
        }
        _setResult("audio","SQ Digital Tracker module (.SQD,.M)","",soption)
    }

    function isFredMon() {
        //ref https://zakalwe.fi/uade/uade3/uade-3.03.tar.bz2 / uade/src/frontends/common/amifilemagic.c
        if(!File.compare("4EFA....4EFA....4EFA....4EFA")) return false;
        for (i = 0x10; i < 256; i += 2)
            if (File.compare("4E7547FA ........ ........ 4E75",i)) return true
        return false
    }
    if(File.isDeepScan() && isFredMon()) {
        _setResult("audio","FredMon module (.FRED)?","","")
    }

    function isMON() {
        if(!File.compare("4EFA....4EFA....4EFA")) return false;
        var t = File.read_uint16(2,_BE); if (t > File.getSize() - 9) return false;
        return File.compare("4BFA.... 08AD 0000",t+2)
    }
    if(File.isDeepScan() && isMON()) {
        _setResult("audio","Maniacs of Noise module (.MON)?","","")
    }

    function isD00nohdr() { if(File.getSize() > 0xFFFF) return false;
        if(File.compare("00...... 000D00") && File.compare("FFFF",0x0B)) nV = 0;
        else if(File.compare("01...... 000F00") && File.compare("FFFF",0x0D)) nV = 1;
        else return false;
        t = File.read_uint16(5,_LE); if(t < 13 || t > File.getSize()) return false;
        p = File.read_uint16(t,_LE); t1 = File.read_uint16(3,_LE); if(t1 < 13 || t > File.getSize()) return false;
        t1 = File.read_uint16(7,_LE); if(t1 < 13 || t > File.getSize()) return false;
        if(t == t1 || t == t2) return false; tn = t1+18; oldp = t;
        while(t < tn) { if(t === t1) p = oldp = t;
            if(p) oldp = p; p = File.read_uint16(t,_LE); if(!p) { t += 2; continue }
            if(p < oldp) return false; q = p;
            while(!File.compare("FFFF",q) && q < File.getSize()) q += 2;
            if(!File.compare("FFFF",q)) return false; t += 2
        }
        p = oldp = t;
        if(!File.compare("FFFF",t+14)) return false;
        sz = File.read_uint16(9,_LE);
            if(!File.compare("FFFF",sz-2)) return false;
        return true;
    }
    if(File.isDeepScan() && isD00nohdr()) {
        if(File.isVerbose()) _setResult("audio","Edlib Tracker module (.D00)?","v"+nV+" headerless","sz:"+outSz(File.read_uint16(9,_LE)));
        else _setResult("audio","Maniacs of Noise module (.MON)","v"+nV+" headerless","")
    }

} //isHeuristicScan parallel block

    return result(bShowType,bShowVersion,bShowOptions);
}
