前言

不知道各位看官是否有过类似的经历。好不容易找到一个电影的种子文件,想用百度云的离线下载功能去下载文件,却被百度云无情提示“离线文件因含有违规内容被系统屏蔽无法下载”!假设有这么一个场景,比如最近有一部电影《速度与激情7》很火,百度为了保护版权方的利益,对于凡是种子文件中包含了关键字“速度与激情7”的,一律提示包含有违规内容,禁止下载。

于是乎,后来就有了通过“BEncode Editor”这款工具来修改其中一些敏感性文字的方法来修改种子文件,以达到下载的目的。比如在上一个例子中,我们把种子文件中凡是包含有“速度与激情7”的文字全部修改为“电影7”,然后保存种子文件再下载的话,就不会被百度屏蔽了。修改的方法如下图所示。

本来故事到此应该就已经结束了。但是后来有两点促使了我编写一个自动化的种子文件修改工具。第一点是因为有的种子文件包含的文件实在太多了,每一个文件的文件名都改过去很麻烦,第二点是因为能用电脑解决的重复性劳动我就懒得用手解决。所以才有了下面的故事。

种子文件编码格式

BT种子文件使用了一种叫bencoding的编码方法来保存数据[1]。

编码规则如下:
strings(字符串)编码为:<字符串长度>:<字符串>
例如: 4:test 表示为字符串test
4:例子 表示为字符串“例子”
字符串长度单位为字节
没开始或结束标记

integers(整数)编码为:i<整数>e
开始标记i,结束标记为e
例如: i1234e 表示为整数1234
i-1234e 表示为整数-1234
整数没有大小限制
i0e 表示为整数0
i-0e 为非法
以0开头的为非法如: i01234e 为非法

lists(列表)编码为:l<bencoding编码类型>e
开始标记为l,结束标记为e
列表里可以包含任何bencoding编码类型,包括整数,字符串,列表,字典。
例如: l4:test5abcdee 表示为二个字符串[test,abcde]

dictionaries(字典)编码为d<bencoding字符串><bencoding编码类型>e
开始标记为d,结束标记为e
关键字必须为bencoding字符串
值可以为任何bencoding编码类型
例如: d3:agei20ee 表示为{age=20}
d4:path3:C:/8:filename8:test.txte 表示为{path=C:/,filename=test.txt}

节点的定义

为了对BT文件有一个直观的印象,我们还是以速度与激情7这个BT文件为例,从图中为各位看官做一下介绍。仔细观察下图,我们发现在图中的节点无非是三种类型,第一种是根节点,第二种是键值对节点(字典也是一个特殊的键值对节点,其键为名字,而值为其所有子节点),第三种列表节点。

简单的BT文件解析器

可以看到bencoding编码中的四种类型都有一个标识头,比如整数类型以'i'开始,string类型以数字开始。利用这一特性,对于每一个类型,我们先尝试读一个字符,并根据读入的字符判断读入的是什么类型,如‘i’为整形,'d'为字典,'l'为列表而剩下的数字则为字符串。

那么接下来的思路就非常清晰了,我们需要四个方法来分别解析数字,字符串,字典和列表。其中数字和字符串类型只用于表示值,而不能作为容器;列表和字典类型都可以作为容器,故还有一个parent参数,用于向父节点添加子节点。

1 private byte[] AnalysisInteger(); // 解析整形,由于会超出Int32的表示范围,用byte[]代替
2
3 private byte[] AnalysisString(); // 解析字符串,考虑到编码问题,这里用byte[]表示
4
5 private void AnalysisList(IBNode parent); // 解析列表
6
7 private void AnalysisDictionary(IBNode parent); // 解析字典

由于到BT文件是树状结构的,这里我们使用递归来实现对BT文件的解析。可以确定的,BT文件一定是以一个字典类型开始的,所以我们先调用AnalysisDictionary方法,并把参数根节点传给它。之后在该方法中通过读入下一个字符来判断是什么类型,并调用相应的方法来解析该类型,而相应的方法又通过相同的方法继续调用另外的方法,如此循环,直到解析完毕,这也正是递归的思想。下边就是我实现的一个简单的BT文件解析器,返回的是一个IBNode类型的根节点。

  1 /// <summary>
  2 /// 一个最简单的BT文件分析器
  3 /// </summary>
  4 class CommonAnalyser:IAnalyser
  5 {
  6     private byte[] torrentStream = null;
  7     private int index = 0;
  8     private List<IBNode> _bNodeList = null;
  9     private BNodeFactory _bNodeFactory = null;
 10
 11     public CommonAnalyser()
 12     {
 13         torrentStream = null;
 14         _bNodeList = new List<IBNode>();
 15         _bNodeFactory = new BNodeFactory(_bNodeList);
 16         index = 0;
 17     }
 18
 19     public IBNode Analysis(byte[] torrentStream)
 20     {
 21         // 清空上一次处理的信息
 22         _bNodeList = new List<IBNode>();
 23         _bNodeFactory = new BNodeFactory(_bNodeList);
 24         index = 0;
 25
 26         this.torrentStream = torrentStream;
 27         // bt文件一定是一个字典开始的
 28
 29         DictNode rootNode = (DictNode)_bNodeFactory.GetBNode('d');
 30         AnalysisDictionary(rootNode);
 31         return rootNode;
 32     }
 33
 34     /// <summary>
 35     /// 取出当前字符,并指针后移
 36     /// </summary>
 37     /// <returns></returns>
 38     private char GetCurrentCharMove()
 39     {
 40         return (char)torrentStream[index++];
 41     }
 42
 43     /// <summary>
 44     /// 取出当前字符,并指针不后移
 45     /// </summary>
 46     /// <returns></returns>
 47     private char GetCurrentChar()
 48     {
 49         return (char)torrentStream[index];
 50     }
 51
 52     private void AnalysisDictionary(IBNode parent)
 53     {
 54         // 字典一定是d开始的
 55         if (GetCurrentCharMove() != 'd')
 56             return;
 57
 58         // 循环分析键值对
 59         do
 60         {
 61             KeyValueNode keyValueNode = (KeyValueNode)_bNodeFactory.GetBNode('k');
 62             // 键值对,键一定是string
 63             keyValueNode.SetKey(AnalysisString());
 64             // 值
 65             switch (GetCurrentChar())
 66             {
 67                 case 'i': // 数字
 68                     keyValueNode.SetValue(AnalysisInteger());
 69                     keyValueNode.ValueType = 'i';
 70                     break;
 71                 case 'd': // 字典
 72                     AnalysisDictionary(keyValueNode);
 73                     keyValueNode.ValueType = 'd';
 74                     break;
 75                 case 'l': // 列表
 76                     AnalysisList(keyValueNode);
 77                     keyValueNode.ValueType = 'l';
 78                     break;
 79                 default: // 字符串
 80                     keyValueNode.SetValue(AnalysisString());
 81                     keyValueNode.ValueType = 's';
 82                     break;
 83             }
 84             parent.Child.Add(keyValueNode);
 85         } while (GetCurrentChar() != 'e');
 86         GetCurrentCharMove();
 87     }
 88
 89     private void AnalysisList(IBNode parent)
 90     {
 91         // 列表一定是l开始的
 92         if (GetCurrentCharMove() != 'l')
 93             return;
 94
 95         int count = 0;
 96         // 循环读入列表项
 97         do
 98         {
 99             ListItemNode listItemNode = (ListItemNode)_bNodeFactory.GetBNode('l');
100             switch (GetCurrentChar())
101             {
102                 case 'i': // 数字
103                     listItemNode.SetValue(AnalysisInteger());
104                     listItemNode.ValueType = 'i';
105                     break;
106                 case 'd': // 字典
107                     AnalysisDictionary(listItemNode);
108                     listItemNode.ValueType = 'd';
109                     break;
110                 case 'l': // 列表
111                     AnalysisList(listItemNode);
112                     listItemNode.ValueType = 'l';
113                     break;
114                 default:
115                     listItemNode.SetValue(AnalysisString());
116                     listItemNode.ValueType = 's';
117                     break;
118             }
119             listItemNode.ListIndex = count++;
120             parent.Child.Add(listItemNode);
121         } while (GetCurrentChar() != 'e');
122         GetCurrentCharMove();
123     }
124
125     // 由于有些数字太大,用string来代替int
126     private byte[] AnalysisInteger()
127     {
128         // 数字一定是i开始e结尾的
129         if (GetCurrentCharMove() != 'i')
130             return null;
131
132         //StringBuilder builder = new StringBuilder();
133         List<byte> integerByte = new List<byte>();
134         char currentChar = ' ';
135         while ((currentChar = GetCurrentCharMove()) != 'e')
136         {
137             //builder.Append(currentChar);
138             integerByte.Add((byte)currentChar);
139         }
140
141         return integerByte.ToArray();
142     }
143
144     private byte[] AnalysisString()
145     {
146         char currentChar = GetCurrentCharMove();
147         // 字符串一定是数字开始开始
148         if (currentChar < '0' || currentChar > '9')
149             return null;
150
151         StringBuilder builder = new StringBuilder();
152
153         do
154         {
155             builder.Append(currentChar);
156             currentChar = GetCurrentCharMove();
157         } while (currentChar >= '0' && currentChar <= '9');
158
159         // 中间必须为:
160         if (currentChar != ':')
161             return null;
162
163         int length = Int32.Parse(builder.ToString());
164         byte[] buffer = new byte[length];
165         for (int i = 0; i < length; ++i)
166         {
167             buffer[i] = torrentStream[index++];
168             //builder.Append(GetCurrentCharMove());
169         }
170
171         return buffer;
172     }
173
174     public List<IBNode> bNodeList
175     {
176         get
177         {
178             return _bNodeList;
179         }
180         set
181         {
182             _bNodeList = value;
183         }
184     }
185 }

View Code

显示BT文件树状图

好不容易解析完了,当然要先把它显示出来看是否正确。这里我们仿照“BEncode Editor”这款工具的界面来显示。简单分析一下,其实就是使用了一个TreeView的控件来显示。由于我们解析出来的节点和TreeView控件的节点正好是一一对应的,所以这里也用一个递归就能实现了。

 1 private void ConstructTree(TreeNode tParent, IBNode bParent)
 2 {
 3     tParent.Text = bParent.ToString(); // 这里用了一点格式化的方法,详见完整代码
 4     foreach (IBNode bNode in bParent.Child)
 5     {
 6         TreeNode tNode = new TreeNode();
 7         tNode.Text = bNode.ToString();
 8         tNode.Tag = bNode;
 9         tParent.Nodes.Add(tNode);
10         ConstructTree(tNode, bNode);
11     }
12 }

显示效果就像下面这个样子。已经和上面BT文件修改工具很像了。

修改BT文件

至今为止我们都在做重复的工作,模仿已有的工具,那么接下来就是新的内容了。经过我的仔细观察后发现,百度云离线下载检测的关键词主要为

 { "name", "name.utf-8", "path", "path.utf-8", "comment", "comment.utf-8", "publisher", "publisher-url", "publisher-url.utf-8", "publisher.utf-8"}

这些键后面的值。只要我们把这些后面对应的值改为一些不敏感的词,那么就能躲过百度的审查。

为了把刚学的设计模式用上去,我在之前定义IBNode接口的时候预留了一个方法。

1 /// <summary>
2 /// 接受修改
3 /// </summary>
4 /// <param name="visitor"></param>
5 void Accept(IVisitor visitor);

这里主要用到了设计模式当中访问者模式。现在要修改这些键对用的值,我们只需要遍历一遍所有的节点,并在每一个节点上调用一次Accept方法,让访问者去做修改的工作就可以了。访问者的代码如下,大致思想就是判断键值如果在我们上文提到的那些键值中的其中一个,那么就把其对应的值改为“somename”,相应百度应该不会把somename认为是敏感的词。

 1 /// <summary>
 2 /// 用于修改特定键值对节点的值
 3 /// </summary>
 4 class KeyValueVisitor:IVisitor
 5 {
 6     private string[] tabooString = { "name", "name.utf-8", "path", "path.utf-8", "comment", "comment.utf-8",
 7                                    "publisher", "publisher-url", "publisher-url.utf-8", "publisher.utf-8"};
 8     public void Visit(KeyValueNode keyValueNode)
 9     {
10         string key = keyValueNode.Key;
11         foreach (string name in tabooString)
12         {
13             if (key.Equals(name))
14             {
15                 // 普通键值对
16                 if (keyValueNode.Child.Count == 0)
17                     keyValueNode.SetValue(Encoding.UTF8.GetBytes("somename"));
18                 else // 列表项,通常是文件名
19                 {
20                     // 保留文件名,其余替换为somename
21                     string value = ((ListItemNode)keyValueNode.Child[0]).Value;
22                     int startIndex = value.LastIndexOf(".");
23                     value = String.Format("{0}.{1}", "somename", value.Substring(startIndex+1));
24                     (keyValueNode.Child[0] as ListItemNode).SetValue(Encoding.UTF8.GetBytes(value));
25                 }
26                 break;
27             }
28         }
29     }
30 }

View Code

修改完了之后把BT文件按读取的顺序写回文件就可以了。同样也是一个递归的方法,这里就不再赘述了,至此,BT文件的修改就大功告成了。

小结

除了文中提到的一些功能外,整个小工具还加上了日志记录,批量转换功能,也算是平时闲着的无聊之作吧。

下面回到正题,为什么篇名还要加上福利二字呢?我开始动手写这个修改工具的时候也是没有想到,原来通过还能修改一些动作片的种子文件,从而逃过百度的审查,顺利使用百度云离线。整个工程源码下载见链接部分。至于效果怎么样,谁用谁知道吧^_^

 

链接

种子文件编码格式:http://www.cnblogs.com/hnrainll/archive/2011/07/26/2117423.html

可执行文件(博客园):http://files.cnblogs.com/files/fantacity/BTTool%287.8%29.rar

Github 项目地址: http://github.com/yosef-gao/BTTool

转载于:https://www.cnblogs.com/fantacity/p/4614816.html

[c#][福利]BTTool种子文件修改工具相关推荐

  1. 上古卷轴5json文件修改_永恒之柱2文件修改工具下载-永恒之柱2死火JSON配置文件修改工具下载 --pc6下载站...

    永恒之柱2死火JSON配置文件修改工具,一个用于修改柱子2中JSON格式配置文件的小工具,修改游戏中文件扩展名以bundle结尾的文件以修改游戏. 相关软件软件大小版本说明下载地址 永恒之柱2死火JS ...

  2. linux种子文件制作工具,Centos 制作BT种子并获取BT种子信息

    最近研究了一下linux BT服务器环境的搭建,需要在linux下制作BT种子并获取BT种子信息,整理了一下这个过程: 制作BT种子软件下载地址:http://jaist.dl.sourceforge ...

  3. exe程序文件修改工具有哪些

    1,peid, 2,exescope 3,quickunpack查壳 4,procdump 5,die 6,resource hacker 7,uedit图标修改 8,红盟EXE修改器 9.resto ...

  4. thor修改html,f4thor修改工具[thor规则解包打包] | 贝贝吧

    软件简介 f4thor修改工具一款简单易用thor规则过滤器文件修改工具,可以一键解包打包f4thor文件,支持一键去除.修改权限. 食用说明 文件管理 导入方式 文件导入方式为拖入一个f4thor或 ...

  5. ls mac 显示最近修改日期_Find Any File for Mac(Mac本地文件搜索工具)

    今天小编给大家带来的Find Any File mac是Mac平台上的一款本地文件搜索查找工具,可以让你在本地磁盘上快速搜索.查找你需要的文件.甚至是隐藏文件都逃不出Find Any File mac ...

  6. 文件指纹修改工具 Hash Modifier

    指纹修改工具 前言 介绍 细节 截图示例 代码 测试 前言 众所周知,每个人都有独一无二的指纹.与之类似,在计算机世界里,文件也可以通过一些貌似很复杂的算法(常见的MD5, SHA-1等),得到一个序 ...

  7. 【Python Intelhex- HEX文件修改器工具】

    Python Intelhex- HEX文件修改器工具 提示:文章写完后,目录可 以自动生成,如何生成可参考右边的帮助文档 文章目录 Python Intelhex- HEX文件修改器工具 前言 一. ...

  8. Foldor for Mac(文件夹图标样式修改工具)

    如何修改文件夹图标?推荐Foldor for Mac文件夹图标样式修改工具,多达1000个图标供您选择,支持自定义调色板,需要的朋友快来试试吧! Foldor版安装教程 安装包下载完成后打开,双击.p ...

  9. Folx Pro5下载器堪比IDM的下载工具 支持下载磁力链接和种子文件

    Folx Pro是Mac系统上一款媲美IDM的,不对,它还支持下载磁力链接和种子文件,可以说是十分强大的下载工具! Folx是一款免费的macOS专用的下载器,完全Mac风格的用户界面.提供便捷的下载 ...

最新文章

  1. 树莓派文件服务器nas,树莓派搭建NAS服务器
  2. PreparedStatement批量处理的一个Framework(原创)
  3. php image处理,php 中图像压缩处理类(二)imageutil.php
  4. HtmlUnit爬取Ajax动态生成的网页以及自动调用页面javascript函数
  5. wap开发使用jquery mobile之后页面不加载外部css样式文件/js文件
  6. 项目中对网内IT资产进行管理
  7. centos 环境变量_Centos7:Linux环境变量配置文件
  8. 平板边界层内的流速分布实验
  9. 傅里叶分析——傅里叶级数
  10. mac切换网卡|IP
  11. 利用gflags自定义标志的使用方法
  12. Oracle修改expired状态,Oracle数据库用户账号处于expired状态解决方法
  13. CTO、技术总监、技术经理的区别 互联网技术团队的角色区分
  14. 分享一个开源免费、目前最好的API接口管理平台----eoLinker
  15. ElasticSearch实战(三十六)-Ingest Pipeline 多管道处理器
  16. MS-RTOS --- 技术特点及其检测标准
  17. 手机怎么把视频压缩到最小
  18. 我国数据安全法详细解读
  19. 两种方式登录QQ空间提取SKEYamp;P_skey源码
  20. 大数据进阶之路——Spark SQL 之 DataFrameDataset

热门文章

  1. Ubuntu18.04等远程桌面无法打开终端和文件管理器解决方法
  2. 程序员如何保护眼睛?
  3. hive sql 转化为 MR 的流程博客
  4. 自学java第2天(随机数,猜字游戏,数组)
  5. Seq2Seq重复解码问题追根溯源
  6. udk2017环境搭建编译步骤
  7. SpringAOP之代理模式选择
  8. .zip.001 .zip.002怎么用压缩包解压缩
  9. python练习题 019:苹果和虫子2
  10. webpackjsonp 还原_钌金属单原子电催化实现常温常压高效还原固氮