package com.proxgrind.chameleon.utils.mifare; import android.net.Uri; import android.nfc.tech.MifareClassic; import com.proxgrind.chameleon.javabean.MifareBean; import com.proxgrind.chameleon.javabean.M1KeyBean; import com.proxgrind.chameleon.utils.stream.FileUtils; import com.proxgrind.chameleon.utils.system.LogUtils; import com.proxgrind.chameleon.utils.tools.HexUtil; import com.proxgrind.chameleon.utils.tools.MifareUtils; import com.proxgrind.chameleon.utils.tools.RegexGroupUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; /*Dump文件操作类封装*/ public class DumpUtils { private static final String LOG_TAG = DumpUtils.class.getSimpleName(); //默认的空数据! public static final String BLANK_DATA = "00000000000000000000000000000000"; // 默认的秘钥 public static final String BLANK_KEY = "FFFFFFFFFFFF"; // 默认的尾部块 public static final String BLANK_TRAIL = "FF078069"; // 空的尾部块! public static final String BLANK_TRAIL_BLOCK = BLANK_KEY + BLANK_TRAIL + BLANK_KEY; //没有密钥时的填充 public static final String NO_KEY = "*FFFFFFFFFFF"; //没有数据时的填充 public static final String NO_DAT = "*0000000000000000000000000000000"; //没有尾部块的时候的填充 public static final String NO_TRAIL = "*F078069"; //默认的空尾部块! public static final String NO_TRAIL_BLOCK = NO_KEY + NO_TRAIL + NO_KEY; // 默认秘钥! public final static String[] KEY_DEFAULT = new String[]{ "FFFFFFFFFFFF", "A0A1A2A3A4A5", "D3F7D3F7D3F7", "000000000000", "A0B0C0D0E0F0", "A1B1C1D1E1F1", "B0B1B2B3B4B5", "4D3A99C351DD", "1A982C7E459A", "AABBCCDDEEFF" }; public final static byte[][] KEY_DEFAULT_BYTE = mergeHexKeys(KEY_DEFAULT); public final static long MAX_DUMP_FILE_SIZE = FileUtils.Size.KB * 10; public final static int NEWPREFIXLENGTH = 56; public final static int OLDPREFIXLENGTH = 48; /* * 由于卡的种类问题,扇区数不能固定,块数量也不能固定 * 因此,不可以写死扇区和数据块,并且,数据一定要完整!!! * */ private DumpUtils() { //no instance } /* * 目前已知的数据格式 * S50的结构和S70的结构 * 我们需要知道,块的数据格式总是16字节的长度 * 但是,S50和S70的卡由于扇区数量不同,因此,不能单纯判断块的长度 * S50: 64个块,64 * 16 = 1024b * S70: 256个块,256 * 16 = 4096b * */ public static final int TYPE_TXT = 1; public static final int TYPE_BIN = 2; public static final int TYPE_NOT = -1; // 将秘钥字符串数组转换为秘钥字节数组! public static byte[][] mergeHexKeys(String[] hexKeys) { byte[][] ret = new byte[hexKeys.length][]; for (int i = 0; i < ret.length; i++) { // 将字符串转为字节! ret[i] = HexUtil.hexStringToByteArray(hexKeys[i]); } return ret; } //判断是否是注释行 public static boolean isAnnotaion(String str) { return str.trim().startsWith("#"); } // 根据扇区号来获取相应的空扇区! public static MifareBean getEmptyM1Bean(int sector) { MifareBean ret = new MifareBean(); int blockCount = MfDataUtils.getBlockCountInSector(sector); String[] datas = new String[blockCount]; for (int i = 0; i < blockCount; i++) { datas[i] = i < (blockCount - 1) ? BLANK_DATA : BLANK_TRAIL_BLOCK; } ret.setDatas(datas); ret.setSector(sector); return ret; } //判断是否是密钥格式 public static boolean isKeyFormat(String key) { return HexUtil.isHexString(key) && key.length() == 12; } // 是否是正常的块数据! public static boolean isBlocksValids(String[] datas) { switch (datas.length) { case MifareClassic.SIZE_1K / MifareClassic.BLOCK_SIZE: case MifareClassic.SIZE_2K / MifareClassic.BLOCK_SIZE: case MifareClassic.SIZE_4K / MifareClassic.BLOCK_SIZE: return true; } // LogUtils.d("检测的长度: " + datas.length); return false; } //提取密钥 public static String[] extractKeys(MifareBean[] datas) { ArrayList ret = new ArrayList<>(); //迭代当前的bean。 for (MifareBean b : datas) { //跳过无效的bean包! if (b == null) continue; //得到其中的数据封包 String[] dataArr = b.getDatas(); //跳过无效的数据封包! if (dataArr == null) continue; //判断块的长度,必须正确! if (dataArr.length == 4 || dataArr.length == 16) { //得到尾部的数据块! String lastBlock = dataArr[dataArr.length - 1]; //判断尾部块的数据是否有效! if (lastBlock == null || (lastBlock.length() != 32)) continue; //开始提取A密钥! String keyA = lastBlock.substring(0, 12); //开始提取密钥B! String keyB = lastBlock.substring(20, 32); //判断密钥有效性,酌情提取! if (isKeyFormat(keyA)) { ret.add(keyA); } if (isKeyFormat(keyB)) { ret.add(keyB); } } } return ret.toArray(new String[0]); } /* * BCC获取 * */ public static byte calcBCC(byte[] uid) { if (uid.length != 4) { return -1; } byte bcc = uid[0]; for (int i = 1; i < uid.length; i++) bcc = (byte) (bcc ^ uid[i]); return bcc; } /* * BCC有效性判断! * */ public static boolean isBCCVaild(String uidAndBcc) { if (uidAndBcc.length() != 10) return false; String bccStr = uidAndBcc.substring(8, 10); byte bcc = HexUtil.hexStringToByteArray(bccStr)[0]; String uidStr = uidAndBcc.substring(0, 8); byte[] uidBytes = HexUtil.hexStringToByteArray(uidStr); return calcBCC(uidBytes) == bcc; } /* * 分离字符串,以换行符分割 * */ public static String[] splitDump(String dump) { //判断dump来源 if (isUnixLFFormat(dump)) { //unix类系统 return dump.split(getSystemLF("unix")); } else { return dump.split(getSystemLF("windows")); } } /* * 判断是不是原生的数据文件 * */ public static boolean isRaw1K(File dump) { return dump.length() == 1024; } public static boolean isRaw1K(byte[] dumpByte) { return dumpByte.length == 1024; } /* * 判断是否是2K的数据文件 * */ public static boolean isRaw2K(File dump) { return dump.length() == 2048; } public static boolean isRaw2K(byte[] dumpByte) { return dumpByte.length == 2048; } /* * 判断是否是原生4k文件 * */ public static boolean isRaw4K(File dump) { return dump.length() == 4096; } public static boolean isRaw4K(byte[] dumpByte) { return dumpByte.length == 4096; } /* * 将1kdump转为4k文件 * */ public static byte[] raw1Kto4k(byte[] raw1k) throws Exception { if (isRaw1K(raw1k)) throw new Exception("非1k字节文件!"); //建立一个4096大小的数组 byte[] ret = new byte[4096]; for (int i = 0; i < raw1k.length; i++) { //将1k数据合并至4k的数据 ret[i] = raw1k[i]; } //填充剩余的字节为0 for (int i = raw1k.length; i < ret.length; i++) { ret[i] = 0x00; } return ret; } /* * 将4k文件转换为1k的文件 * */ public static byte[] raw4kto1k(byte[] raw4k) throws Exception { if (isRaw4K(raw4k)) throw new Exception("非4k字节文件!"); byte[] ret = new byte[1024]; for (int i = 0; i < ret.length; i++) { ret[i] = raw4k[i]; } return ret; } /* * 判断是否是16进制字符 * */ public static boolean isDataChar(char c) { if (c >= 'a' && c <= 'z') { return true; } if (c >= 'A' && c <= 'Z') { return true; } if (c >= '0' && c <= '9') { return true; } if (c == '-') { return true; } if (c == '?') return true; return c == '*'; } /* * 裁剪有效数据 * */ public static String cutVaildData(String str) { String ret; ret = RegexGroupUtils.matcherGroup(str, "([A-Fa-f0-9]{32})", 1, 0); if (ret != null) { return ret; } char[] cs = new char[str.length()]; str.getChars(0, str.length(), cs, 0); //搜索字符串数组,从尾部开始搜素! int pos = 0; for (int i = cs.length - 1; i >= 0; i--) { if (!isDataChar(cs[i])) { //如果当前不是数据字符,则移动指针到上一位(也是顺序下一位) pos = i + 1; // Log.d(LOG_TAG, "非数据字符,跳过"); break; } } //走常规的判断应当从尾部开始截取,直到遇见任何非16进制字符或者非注释字符时停止并获得定位 if (pos == 0) { // Log.d(LOG_TAG, "定位在0,直接返回原字符串: " + str); return str; } else { ret = str.substring(pos); // Log.d(LOG_TAG, "定位不为零,返回经过裁剪后的: " + ret); } // 修复非有效块依旧返回的问题! return isValidBlockData(ret) ? ret : null; } /* * 判断是否是块数据 * */ public static boolean isBlockData(String data) { return data.length() == 32 && data.matches("[0-9a-fA-F-?*]{32}"); } public static boolean isValidBlockData(String data) { return data.length() == 32 && data.matches("[0-9a-fA-F]{32}"); } public static boolean isM1Data(byte[] data) { return data.length == MifareClassic.SIZE_1K || data.length == MifareClassic.SIZE_2K || data.length == MifareClassic.SIZE_4K; } public static boolean isM1Data(String[] data) { for (String block : data) { if (!isValidBlockData(block)) return false; } return true; } public static boolean isULData(String[] data) { for (String page : data) { if (page.length() != 8 || !HexUtil.isHexString(page)) return false; } return true; } public static boolean isULData(byte[] data) { return data.length == 64 || data.length == 80 || data.length == 164 || data.length == 176 || data.length == 192; } /* * 转换为bin数据流 */ public static byte[][] getBin(byte[] data) { if (data == null) return null; if (isM1Data(data)) { return HexUtil.splitBytes(data, 16); } else if (isULData(data)) { return HexUtil.splitBytes(data, 4); } else if (hasUltralightHeader(data)) { data = Arrays.copyOfRange(data, OLDPREFIXLENGTH, data.length); return isULData(data) ? HexUtil.splitBytes(data, 4) : null; } else if (hasUltralightNewHeader(data)) { data = Arrays.copyOfRange(data, NEWPREFIXLENGTH, data.length); return isULData(data) ? HexUtil.splitBytes(data, 4) : null; } else { return null; } } /* * 合并 * */ public static byte[] mergeBins(byte[][] datas) { if (datas == null) return null; ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); for (byte[] data : datas) { try { bos.write(data); } catch (IOException e) { e.printStackTrace(); } } return bos.toByteArray(); } /* * 转换为txt数据流 * */ public static String[] getTxt(byte[] data) { if (data == null) return null; StringBuilder sb = new StringBuilder(); //转换为字符串 String dataStr = new String(data); //根据换行符进行切割字符串 String[] dataLines = splitDump(dataStr); //进行有效数据的判断切割 for (int i = 0; i < dataLines.length; i++) { //判断一下,如果数据有效,则追加进字符串构造器中 //16进制的字符串必须要有至少HEX32个字符 if (dataLines[i].length() < 32) continue; //如果等于32个字符串则需要验证一下字符串是否是正确的16进制字符串! if (dataLines[i].length() == 32) { //添加结果集 if (isBlockData(dataLines[i])) sb.append(dataLines[i]); //判断是否需要换行! if (i != (dataLines.length - 1)) sb.append(getSystemLF("unix")); } else { //如果大于32个字符则需要尝试截取一下 String vaildData = cutVaildData(dataLines[i]); // Log.d(LOG_TAG, "测试输出数据: " + vaildData); //数据裁剪成功,添加进数据集中 if (vaildData != null) sb.append(vaildData); //判断是否需要换行 if (i != dataLines.length - 1) sb.append(getSystemLF("unix")); } } //结果集转换为数组 String[] ret = sb.toString().split(getSystemLF("unix")); //分别符合1K,2K,4K的规范! //判断块数量,严格控制规范,使块数量在64或者256之间 if (ret.length == 64 || ret.length == 128 || ret.length == 256) { /*for (String b : ret) { Log.d(LOG_TAG, "测试输出截取结果: " + b); }*/ return ret; } else { return null; } } /* * 合并数组为字符串! * */ public static String mergeTxt(String[] txts, boolean needNewLine, String lineChar) { if (txts == null) return null; StringBuilder sb = new StringBuilder(); for (int i = 0; i < txts.length; i++) { if (needNewLine) { if (i == txts.length - 1) { sb.append(txts[i]); } else { sb.append(txts[i]).append(lineChar); } } else { sb.append(txts[i]); } } return sb.toString(); } /* * 判断数据类型 * */ public static int getType(byte[] data) { //初步断定是原生的二进制文件 if (getBin(data) != null) return TYPE_BIN; //最后判断是txt文件 if (getTxt(data) != null) return TYPE_TXT; //然后尝试切割有效的数据 return TYPE_NOT; } /* * txt转换为bin * */ public static byte[] txt2Bin(byte[] txt) { //已经封装过将文本忽略修饰直接转换为纯hex文本的方法 //直接调用这个方法进行获取,而后转换为bin格式 String[] datas = getTxt(txt); //输出流,把字节输出到buf,初始大小1024,4k时自动扩展! ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); //进行迭代 for (String data : datas) { //进行转换 byte[] _tmps = HexUtil.hexStringToByteArray(data); try { if (_tmps != null) { baos.write(_tmps); } } catch (IOException e) { e.printStackTrace(); } finally { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } } return baos.toByteArray().length >= 1024 ? baos.toByteArray() : null; } /* * bin转换为txt * */ public static byte[] bin2Txt(byte[] bin) { //得到经过解析的块数组 byte[][] blocks = getBin(bin); StringBuilder sb = new StringBuilder(); //如果不为空,则是有效的数据 if (blocks != null) { //抽取出每一块的数据,将其转换为16进制的字符串 for (int i = 0; i < blocks.length; ++i) { //迭代这个数据,进行转换处理 if (i == blocks.length - 1) { //最后一个元素,无需添加换行 sb.append(HexUtil.toHexString(blocks[i])); } else { sb.append(HexUtil.toHexString(blocks[i])).append("\n"); } } } else { return null; } return sb.toString().getBytes(); } /* * 进行修饰 * */ public static String decorate(byte[] bytes) { /* * 修饰为MCT支持的格式(为了兼容性) * */ //调用已经封装的字节数组转String数组的方法 String[] blocks = getTxt(bytes); return decorate(blocks); } public static String decorate(String[] bytes) { //根据规范,当块的数量是64时,他是1K卡(S50),有16个扇区 //如果是256个块时,他是4K卡(S70) if (bytes == null) { // LogUtils.d("decorate()函数截取失败!"); return null; } if (bytes.length != 64 && bytes.length != 128 && bytes.length != 256) return null; String label = "+Sector: "; StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { //当前是每个扇区的起始块! if (isHeader(i)) { //添加扇区修饰,使数据可被MCT识别! sb.append(label).append(toSector(i)).append('\n'); //继而将这个起始扇区写在后面 sb.append(bytes[i]).append('\n'); } else { if (i == bytes.length - 1) { //已经在最结尾的一行,不需要换行 sb.append(bytes[i]); } else { //需要换行 sb.append(bytes[i]).append('\n'); } } } return sb.toString(); } /* * 块转扇区 * */ public static int toSector(int block) { if (block < 32 * 4) { return (block / 4); } else { return (32 + (block - 32 * 4) / 16); } } /* * 扇区转块 * */ public static int toBlock(int sector) { if (sector < 32) { return (sector * 4); } else { return (32 * 4 + (sector - 32) * 16); } } /* * 是否是在首行 * */ public static boolean isHeader(int block) { if (block < 128) return (block % 4 == 0); else return (block % 16 == 0); } /* * 是否在尾部 * */ public static boolean isFoolter(int block) { if (block < 128) return ((block + 1) % 4 == 0); else return ((block + 1) % 16 == 0); } /* * 去除修饰 * */ public static String[] undecorate(byte[] bytes) { return getTxt(bytes); } /* * 将M1数据bean合并为数组 * */ public static String[] mergeDatas(ArrayList beans) { StringBuilder sb = new StringBuilder(); // 排序一下! Collections.sort(beans, new Comparator() { @Override public int compare(MifareBean o1, MifareBean o2) { return Integer.compare(o1.getSector(), o2.getSector()); } }); for (int i = 0; i < beans.size(); i++) { //取出bean MifareBean bean = beans.get(i); if (bean == null) return null; //迭代bean中的信息,进行处理 String[] _tmp = bean.getDatas(); if (_tmp != null) { for (int j = 0; j < _tmp.length; j++) { if (i == beans.size() - 1 && j == (_tmp.length - 1)) { //结尾,无需换行 sb.append(_tmp[j]); } else { sb.append(_tmp[j]).append("\n"); } } } else { return null; } } return sb.toString().split("\n"); } /* * 将数组分散为M1数据! * */ public static MifareBean[] mergeDatas(String[] datas) { //块数量! int blockCount = datas.length; ArrayList rets = new ArrayList<>(16); for (int i = 0; i < blockCount; ) { MifareBean bean = new MifareBean(); //获取当前的扇区 int sector = MifareUtils.blockToSector(i); bean.setSector(sector); //获取当前的块数量统计! int blockCountInSecrot = MifareUtils.getBlockCountInSector(sector); String[] dataArray = new String[blockCountInSecrot]; //进行迭代添加! for (int j = i, k = 0; j < blockCountInSecrot; ++j, ++k) { dataArray[i] = datas[j]; } //设置进去数据当中! bean.setDatas(dataArray); rets.add(bean); //i值自增块数量! i += blockCountInSecrot; } return rets.toArray(new MifareBean[0]); } // 是否整个块都是零! public static boolean isBlockAllZero(String block) { if (block == null) return true; return block.matches("[0]{32}"); } // 是否有密钥可用! public static boolean isAnyOneKeyAvailable(M1KeyBean[] keyBeans) { for (M1KeyBean bean : keyBeans) { return isKeyFormat(bean.getKeyA()) || isKeyFormat(bean.getKeyB()); } return false; } //实现合并读取结果 public static MifareBean mergeBean(MifareBean aBean, MifareBean bBean) { MifareBean ret; //判断某个bean是否可用 if (aBean == null || bBean == null) { if (aBean != null) { return aBean; } if (bBean != null) { return bBean; } return getEmptyM1Bean(0); } //初始化结果bean ret = new MifareBean(bBean.getSector()); //否则将数据进行对比合并 String[] aBeanDatas = aBean.getDatas(); if (aBeanDatas == null) { aBean = getEmptyM1Bean(aBean.getSector()); aBeanDatas = aBean.getDatas(); } int last = aBeanDatas.length - 1; //建立数据保存数组 String[] datas = new String[aBean.getDatas().length]; //取出块数据 String[] tmpA = aBeanDatas; String[] tmpB = bBean.getDatas() == null ? getEmptyM1Bean(bBean.getSector()).getDatas() : bBean.getDatas(); for (int i = 0; i <= last; ++i) { //先判断两组块数据是否相同 if (tmpA[i].equals(tmpB[i])) { //相同则取任意一个合并到ret中 datas[i] = tmpA[i]; } else { // 修复某些块异常也被用来处理的问题 if (!isValidBlockData(tmpA[i]) && isValidBlockData(tmpB[i])) { datas[i] = tmpB[i]; continue; } if (!isValidBlockData(tmpB[i]) && isValidBlockData(tmpA[i])) { datas[i] = tmpA[i]; continue; } // 判断数据有效性 if (i != last) { //否则是否全都是0,如果全部为零则优先考虑其他的情况! boolean isADataAllZero = isBlockAllZero(tmpA[i]); boolean isBDataAllZero = isBlockAllZero(tmpB[i]); // 两者都是零,说明数据真的是 if (isADataAllZero && isBDataAllZero) { // 开始选择用正确的数据 if (DumpUtils.isValidBlockData(tmpB[i])) { datas[i] = tmpB[i]; } else if (DumpUtils.isValidBlockData(tmpA[i])) { datas[i] = tmpA[i]; } else { //不符合数据规范,跳过操作 datas[i] = DumpUtils.BLANK_DATA; } } else { // 开始选择用正确的数据 // 如果AB数据有一个非0,则用那个 if (DumpUtils.isValidBlockData(tmpB[i]) && isADataAllZero) { datas[i] = tmpB[i]; } else if (DumpUtils.isValidBlockData(tmpA[i]) && isBDataAllZero) { datas[i] = tmpA[i]; } else { //不符合数据规范,跳过操作 datas[i] = DumpUtils.BLANK_DATA; } } } else { //判断bBean的块数据非空 //并且aBean中密钥B不可读的情况下 //才能将bBean中的数据传入到ret中 //得到控制位 boolean isADataTrialerAllDefault = tmpA[i].equalsIgnoreCase(BLANK_TRAIL_BLOCK); boolean isBDataTrialerAllDefault = tmpB[i].equalsIgnoreCase(BLANK_TRAIL_BLOCK); // 两者皆为默认,暂且认定为真的数据就是这个! if (isADataTrialerAllDefault && isBDataTrialerAllDefault) { // 随便填充一个就行了! datas[i] = tmpA[i]; } else { if (DumpUtils.isValidBlockData(tmpB[i]) && isADataTrialerAllDefault) { //有效的尾部块 datas[i] = tmpB[i]; } else if (DumpUtils.isValidBlockData(tmpA[i]) && isBDataTrialerAllDefault) { datas[i] = tmpA[i]; } else { //有效的尾部块 // 置空尾部块! datas[i] = DumpUtils.BLANK_TRAIL_BLOCK; } } } } } ret.setDatas(datas); return ret; } //把密钥更新进数据组中 public static void updateTrailer(MifareBean dB, M1KeyBean kB) { if (dB == null || kB == null) return; if (dB.getSector() != kB.getSector()) return; String[] datas = dB.getDatas(); if (datas == null) { dB = getEmptyM1Bean(dB.getSector()); datas = dB.getDatas(); } //有些时候,我们可以验证成功,但是无法读取操作数据块,此时,我们还是可以将密钥更新进数据集中 int lastIndex = datas.length - 1; String last = dB.getDatas()[lastIndex]; if (last.length() != 32) { //修复越界异常,我们进行判断并且跳过操作! /*Log.d(LOG_TAG, "****************"); Log.d(LOG_TAG, "Update position: " + dB.getSector()); Log.d(LOG_TAG, "Invalid content: " + last); Log.d(LOG_TAG, "Invalid length: " + last.length()); Log.d(LOG_TAG, "****************");*/ return; } //截取到最后一个块(尾部块),然后把有效密钥更新进去 String _kA = kB.getKeyA().replaceAll("\\*", "F"); String _kB = kB.getKeyB().replaceAll("\\*", "F"); //更新密钥进去 last = _kA + last.substring(12, 32); last = last.substring(0, 20) + _kB; dB.getDatas()[dB.getDatas().length - 1] = last; } /* * 判断是否是unix类系统 * */ public static boolean isUnixLFFormat(String str) { return !str.contains("\r\n"); } /* * 获取对应的类系统的换行符 * */ public static String getSystemLF(String sysClz) { String ret = "\r\n"; switch (sysClz) { case "windows": break; case "unix": ret = "\n"; break; } return ret; } public static MifareBean[] getSectorFromArray(String[] contents) { ArrayList retList = new ArrayList<>(); if (contents != null) { // 类型判断! if (DumpUtils.isBlocksValids(contents)) { //1K卡,2K卡! MifareBean bean = null; String[] _tmps = null; for (int i = 0; i < contents.length; ) { // 进行尾部的判断,如果是256的扇区的话我们需要进行偏移量的重新设置! int count = MifareUtils.getBlockCountInSector(MifareUtils.blockToSector(i)); if (DumpUtils.isHeader(i)) { //在首部块! bean = new MifareBean(); _tmps = new String[count]; } //进行偏移拷贝! if (_tmps != null) System.arraycopy(contents, i, _tmps, 0, count); if (bean != null) { bean.setDatas(_tmps); bean.setSector(DumpUtils.toSector(i)); } //结束(将结果添加至集合中) retList.add(bean); i += count; } } boolean isUL = contents.length == 16 || contents.length == 20 || contents.length == 41 || contents.length == 44 || contents.length == 48; if (isUL) { for (int i = 0; i < contents.length; ++i) { LogUtils.d("数据打印测试: " + contents[i]); MifareBean mifareBean = new MifareBean(i, new String[]{contents[i]}); retList.add(mifareBean); } } } return retList.toArray(new MifareBean[0]); } public static boolean isDump(File file) { if (file == null) return false; // 大于50k的数据也认为不是正确的数据! if (file.length() > MAX_DUMP_FILE_SIZE) return false; if (file.exists() && file.isFile()) { try { return getType(FileUtils.readBytes(file)) != TYPE_NOT; } catch (IOException e) { e.printStackTrace(); return false; } } return false; } public static boolean isDump(byte[] data) { if (data == null) return false; // 大于50k的数据也认为不是正确的数据! if (data.length > MAX_DUMP_FILE_SIZE) return false; return getType(data) != TYPE_NOT; } public static boolean isDump(Uri uri) { byte[] data = readDump(uri); if (data == null) return false; return isDump(data); } public static byte[] readDump(Uri uri) { try { return FileUtils.readBytes(uri, -1, MAX_DUMP_FILE_SIZE, -1); } catch (IOException e) { e.printStackTrace(); } return null; } public static MifareBean[] readDumpBeans(Uri uri) { byte[] hexDat = readDump(uri); if (hexDat == null) return null; String decorate = decorate(hexDat); if (decorate == null) return null; return getSectorFromArray(splitDump(decorate)); } public static boolean hasUltralightNewHeader(byte[] bytes) { if (bytes == null) return false; if (bytes.length % 4 != 0 || bytes.length <= NEWPREFIXLENGTH) return false; // tbo should be ZERO if (bytes[8] != 0x00 || bytes[9] != 0x00) return false; // tbo1 should be ZERO if (bytes[10] != 0x00) return false; // pages count must be equals to pages in header int maxPage = (bytes.length - NEWPREFIXLENGTH) / 4 - 1; return maxPage == bytes[11]; } public static boolean hasUltralightHeader(byte[] bytes) { if (bytes == null) return false; // empty header byte[] empty_header = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (HexUtil.sequenceEqual(bytes, empty_header, empty_header.length)) { return true; } // detect mfu header. If probability of magic values is more than 50%, assume file has header. double probability = 0d; // first two bytes of version should be 0x00, 0x04 if (bytes[0] == 0x00 && bytes[1] == 0x04) probability += 0.25; // tbo should be ZERO if (bytes[8] == 0x00 && bytes[9] == 0x00) probability += 0.15; // tbo1 should be ZERO if (bytes[15] == 0x00) probability += 0.15; // tearing is normally 0xBD if (bytes[10] == (byte) 0xBD || bytes[11] == (byte) 0xBD || bytes[12] == (byte) 0xBD) probability += 0.35; return (probability >= 0.50); } }