实现龙卷风收音机客户端电台数据解密算法 算法F#
程序员文章站
2022-07-15 08:45:05
...
最新说明:自7月份始,文章公布后,Cradio官方更新了加密算法,故以下实现只对7月份之前的版本有效.关于最新的电台数据,目前只能通过js的方式获取,而相关的除电台名称,地址之外信息还需另外搜集和整理.在此,请诸位自行决定.(再次声明:js获取数据方式因可能对Cradio官方服务器产生不良影响,在此不再公布.)
以下是解密算法的java实现,已通过验证:
以下是解密算法的java实现,已通过验证:
public class Test { public static void main(String[] args) { RadioParser parser = new RadioParser("f:/data.idp", "f:/data.dat"); for(RadioSegement segement : parser) { System.out.println(segement.getRadioInfo()); System.out.println("\n\n"); } } } public class RadioSegement { private int startAddr; private int endAddr; private String info; private RadioInfo radioInfo; public RadioSegement(int startAddr, int endAddr) { this.startAddr = startAddr; this.endAddr = endAddr; } public int getStartAddr() { return startAddr; } public int getEndAddr() { return endAddr; } public int length() { return endAddr - startAddr; } void setInfo(String info) { this.info = info; } public String getInfo() { return info; } public RadioInfo getRadioInfo() { if(radioInfo == null) { radioInfo = new RadioInfo(this); } return radioInfo; } public String toString() { return "startAddr: " + startAddr + ", endAddr: " + endAddr + ", info: " + info; } } public class RadioInfo { private String[] data; private static int index = 0; private final static int INDEX_NAME = index++; private final static int INDEX_TYPE = index++; private final static int INDEX_COUNTRY = index++; private final static int INDEX_PROVINCE = index++; private final static int INDEX_CITY = index++; private final static int INDEX_LANGUAGE = index++; private final static int INDEX_FORMAT = index++; private final static int INDEX_VALIDITY = index++; private final static int INDEX_SPEED = index++; private final static int INDEX_FREQUENCY = index++; static { index++; } private final static int INDEX_HOME_URL = index++; private final static int INDEX_PROGRAMME_URL = index++; private final static int INDEX_BROADCAST_URL = index++; RadioInfo(RadioSegement segement) { data = segement.getInfo().split("\t", -1); if(data.length != 14) { return; } } public String getName() { return data[INDEX_NAME]; } public String getType() { return data[INDEX_TYPE]; } public String getCountry() { return data[INDEX_COUNTRY]; } public String getProvince() { return data[INDEX_PROVINCE]; } public String getCity() { return data[INDEX_CITY]; } public String getLanguage() { return data[INDEX_LANGUAGE]; } public String getFormat() { return data[INDEX_FORMAT]; } public String getValidity() { return data[INDEX_VALIDITY]; } public String getSpeed() { return data[INDEX_SPEED]; } public String getFrequency() { return data[INDEX_FREQUENCY]; } public String getHomeUrl() { return data[INDEX_HOME_URL]; } public String getProgrammeUrl() { return data[INDEX_PROGRAMME_URL]; } public String getBroadcastUrl() { return data[INDEX_BROADCAST_URL]; } public String toString() { return new StringBuilder() .append("\n名称: ").append(getName()) .append("\n类型: ").append(getType()) .append("\n国家: ").append(getCountry()) .append("\n省份: ").append(getProvince()) .append("\n城市: ").append(getCity()) .append("\n语言: ").append(getLanguage()) .append("\n格式: ").append(getFormat()) .append("\n有效性: ").append(getValidity()) .append("\n速率: ").append(getSpeed()) .append("\n频率: ").append(getFrequency()) .append("\n主页: ").append(getHomeUrl()) .append("\n节目表: ").append(getProgrammeUrl()) .append("\n广播地址: ").append(getBroadcastUrl()) .toString(); } } import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.bao.util.io.FileUtil; import com.bao.util.lang.ByteUtil; public class RadioParser implements Iterable<RadioSegement>{ /** * 解码字符 */ private final static char[] xorChar = "jin_cr".toCharArray(); /** * 地址文件 */ private File addrFile; /** * 数据文件 */ private File dataFile; /** * 数据段 */ private List<RadioSegement> segements = null; public RadioParser(String addrFilename, String dataFilename) { this.addrFile = new File(addrFilename); this.dataFile = new File(dataFilename); } @Override public Iterator<RadioSegement> iterator() { if(segements == null) { initAddrList(); initRadioData(); } return segements.iterator(); } /** * 解析地址 */ private void initAddrList() { segements = new LinkedList<RadioSegement>(); BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(addrFile)); int maxLen = (int)dataFile.length(); byte[] bys = new byte[4]; for(int start = 0; bis.read(bys) != -1; ) { int end = ByteUtil.bytes2IntLE(bys); if(end == 0) { continue; } if(end > maxLen) { break; } segements.add(new RadioSegement(start, end)); start = end; } }catch(IOException e) { e.printStackTrace(); }finally{ try { FileUtil.closeIO(bis); } catch (IOException e) { e.printStackTrace(); } } } /** * 解析数据段 */ private void initRadioData() { BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(dataFile)); for(RadioSegement segement : segements) { byte[] bys = new byte[segement.length()]; bis.read(bys); segement.setInfo(parseBytes(bys)); } }catch(IOException e) { e.printStackTrace(); }finally{ try { FileUtil.closeIO(bis); } catch (IOException e) { e.printStackTrace(); } } } /** * 解码 * @param bys * @return * @throws UnsupportedEncodingException */ private static String parseBytes(byte[] bys) throws UnsupportedEncodingException { if(bys == null) { return null; } if(bys.length == 0) { return ""; } int offset = 0; while(offset < bys.length && bys[offset++] != 0x09); for(int i = offset, k = 0; i < bys.length; i++, k++) { bys[i] = (byte)(bys[i] ^ xorChar[k % xorChar.length]); } return new String(bys, offset, bys.length - offset - 2, "gbk"); } } import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * 读取文件工具类 * * @author bao110908 * @since 2008-12-14 */ public class FileUtil { /** * 行分隔符 */ public final static String LINE_SEPARATOR = System.getProperty("line.separator"); /** * 建一个目录 * @param file */ public static boolean createDirectory(File file) { if(file.exists()) { return true; } return file.mkdirs(); } public static boolean createDirectory(String dirname) { return createDirectory(new File(dirname)); } /** * 从文件名中读取文件 * @param filename * @return * @throws IOException */ public static String readFile2String(String filename) throws IOException { return readFile2String(new File(filename)); } /** * 从 File 对象中读取文件 * @param file * @return * @throws IOException */ public static String readFile2String(File file) throws IOException { if((file == null) || !file.exists() || file.isDirectory()) { return null; } return readInputStream2String(new FileInputStream(file)); } /** * 使用系统默认字符集读取二进制流 * @param is * @return * @throws IOException */ public static String readInputStream2String(InputStream is) throws IOException { return readInputStream2String(is, Charset.defaultCharset().name()); } /** * 使用指定编码读取二进制流 * @param is * @param charset * @return * @throws IOException */ public static String readInputStream2String(InputStream is, String charset) throws IOException { BufferedReader br = null; StringBuilder sb = new StringBuilder(); try { br = new BufferedReader(new InputStreamReader(is, charset)); for(String str = null; (str = br.readLine()) != null; ) { sb.append(str).append(LINE_SEPARATOR); } } finally { closeIO(br); } return sb.toString().trim(); } public static List<String> readFile2List(String filename) throws IOException { return readFile2List(new File(filename)); } /** * 将文件读取成为 List,List 中的每一个元素是文件中的一行字符串 * @param file * @return * @throws IOException */ public static List<String> readFile2List(File file) throws IOException { if((file == null) || !file.exists() || file.isDirectory()) { return null; } BufferedReader br = null; List<String> list = new ArrayList<String>(); try { br = new BufferedReader(new FileReader(file)); for(String str = null; (str = br.readLine()) != null; ) { list.add(str); } } finally { closeIO(br); } return list; } public static void writeString2File(String str, String filename) throws IOException { writeString2File(str, new File(filename)); } public static void writeString2File(String str, File file) throws IOException { BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(file)); bw.write(str); } finally { closeIO(bw); } } public static void writeList2File(List<String> list, String filename) throws IOException { writeList2File(list, new File(filename), LINE_SEPARATOR); } public static void writeList2File(List<String> list, File file) throws IOException { writeList2File(list, file, LINE_SEPARATOR); } public static void writeList2File(List<String> list, String filename, String lineSeparator) throws IOException { writeList2File(list, new File(filename), lineSeparator); } /** * 以 List 中的每一个元素作为写入文件中的一行 * @param list * 元素集 * @param file * 文件 * @param lineSeparator * 每个元素写入文件时的分隔符 * @throws IOException */ public static void writeList2File(List<String> list, File file, String lineSeparator) throws IOException { StringBuffer sb = new StringBuffer(); for(int i = 0, k = list.size(); i < k; i++) { if(i > 0) { sb.append(lineSeparator); } sb.append(list.get(i)); } writeString2File(sb.toString(), file); } /** * 关闭 IO 流 * @param io * @throws IOException */ public static void closeIO(Closeable io) throws IOException { if(io != null) { io.close(); } } } /** * 字节工具类 * @author bao110908 * @since 2009-03-07 */ public class ByteUtil { private final static char[] HEX = "0123456789abcdef".toCharArray(); /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,每个字节间采用 * 一个空格分隔<br /> * 采用实现较为高效的 bytes2Hex 方法 * @param bys 需要转换成 16 进制的字节数组 * @return * @deprecated */ public static String bytes2Hex1(byte[] bys) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < bys.length; i++) { if(i > 0) { sb.append(" "); } sb.append(HEX[bys[i] >> 4 & 0xf]); sb.append(HEX[bys[i] & 0xf]); } return sb.toString(); } /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,每个字节间采用 * 一个空格分隔 * @param bys 需要转换成 16 进制的字节数组 * @return */ public static String bytes2HexSpace(byte[] bys) { char[] chs = new char[bys.length * 2 + bys.length - 1]; for(int i = 0, offset = 0; i < bys.length; i++) { if(i > 0) { chs[offset++] = ' '; } chs[offset++] = HEX[bys[i] >> 4 & 0xf]; chs[offset++] = HEX[bys[i] & 0xf]; } return new String(chs); } /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示 * * @param bys 需要转换成 16 进制的字节数组 * @return */ public static String bytes2Hex(byte[] bys) { char[] chs = new char[bys.length * 2]; for(int i = 0, offset = 0; i < bys.length; i++) { chs[offset++] = HEX[bys[i] >> 4 & 0xf]; chs[offset++] = HEX[bys[i] & 0xf]; } return new String(chs); } /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,字节间没有分隔 * 符。<br /> * 采用实现较为高效的 bytes2Hex 方法 * @param bys 需要转换成 16 进制的字节数组 * @return * @deprecated */ public static String bytes2Hex2(byte[] bys) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < bys.length; i++) { sb.append(HEX[bys[i] >> 4 & 0xf]); sb.append(HEX[bys[i] & 0xf]); } return sb.toString(); } public static byte[] int2BytesBE(int num) { byte[] bys = new byte[Integer.SIZE / Byte.SIZE]; for(int i = 0, k = bys.length; i < k; i++) { bys[i] = (byte)(num >>> ((k - 1 - i) * Byte.SIZE) & 0xff); } return bys; } public static byte[] int2BytesLE(int num) { return int2BytesBE(Integer.reverseBytes(num)); } /** * 采用 Big-Endian 方式将 long 数据转为 byte 数组 * * @param num * @return 转为 Big-Endian 方式的 byte 数组 */ public static byte[] long2BytesBE(long num) { byte[] bys = new byte[Long.SIZE / Byte.SIZE]; for(int i = 0, k = bys.length; i < k; i++) { bys[i] = (byte)(num >>> ((k - 1 - i) * Byte.SIZE) & 0xff); } return bys; } /** * 采用 Little-Endian 方式将 long 数据转为 byte 数组 * * @param num * @return 转为 Little-Endian 方式的 byte 数组 */ public static byte[] long2BytesLE(long num) { return long2BytesBE(Long.reverseBytes(num)); } /** * 将 Little-Endian 的字节数组转为 int 类型的数据<br /> * Little-Endian 表示高位字节在低位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 LE 方式表示的 int 数值 */ public static int bytes2IntLE(byte[] bys, int start, int len) { return bytes2Int(bys, start, len, false); } public static int bytes2IntLE(byte[] bys) { return bytes2Int(bys, 0, bys.length, false); } /** * 将 Big-Endian 的字节数组转为 int 类型的数据<br /> * Big-Endian 表示高位字节在高位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 BE 方式表示的 int 数值 */ public static int bytes2IntBE(byte[] bys, int start, int len) { return bytes2Int(bys, start, len, true); } private static int bytes2Int(byte[] bys, int start, int len, boolean isBigEndian) { int n = 0; for(int i = start, k = start + len % (Integer.SIZE / Byte.SIZE + 1); i < k; i++) { n |= (bys[i] & 0xff) << ((isBigEndian ? (k - i - 1) : i) * Byte.SIZE); } return n; } /** * 将 Little-Endian 的字节数组转为 long 类型的数据<br /> * Little-Endian 表示高位字节在低位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 LE 方式表示的 long 数值 */ public static long bytes2LongLE(byte[] bys, int start, int len) { return bytes2Long(bys, start, len, false); } public static long bytes2LongLE(byte[] bys) { return bytes2Long(bys, 0, bys.length, false); } /** * 将 Big-Endian 的字节数组转为 long 类型的数据<br /> * Big-Endian 表示高位字节在高位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 BE 方式表示的 long 数值 */ public static long bytes2LongBE(byte[] bys, int start, int len) { return bytes2Long(bys, start, len, true); } public static long bytes2LongBE(byte[] bys) { return bytes2Long(bys, 0, bys.length, true); } private static long bytes2Long(byte[] bys, int start, int len, boolean isBigEndian) { long n = 0L; for(int i = start, k = start + len % (Long.SIZE / Byte.SIZE + 1); i < k; i++) { n |= (bys[i] & 0xffL) << ((isBigEndian ? (k - i - 1) : i) * Byte.SIZE); } return n; } }
上一篇: Android系统JNI解决上层直接调用Linux内核层
下一篇: 优先队列与堆的解析