前言

不知道各位看官是否有过类似的经历。好不容易找到一个电影的种子文件,想用百度云的离线下载功能去下载文件,却被百度云无情提示“离线文件因含有违规内容被系统屏蔽无法下载”!假设有这么一个场景,比如最近有一部电影《速度与激情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参数,用于向父节点添加子节点。

 private byte[] AnalysisInteger(); // 解析整形,由于会超出Int32的表示范围,用byte[]代替

 private byte[] AnalysisString(); // 解析字符串,考虑到编码问题,这里用byte[]表示

 private void AnalysisList(IBNode parent); // 解析列表

 private void AnalysisDictionary(IBNode parent); // 解析字典

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

 /// <summary>
/// 一个最简单的BT文件分析器
/// </summary>
class CommonAnalyser:IAnalyser
{
private byte[] torrentStream = null;
private int index = ;
private List<IBNode> _bNodeList = null;
private BNodeFactory _bNodeFactory = null; public CommonAnalyser()
{
torrentStream = null;
_bNodeList = new List<IBNode>();
_bNodeFactory = new BNodeFactory(_bNodeList);
index = ;
} public IBNode Analysis(byte[] torrentStream)
{
// 清空上一次处理的信息
_bNodeList = new List<IBNode>();
_bNodeFactory = new BNodeFactory(_bNodeList);
index = ; this.torrentStream = torrentStream;
// bt文件一定是一个字典开始的 DictNode rootNode = (DictNode)_bNodeFactory.GetBNode('d');
AnalysisDictionary(rootNode);
return rootNode;
} /// <summary>
/// 取出当前字符,并指针后移
/// </summary>
/// <returns></returns>
private char GetCurrentCharMove()
{
return (char)torrentStream[index++];
} /// <summary>
/// 取出当前字符,并指针不后移
/// </summary>
/// <returns></returns>
private char GetCurrentChar()
{
return (char)torrentStream[index];
} private void AnalysisDictionary(IBNode parent)
{
// 字典一定是d开始的
if (GetCurrentCharMove() != 'd')
return; // 循环分析键值对
do
{
KeyValueNode keyValueNode = (KeyValueNode)_bNodeFactory.GetBNode('k');
// 键值对,键一定是string
keyValueNode.SetKey(AnalysisString());
// 值
switch (GetCurrentChar())
{
case 'i': // 数字
keyValueNode.SetValue(AnalysisInteger());
keyValueNode.ValueType = 'i';
break;
case 'd': // 字典
AnalysisDictionary(keyValueNode);
keyValueNode.ValueType = 'd';
break;
case 'l': // 列表
AnalysisList(keyValueNode);
keyValueNode.ValueType = 'l';
break;
default: // 字符串
keyValueNode.SetValue(AnalysisString());
keyValueNode.ValueType = 's';
break;
}
parent.Child.Add(keyValueNode);
} while (GetCurrentChar() != 'e');
GetCurrentCharMove();
} private void AnalysisList(IBNode parent)
{
// 列表一定是l开始的
if (GetCurrentCharMove() != 'l')
return; int count = ;
// 循环读入列表项
do
{
ListItemNode listItemNode = (ListItemNode)_bNodeFactory.GetBNode('l');
switch (GetCurrentChar())
{
case 'i': // 数字
listItemNode.SetValue(AnalysisInteger());
listItemNode.ValueType = 'i';
break;
case 'd': // 字典
AnalysisDictionary(listItemNode);
listItemNode.ValueType = 'd';
break;
case 'l': // 列表
AnalysisList(listItemNode);
listItemNode.ValueType = 'l';
break;
default:
listItemNode.SetValue(AnalysisString());
listItemNode.ValueType = 's';
break;
}
listItemNode.ListIndex = count++;
parent.Child.Add(listItemNode);
} while (GetCurrentChar() != 'e');
GetCurrentCharMove();
} // 由于有些数字太大,用string来代替int
private byte[] AnalysisInteger()
{
// 数字一定是i开始e结尾的
if (GetCurrentCharMove() != 'i')
return null; //StringBuilder builder = new StringBuilder();
List<byte> integerByte = new List<byte>();
char currentChar = ' ';
while ((currentChar = GetCurrentCharMove()) != 'e')
{
//builder.Append(currentChar);
integerByte.Add((byte)currentChar);
} return integerByte.ToArray();
} private byte[] AnalysisString()
{
char currentChar = GetCurrentCharMove();
// 字符串一定是数字开始开始
if (currentChar < '' || currentChar > '')
return null; StringBuilder builder = new StringBuilder(); do
{
builder.Append(currentChar);
currentChar = GetCurrentCharMove();
} while (currentChar >= '' && currentChar <= ''); // 中间必须为:
if (currentChar != ':')
return null; int length = Int32.Parse(builder.ToString());
byte[] buffer = new byte[length];
for (int i = ; i < length; ++i)
{
buffer[i] = torrentStream[index++];
//builder.Append(GetCurrentCharMove());
} return buffer;
} public List<IBNode> bNodeList
{
get
{
return _bNodeList;
}
set
{
_bNodeList = value;
}
}
}

显示BT文件树状图

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

 private void ConstructTree(TreeNode tParent, IBNode bParent)
{
tParent.Text = bParent.ToString(); // 这里用了一点格式化的方法,详见完整代码
foreach (IBNode bNode in bParent.Child)
{
TreeNode tNode = new TreeNode();
tNode.Text = bNode.ToString();
tNode.Tag = bNode;
tParent.Nodes.Add(tNode);
ConstructTree(tNode, bNode);
}
}

显示效果就像下面这个样子。已经和上面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接口的时候预留了一个方法。

 /// <summary>
/// 接受修改
/// </summary>
/// <param name="visitor"></param>
void Accept(IVisitor visitor);

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

 /// <summary>
/// 用于修改特定键值对节点的值
/// </summary>
class KeyValueVisitor:IVisitor
{
private string[] tabooString = { "name", "name.utf-8", "path", "path.utf-8", "comment", "comment.utf-8",
"publisher", "publisher-url", "publisher-url.utf-8", "publisher.utf-8"};
public void Visit(KeyValueNode keyValueNode)
{
string key = keyValueNode.Key;
foreach (string name in tabooString)
{
if (key.Equals(name))
{
// 普通键值对
if (keyValueNode.Child.Count == )
keyValueNode.SetValue(Encoding.UTF8.GetBytes("somename"));
else // 列表项,通常是文件名
{
// 保留文件名,其余替换为somename
string value = ((ListItemNode)keyValueNode.Child[]).Value;
int startIndex = value.LastIndexOf(".");
value = String.Format("{0}.{1}", "somename", value.Substring(startIndex+));
(keyValueNode.Child[] as ListItemNode).SetValue(Encoding.UTF8.GetBytes(value));
}
break;
}
}
}
}

修改完了之后把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

[c#][福利]BTTool种子文件修改工具的更多相关文章

  1. Linux文件系统测试工具

    一.文件系统测试工具简介 1.LTP 参考网站:http://oss.sgi.com/projects/ltp/ LTP(Linux Test Project)是由SGI和IBM联合发起的项目,提供一 ...

  2. 工具软件发现(编写chm 文件的工具)

    编写chm 文件的工具 1.PrecisionHelper 安装之后,发现 编写的很不方便,直接在html 上编写-- 不好用 2.Winchm (推荐) 很好用,赞!至少对比了上面那个复杂的操作之后 ...

  3. 文件同步工具BT Sync介绍和使用说明

    BT Sync介绍 BT 下载,相信大伙儿都知道的.今儿个要介绍的 BT Sync,跟 BT 下载一样,都是 BitTorrent 公司发明滴玩意儿,都是采用 P2P 协议来进行传输. 简而言之,BT ...

  4. 超好用文件对比工具 – Beyond Compare

    超好用文件对比工具 – Beyond Compare,开发中文件.目录对比神器,有了它,再也不用为找不到修改的内容而发愁了. 具备的丰富实用功能: 并列比较文件夹.FTP 网站或 Zip 文件: 为以 ...

  5. VS2015如何另存解决方案文件-修改解决方案sln文件的路径

    原文:VS2005如何另存解决方案文件-修改解决方案sln文件的路径 修改解决方案sln文件的路径 方法一:工具→选项→项目和解决方案,可设置项目的默认保存位置.方法二:"解决方案资源管理器 ...

  6. Host文件修改后无效的解决办法

    什么是hosts文件? 简单的说,hosts文件是用于本地dns服务(相关主题:什么是DNS缓存,如何清除DNS缓存?)的,采用ip 域名的格式写在一个文本文件当中,Hosts是一个没有扩展名的系统文 ...

  7. Git版本控制:Git查阅、撤销文件修改和撤销文件追踪

    http://blog.csdn.net/pipisorry/article/details/47867097 查看文件的修改历史 git log --pretty=oneline 文件名 # 显示修 ...

  8. win7 Host文件修改后无效的解决办法

    win7 Host文件修改后无效的解决办法 正常情况下hosts文件随时修改随时生效,如果出现修改后不生效的情况,首先确定文件是ascii编码,以windows格式为换行符,然后依次采用如下方法  1 ...

  9. 用户组修改工具samusrgrp

    用户组修改工具samusrgrp   Windows系统内置了很多用户组,如Administrators.PowerUser.User等.用户隶属不同的组,就具备对应的权限.Kali Linux提供一 ...

随机推荐

  1. SQLServer 学习笔记之超详细基础SQL语句 Part 1

    Sqlserver 学习笔记 by:授客 QQ:1033553122 1创建数据库 格式: CREATE DATABASE database_name ON PRIMARY(在组文件组中指定文件) ( ...

  2. Flutter 网络请求库http

    http 集成http库 https://pub.dartlang.org/packages/http 添加依赖 dependencies: http: ^ 安装 flutter packages g ...

  3. Expo大作战(十七)--expo结合哨兵(sentry)进行错误异常记录

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  4. tinymce4.x 上传本地图片(自己写个插件)

    tinymce是一款挺不错的html文本编辑器.但是添加图片是直接添加链接,不能直接选择本地图片. 下面我写了一个插件用于直接上传本地图片. 在tinymce的plugins目录下新建一个upload ...

  5. Linux Ubuntu16.04LTS安装TensorFlow(CPU-only,python3.7)——使用Anaconda安装

    1.安装Anaconda(在此不再赘述) 2.用Conda安装TensorFlow 1)建立TensorFlow运行环境并激活 conda create -n tensorflow pip pytho ...

  6. 从ibd文件获取表空间id

    xtrabackup恢复过程中出现如下错误 InnoDB: Doing recovery: scanned up to log sequence number ( %) InnoDB: Doing r ...

  7. Distribution setup SQL Server Agent error: "RegCreateKeyEx() returned error 5, 'Access is denied.'" (转载)

    In the Configure Distribution Wizard, the step "Configuring SQL Server Agent to start automatic ...

  8. centos 安装elk监控

    下面就是要安装一些收集日志 或者分配日志的工具,我选择的是 Filebeat 来收集日志,然后放到kafka中 让kafka这个消息队列来分配生产者消费者  然后通过Logstash 或者一个国产大神 ...

  9. 常用js对象、数组、字符串的方法

    字符串charAt() 返回在指定位置的字符.charCodeAt() 返回在指定的位置的字符的 Unicode 编码.concat() 连接字符串.indexOf() 检索字符串.match() 找 ...

  10. 【Android自动化】在使用uiautomator框架自动化时,往往有时再运行脚本时发现xxx实例属性不被允许

    例如: # -*- coding:utf-8 -*- from uiautomator import device as d d(classname="android.widget.List ...