docs: function dbf_to_aoa(buf, opts)/*:AOA*/ {
  	var out/*:AOA*/ = [];
  	/* TODO: browser based */
  	var d/*:Block*/ = (new_raw_buf(1)/*:any*/);
  	switch(opts.type) {
  		case 'base64': d = s2a(Base64.decode(buf)); break;
  		case 'binary': d = s2a(buf); break;
  		case 'buffer':
  		case 'array': d = buf; break;
  	}
  	prep_blob(d, 0);
  	/* header */
  	var ft = d.read_shift(1);
  	var memo = false;
  	var vfp = false, l7 = false;
  	switch(ft) {
  		case 0x02: case 0x03: break;
  		case 0x30: vfp = true; memo = true; break;
  		case 0x31: vfp = true; break;
  		case 0x83: memo = true; break;
  		case 0x8B: memo = true; break;
  		case 0x8C: memo = true; l7 = true; break;
  		case 0xF5: memo = true; break;
  		default: throw new Error("DBF Unsupported Version: " + ft.toString(16));
  	}
  	var /*filedate = new Date(),*/ nrow = 0, fpos = 0;
  	if(ft == 0x02) nrow = d.read_shift(2);
  	/*filedate = new Date(d.read_shift(1) + 1900, d.read_shift(1) - 1, d.read_shift(1));*/d.l += 3;
  	if(ft != 0x02) nrow = d.read_shift(4);
  	if(ft != 0x02) fpos = d.read_shift(2);
  	var rlen = d.read_shift(2);
  
  	var /*flags = 0,*/ current_cp = 1252;
  	if(ft != 0x02) {
  	d.l+=16;
  	/*flags = */d.read_shift(1);
  	//if(memo && ((flags & 0x02) === 0)) throw new Error("DBF Flags " + flags.toString(16) + " ft " + ft.toString(16));
  
  	/* codepage present in FoxPro */
  	if(d[d.l] !== 0) current_cp = dbf_codepage_map[d[d.l]];
  	d.l+=1;
  
  	d.l+=2;
  	}
  	if(l7) d.l += 36;
  /*:: type DBFField = { name:string; len:number; type:string; } */
  	var fields/*:Array<DBFField>*/ = [], field/*:DBFField*/ = ({}/*:any*/);
  	var hend = fpos - 10 - (vfp ? 264 : 0), ww = l7 ? 32 : 11;
  	while(ft == 0x02 ? d.l < d.length && d[d.l] != 0x0d: d.l < hend) {
  		field = ({}/*:any*/);
  		field.name = cptable.utils.decode(current_cp, d.slice(d.l, d.l+ww)).replace(/[\u0000\r\n].*$/g,"");
  		d.l += ww;
  		field.type = String.fromCharCode(d.read_shift(1));
  		if(ft != 0x02 && !l7) field.offset = d.read_shift(4);
  		field.len = d.read_shift(1);
  		if(ft == 0x02) field.offset = d.read_shift(2);
  		field.dec = d.read_shift(1);
  		if(field.name.length) fields.push(field);
  		if(ft != 0x02) d.l += l7 ? 13 : 14;
  		switch(field.type) {
  			case 'B': // VFP Double
  				if((!vfp || field.len != 8) && opts.WTF) console.log('Skipping ' + field.name + ':' + field.type);
  				break;
  			case 'G': // General
  			case 'P': // Picture
  				if(opts.WTF) console.log('Skipping ' + field.name + ':' + field.type);
  				break;
  			case 'C': // character
  			case 'D': // date
  			case 'F': // floating point
  			case 'I': // long
  			case 'L': // boolean
  			case 'M': // memo
  			case 'N': // number
  			case 'O': // double
  			case 'T': // datetime
  			case 'Y': // currency
  			case '0': // VFP _NullFlags
  			case '@': // timestamp
  			case '+': // autoincrement
  				break;
  			default: throw new Error('Unknown Field Type: ' + field.type);
  		}
  	}
  	if(d[d.l] !== 0x0D) d.l = fpos-1;
  	else if(ft == 0x02) d.l = 0x209;
  	if(ft != 0x02) {
  		if(d.read_shift(1) !== 0x0D) throw new Error("DBF Terminator not found " + d.l + " " + d[d.l]);
  		d.l = fpos;
  	}
  	/* data */
  	var R = 0, C = 0;
  	out[0] = [];
  	for(C = 0; C != fields.length; ++C) out[0][C] = fields[C].name;
  	while(nrow-- > 0) {
  		if(d[d.l] === 0x2A) { d.l+=rlen; continue; }
  		++d.l;
  		out[++R] = []; C = 0;
  		for(C = 0; C != fields.length; ++C) {
  			var dd = d.slice(d.l, d.l+fields[C].len); d.l+=fields[C].len;
  			prep_blob(dd, 0);
  			var s = cptable.utils.decode(current_cp, dd);
  			switch(fields[C].type) {
  				case 'C':
  					out[R][C] = cptable.utils.decode(current_cp, dd);
  					out[R][C] = out[R][C].trim();
  					break;
  				case 'D':
  					if(s.length === 8) out[R][C] = new Date(+s.slice(0,4), +s.slice(4,6)-1, +s.slice(6,8));
  					else out[R][C] = s;
  					break;
  				case 'F': out[R][C] = parseFloat(s.trim()); break;
  				case '+': case 'I': out[R][C] = l7 ? dd.read_shift(-4, 'i') ^ 0x80000000 : dd.read_shift(4, 'i'); break;
  				case 'L': switch(s.toUpperCase()) {
  					case 'Y': case 'T': out[R][C] = true; break;
  					case 'N': case 'F': out[R][C] = false; break;
  					case ' ': case '?': out[R][C] = false; break; /* NOTE: technically uninitialized */
  					default: throw new Error("DBF Unrecognized L:|" + s + "|");
  					} break;
  				case 'M': /* TODO: handle memo files */
  					if(!memo) throw new Error("DBF Unexpected MEMO for type " + ft.toString(16));
  					out[R][C] = "##MEMO##" + (l7 ? parseInt(s.trim(), 10): dd.read_shift(4));
  					break;
  				case 'N': out[R][C] = +s.replace(/\u0000/g,"").trim(); break;
  				case '@': out[R][C] = new Date(dd.read_shift(-8, 'f') - 0x388317533400); break;
  				case 'T': out[R][C] = new Date((dd.read_shift(4) - 0x253D8C) * 0x5265C00 + dd.read_shift(4)); break;
  				case 'Y': out[R][C] = dd.read_shift(4,'i')/1e4; break;
  				case 'O': out[R][C] = -dd.read_shift(-8, 'f'); break;
  				case 'B': if(vfp && fields[C].len == 8) { out[R][C] = dd.read_shift(8,'f'); break; }
  					/* falls through */
  				case 'G': case 'P': dd.l += fields[C].len; break;
  				case '0':
  					if(fields[C].name === '_NullFlags') break;
  					/* falls through */
  				default: throw new Error("DBF Unsupported data type " + fields[C].type);
  			}
  		}
  	}
  	if(ft != 0x02) if(d.l < d.length && d[d.l++] != 0x1A) throw new Error("DBF EOF Marker missing " + (d.l-1) + " of " + d.length + " " + d[d.l-1].toString(16));
  	if(opts && opts.sheetRows) out = out.slice(0, opts.sheetRows);
  	return out;
  }