`
fuyun
  • 浏览: 51620 次
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

实现龙卷风收音机客户端电台数据解密算法

阅读更多
最新说明:自7月份始,文章公布后,Cradio官方更新了加密算法,故以下实现只对7月份之前的版本有效.关于最新的电台数据,目前只能通过js的方式获取,而相关的除电台名称,地址之外信息还需另外搜集和整理.在此,请诸位自行决定.(再次声明:js获取数据方式因可能对Cradio官方服务器产生不良影响,在此不再公布.)
以下是解密算法的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;
    }
}
分享到:
评论
6 楼 fuyun 2009-09-03  
fjlyxx 写道
很好的软件  上班听听  就是现在的广播广告太多了.. 评书类节目太少了  各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢

ps下,我现在一般听怀旧金曲,都市流行和中国之声,经济之声,还有几个地方电台.关于musicRadio都很少听了.确实,广告太多了.而且还太频繁了,半个小时一次,听着不爽.
5 楼 fuyun 2009-09-03  
fjlyxx 写道
很好的软件  上班听听  就是现在的广播广告太多了.. 评书类节目太少了  各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢

电台倒是全部都有了(就是fifm和cradio上面的全部电台数据),但不可能全部都收听过来,如果你需要的话,我可以把你需要的分类进行筛选一下发给你.呵呵~
4 楼 fjlyxx 2009-09-01  
很好的软件  上班听听  就是现在的广播广告太多了.. 评书类节目太少了  各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢
3 楼 raomengwen 2009-09-01  
算法很好,但是看不懂,说明太少了
2 楼 fuyun 2009-07-02  
zhiblin 写道
楼主能不能多一点文字说明一下,毕竟是算法

算法的说明和描述请参看上一篇文章.这里不再重复说明!
1 楼 zhiblin 2009-07-02  
楼主能不能多一点文字说明一下,毕竟是算法

相关推荐

    龙卷风收音机

    【龙卷风收音机】是一款流行的网络收音机应用,以其丰富的电台资源和便捷的使用体验深受用户喜爱。在这款软件中,`.data` 文件通常用于存储应用程序的配置信息、资源或者加密的数据,以便保护应用程序的核心内容不被...

    龙卷风收音机(简单实用)

    龙卷风收音机通过优化的数据压缩技术和高效的流媒体处理,确保了即使在网络环境不理想的情况下,也能流畅播放电台节目,减少了缓冲和卡顿的情况,让用户沉浸在连续无断的音频享受中。 其次,电台资源的丰富性是龙卷...

    最新最好用的龙卷风网络收音机(精藏)

    总结来说,龙卷风网络收音机凭借其最新的版本、丰富的皮肤、全面的功能、实时更新的数据以及优秀的休闲娱乐属性,成为了一款值得信赖的网络收音机应用。无论是对于追求新鲜资讯的新闻迷,还是热爱音乐的发烧友,或者...

    龙卷风网络收音机7.6版_龙卷风网络收音机7.6版_

    龙卷风网络收音机7.6版是一款专为此趋势打造的软件,它将全球各地的广播电台汇聚一堂,让用户在指尖就能畅享丰富的音频内容。 一、软件概述 龙卷风网络收音机7.6版,正如其名,以其强大的功能和流畅的体验,像一股...

    龙卷风收音机 3.8.apk

    华为ep680上很好用的收音机,ep680由于系统版本低,可以用的软件很少,这个软件非常流畅,收到台也很多,速度很快

    龙卷风网络收音机v3.0.1.3(安装包、绿色包和皮肤)

    总结,龙卷风网络收音机v3.0.1.3以其丰富的电台资源、便捷的操作方式和个性化的皮肤设计,为用户提供了一站式的网络广播体验。无论是安装包还是绿色包,都能满足不同用户的需求,而多样化的皮肤则让软件更具吸引力。...

    龙卷风网络收音机 龙卷风网络收音机

    "龙卷风网络收音机"是一款专为用户设计的在线广播软件,它集成了丰富的网络电台资源,让用户能够随时随地享受各种音乐、新闻、谈话节目等。这款软件以其便捷的操作和高质量的音频流服务,深受广大听众喜爱。下面将...

    电脑龙卷风收音机PC版

    在线收听广播

    龙卷风收音机3.35.1204.2201

    “龙卷风收音机”拥有全球范围内的海量电台频道,涵盖各种类型的电台节目,包括新闻、音乐、体育、谈话节目等,满足不同用户的个性化需求。用户可以根据自己的喜好选择国内外的电台,享受无国界的音频盛宴。 二、...

    龙卷风网络收音机3.0

    "龙卷风网络收音机3.0"是一款广受欢迎的网络广播软件,它以其出色的音质、丰富的电台资源和用户友好的界面赢得了用户的青睐。作为一款强大的在线音频播放工具,它不仅支持实时收听全球各地的广播电台,还具备录音、...

    龙卷风网络收音机

    "龙卷风网络收音机"是一款专为网络广播爱好者设计的应用程序,它允许用户在全球范围内收听各种电台节目,只需接入互联网即可。这款软件打破了传统收音机的地域限制,让用户可以随时随地享受来自世界各地的声音。 在...

    龙卷风网络收音机 龙卷风网络收音机 全国的电台任你听

    龙卷风网络收音机 在线听广播 全国的电台任你听 收音机可以扔掉了 这个是最新版

    龙卷风收音机,绝对能用!

    总结来说,【龙卷风收音机】凭借其丰富的电台资源、简便的操作方式、稳定的技术支持以及免费的使用体验,成为了许多用户心目中的首选网络收音机工具。无论是想了解国内外新闻,还是想在闲暇时聆听美妙的音乐,它都能...

Global site tag (gtag.js) - Google Analytics