[c#][福利]BTTool种子文件修改工具
前言
不知道各位看官是否有過類似的經(jīng)歷。好不容易找到一個電影的種子文件,想用百度云的離線下載功能去下載文件,卻被百度云無情提示“離線文件因含有違規(guī)內(nèi)容被系統(tǒng)屏蔽無法下載”!假設(shè)有這么一個場景,比如最近有一部電影《速度與激情7》很火,百度為了保護(hù)版權(quán)方的利益,對于凡是種子文件中包含了關(guān)鍵字“速度與激情7”的,一律提示包含有違規(guī)內(nèi)容,禁止下載。
于是乎,后來就有了通過“BEncode Editor”這款工具來修改其中一些敏感性文字的方法來修改種子文件,以達(dá)到下載的目的。比如在上一個例子中,我們把種子文件中凡是包含有“速度與激情7”的文字全部修改為“電影7”,然后保存種子文件再下載的話,就不會被百度屏蔽了。修改的方法如下圖所示。
本來故事到此應(yīng)該就已經(jīng)結(jié)束了。但是后來有兩點促使了我編寫一個自動化的種子文件修改工具。第一點是因為有的種子文件包含的文件實在太多了,每一個文件的文件名都改過去很麻煩,第二點是因為能用電腦解決的重復(fù)性勞動我就懶得用手解決。所以才有了下面的故事。
種子文件編碼格式
BT種子文件使用了一種叫bencoding的編碼方法來保存數(shù)據(jù)[1]。
編碼規(guī)則如下:
strings(字符串)編碼為:<字符串長度>:<字符串>
例如: 4:test 表示為字符串test
4:例子 表示為字符串“例子”
字符串長度單位為字節(jié)
沒開始或結(jié)束標(biāo)記
integers(整數(shù))編碼為:i<整數(shù)>e
開始標(biāo)記i,結(jié)束標(biāo)記為e
例如: i1234e 表示為整數(shù)1234
i-1234e 表示為整數(shù)-1234
整數(shù)沒有大小限制
i0e 表示為整數(shù)0
i-0e 為非法
以0開頭的為非法如: i01234e 為非法
lists(列表)編碼為:l<bencoding編碼類型>e
開始標(biāo)記為l,結(jié)束標(biāo)記為e
列表里可以包含任何bencoding編碼類型,包括整數(shù),字符串,列表,字典。
例如: l4:test5abcdee 表示為二個字符串[test,abcde]
dictionaries(字典)編碼為d<bencoding字符串><bencoding編碼類型>e
開始標(biāo)記為d,結(jié)束標(biāo)記為e
關(guān)鍵字必須為bencoding字符串
值可以為任何bencoding編碼類型
例如: d3:agei20ee 表示為{age=20}
d4:path3:C:/8:filename8:test.txte 表示為{path=C:/,filename=test.txt}
節(jié)點的定義
為了對BT文件有一個直觀的印象,我們還是以速度與激情7這個BT文件為例,從圖中為各位看官做一下介紹。仔細(xì)觀察下圖,我們發(fā)現(xiàn)在圖中的節(jié)點無非是三種類型,第一種是根節(jié)點,第二種是鍵值對節(jié)點(字典也是一個特殊的鍵值對節(jié)點,其鍵為名字,而值為其所有子節(jié)點),第三種列表節(jié)點。
簡單的BT文件解析器
可以看到bencoding編碼中的四種類型都有一個標(biāo)識頭,比如整數(shù)類型以'i'開始,string類型以數(shù)字開始。利用這一特性,對于每一個類型,我們先嘗試讀一個字符,并根據(jù)讀入的字符判斷讀入的是什么類型,如‘i’為整形,'d'為字典,'l'為列表而剩下的數(shù)字則為字符串。
那么接下來的思路就非常清晰了,我們需要四個方法來分別解析數(shù)字,字符串,字典和列表。其中數(shù)字和字符串類型只用于表示值,而不能作為容器;列表和字典類型都可以作為容器,故還有一個parent參數(shù),用于向父節(jié)點添加子節(jié)點。
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文件是樹狀結(jié)構(gòu)的,這里我們使用遞歸來實現(xiàn)對BT文件的解析。可以確定的,BT文件一定是以一個字典類型開始的,所以我們先調(diào)用AnalysisDictionary方法,并把參數(shù)根節(jié)點傳給它。之后在該方法中通過讀入下一個字符來判斷是什么類型,并調(diào)用相應(yīng)的方法來解析該類型,而相應(yīng)的方法又通過相同的方法繼續(xù)調(diào)用另外的方法,如此循環(huán),直到解析完畢,這也正是遞歸的思想。下邊就是我實現(xiàn)的一個簡單的BT文件解析器,返回的是一個IBNode類型的根節(jié)點。
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 /// 取出當(dāng)前字符,并指針后移 36 /// </summary> 37 /// <returns></returns> 38 private char GetCurrentCharMove() 39 { 40 return (char)torrentStream[index++]; 41 } 42 43 /// <summary> 44 /// 取出當(dāng)前字符,并指針不后移 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 // 循環(huán)分析鍵值對 59 do 60 { 61 KeyValueNode keyValueNode = (KeyValueNode)_bNodeFactory.GetBNode('k'); 62 // 鍵值對,鍵一定是string 63 keyValueNode.SetKey(AnalysisString()); 64 // 值 65 switch (GetCurrentChar()) 66 { 67 case 'i': // 數(shù)字 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 // 循環(huán)讀入列表項 97 do 98 { 99 ListItemNode listItemNode = (ListItemNode)_bNodeFactory.GetBNode('l'); 100 switch (GetCurrentChar()) 101 { 102 case 'i': // 數(shù)字 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 // 由于有些數(shù)字太大,用string來代替int 126 private byte[] AnalysisInteger() 127 { 128 // 數(shù)字一定是i開始e結(jié)尾的 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 // 字符串一定是數(shù)字開始開始 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文件樹狀圖
好不容易解析完了,當(dāng)然要先把它顯示出來看是否正確。這里我們仿照“BEncode Editor”這款工具的界面來顯示。簡單分析一下,其實就是使用了一個TreeView的控件來顯示。由于我們解析出來的節(jié)點和TreeView控件的節(jié)點正好是一一對應(yīng)的,所以這里也用一個遞歸就能實現(xiàn)了。
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 }顯示效果就像下面這個樣子。已經(jīng)和上面BT文件修改工具很像了。
修改BT文件
至今為止我們都在做重復(fù)的工作,模仿已有的工具,那么接下來就是新的內(nèi)容了。經(jīng)過我的仔細(xì)觀察后發(fā)現(xiàn),百度云離線下載檢測的關(guān)鍵詞主要為
{ "name", "name.utf-8", "path", "path.utf-8", "comment", "comment.utf-8", "publisher", "publisher-url", "publisher-url.utf-8", "publisher.utf-8"}這些鍵后面的值。只要我們把這些后面對應(yīng)的值改為一些不敏感的詞,那么就能躲過百度的審查。
為了把剛學(xué)的設(shè)計模式用上去,我在之前定義IBNode接口的時候預(yù)留了一個方法。
1 /// <summary> 2 /// 接受修改 3 /// </summary> 4 /// <param name="visitor"></param> 5 void Accept(IVisitor visitor);這里主要用到了設(shè)計模式當(dāng)中訪問者模式。現(xiàn)在要修改這些鍵對用的值,我們只需要遍歷一遍所有的節(jié)點,并在每一個節(jié)點上調(diào)用一次Accept方法,讓訪問者去做修改的工作就可以了。訪問者的代碼如下,大致思想就是判斷鍵值如果在我們上文提到的那些鍵值中的其中一個,那么就把其對應(yīng)的值改為“somename”,相應(yīng)百度應(yīng)該不會把somename認(rèn)為是敏感的詞。
1 /// <summary> 2 /// 用于修改特定鍵值對節(jié)點的值 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文件按讀取的順序?qū)懟匚募涂梢粤恕M瑯右彩且粋€遞歸的方法,這里就不再贅述了,至此,BT文件的修改就大功告成了。
小結(jié)
除了文中提到的一些功能外,整個小工具還加上了日志記錄,批量轉(zhuǎn)換功能,也算是平時閑著的無聊之作吧。
下面回到正題,為什么篇名還要加上福利二字呢?我開始動手寫這個修改工具的時候也是沒有想到,原來通過還能修改一些動作片的種子文件,從而逃過百度的審查,順利使用百度云離線。整個工程源碼下載見鏈接部分。至于效果怎么樣,誰用誰知道吧^_^
?
鏈接
種子文件編碼格式:http://www.cnblogs.com/hnrainll/archive/2011/07/26/2117423.html
可執(zhí)行文件(博客園):http://files.cnblogs.com/files/fantacity/BTTool%287.8%29.rar
Github 項目地址:?http://github.com/yosef-gao/BTTool
轉(zhuǎn)載于:https://www.cnblogs.com/fantacity/p/4614816.html
總結(jié)
以上是生活随笔為你收集整理的[c#][福利]BTTool种子文件修改工具的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文件共享服务器热备,两台云服务器如何实现
- 下一篇: erdas2014安装