LoranRendel Posted November 7, 2012 Posted November 7, 2012 (edited) Hello,This is my UDF working with *.torrent files.It converts torrent file to readable and editable text structure and back to torrent.Binary data will be encoded in base64.Written on pure AutoIt, except base64 located Due to limitation of this version of base64 code, code will not work on x64 compilation.Updated to new x64 compatible code.This code can also be used to edit utorrent/BitTorrent config files (like settings.dat), as it encoded using bencode.Examples are included.torrentUDF.zip Edited November 8, 2012 by xrewndel bfgxp 1
wraithdu Posted November 8, 2012 Posted November 8, 2012 Look here for x64 compatible machine code functions. LoranRendel 1
LoranRendel Posted November 8, 2012 Author Posted November 8, 2012 Look here for x64 compatible machine code functions.Thanks. I have updated the archive.
garbb Posted October 30, 2013 Posted October 30, 2013 (edited) Thanks for making this, I was having trouble working with torrent files in autoit until I found this thread. I modified your _Torrent_Parse function slightly to add outputting of the infohash for a torrent file because I had a need to do this: expandcollapse popup#include <Crypt.au3> Func _Torrent_Parse($File) Local $Data, $Binary, $hFile, $Byte, $Number, $String, $Size, $infodictlevel, $recording_info_bytes=false, $infobytes $hFile = FileOpen($File, 16) If @error Then Return SetError(1, 0, '') While True $Byte = BinaryToString(FileRead($hFile, 1)) if $recording_info_bytes then $infobytes &= $Byte If @error Then ExitLoop Switch $Byte Case 'd'; Dictionary -> $Data &= __Torrents_StringFormat('Dictionary') __Torrents_Level(1) if $string='info' then $infodictlevel=__Torrents_Level() $recording_info_bytes = true $infobytes = $Byte EndIf Case 'l'; List -> $Data &= __Torrents_StringFormat('List') __Torrents_Level(1) Case 'i'; Integer $Number = '' Do $Number &= $Byte $Byte = BinaryToString(FileRead($hFile, 1)) if $recording_info_bytes then $infobytes &= $Byte If @error Then ExitLoop Until $Byte = 'e' VarTrim($Number, 1) Switch $String Case 'length', 'piece length' If $Number < 2 ^ 20 Then $Number &= ' (' & Round($Number / 2 ^ 10, 3) & ' KiB)' ElseIf $Number >= 2 ^ 20 And $Number < 2 ^ 30 Then $Number &= ' (' & Round($Number / 2 ^ 20, 3) & ' MiB)' Else $Number &= ' (' & Round($Number / 2 ^ 30, 3) & ' GiB)' EndIf Case 'creation date' $Number &= ' (' & _DateAdd('s', $Number, "1970/01/01 00:00:00") & ')' EndSwitch $Data &= __Torrents_StringFormat('integer: ' & $Number) Case 'e'; End of element <- __Torrents_Level(-1) if __Torrents_Level()=$infodictlevel-1 then $recording_info_bytes = false $Data &= __Torrents_StringFormat('End') Case '0' To '9'; String's size $Size &= $Byte Case ':'; String $String = BinaryToString(FileRead($hFile, $Size)) if $recording_info_bytes then $infobytes &= $String $Size = '' If StringInStr($String, @LF) or StringInStr($String, @CR) Then $Data &= __Torrents_StringFormat('binary: ' & _Base64Encode(StringToBinary($String), 0)) Else $Data &= __Torrents_StringFormat('string: ' & $String) EndIf Case Else; Error Return SetError(2, 0, '') EndSwitch WEnd Local $bHash = _Crypt_HashData($infobytes, $CALG_SHA1) $Data &= @CRLF& 'Infohash: '&StringTrimLeft($bHash,2) Return StringAddCR($Data) EndFunc ;==>_Torrent_Parse Edited October 30, 2013 by garbb
argumentum Posted September 8 Posted September 8 ..it lives ! This a good thread for the post  Took the code for torrentcheck, AutoIt-tize it, and added what I wanted. expandcollapse popup#pragma compile(Console, True) #pragma compile(Icon, "") #AutoIt3Wrapper_AU3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 5 -w 6 -w 7 #include <Crypt.au3> #include <File.au3> ; https://www.autoitscript.com/forum/topic/145656-torrent-bencode-udf/ ; Global configuration flags Global $g_sTorrentFile = "" Global $g_sContentPath = "" Global $g_bSingleMismatch = False Global $g_bHideProgress = False Global $g_bShowHashes = False Global $g_sNumberFormat = "" ; "" = raw, "c" = comma, "d" = dot Global $g_bSHA1Mode = False Global $g_sSHA1Target = "" Func ShowUsage() ConsoleWrite("torrentcheck (AutoIt Edition)" & @CRLF) ; "https://github.com/Network-BEncode-inside/torrentcheck" ConsoleWrite("Usage: torrentcheck [torrent-file] [-t torrent-file] [-p content-path] [-n] [-h] [-c] [-d] [-sha1 [hash]]" & @CRLF) ConsoleWrite("Options:" & @CRLF) ConsoleWrite(" -t <torrent-file> Path to .torrent file" & @CRLF) ConsoleWrite(" -p <content-path> Path to files/folder to check" & @CRLF) ConsoleWrite(" -m On Single Mismatch stop checking" & @CRLF) ; new ( not in the original source ) ConsoleWrite(" -n Suppress progress indicator" & @CRLF) ConsoleWrite(" -h Show piece hashes" & @CRLF) ConsoleWrite(" -c Comma-formatted numbers (1,000,000)" & @CRLF) ConsoleWrite(" -d Dot-formatted numbers (1.000.000)" & @CRLF) ConsoleWrite(" -sha1 [hash] Act as SHA1 stdin filter" & @CRLF) EndFunc ;==>ShowUsage Main() Func Main() ParseCommandLine() ; --- SHA-1 Filter Mode --- If $g_bSHA1Mode Then _Crypt_Startup() Local $sCalculatedHash = CalculateStdinSHA1() _Crypt_Shutdown() ConsoleWrite("SHA1: " & $sCalculatedHash & @CRLF) If $g_sSHA1Target <> "" Then If StringLower($sCalculatedHash) = StringLower($g_sSHA1Target) Then ConsoleWrite("SHA1 Hash Matches." & @CRLF) Exit (0) Else ConsoleWrite("SHA1 Hash MISMATCH!" & @CRLF) Exit (1) EndIf EndIf Exit (0) EndIf ; --- Torrent Mode --- If $g_sTorrentFile = "" Then ShowUsage() Exit (1) EndIf If Not FileExists($g_sTorrentFile) Then ConsoleWrite("Error: Torrent file not found: " & $g_sTorrentFile & @CRLF) Exit (1) EndIf ; Read torrent file binary Local $iLen, $hFile = FileOpen($g_sTorrentFile, 16) ; Read in binary mode If $hFile = -1 Then ConsoleWrite("Error: Unable to open torrent file." & @CRLF) Exit (1) EndIf Local $bTorrentData = FileRead($hFile) FileClose($hFile) ; Parse Bencode Local $iPos = 1 Local $oRoot = Bencode_Parse($bTorrentData, $iPos) If @error Or Not IsObj($oRoot) Then ConsoleWrite("Error: Invalid or corrupted bencoded torrent file." & @CRLF) Exit (1) EndIf ; Extract Torrent Info If Not $oRoot.Exists("info") Then ConsoleWrite("Error: Missing 'info' dictionary in torrent file." & @CRLF) Exit (1) EndIf Local $oInfo = $oRoot.Item("info") Local $sAnnounce = $oRoot.Exists("announce") ? BinaryToString($oRoot.Item("announce")) : "N/A" Local $sCreatedBy = $oRoot.Exists("created by") ? BinaryToString($oRoot.Item("created by")) : "N/A" Local $sComment = $oRoot.Exists("comment") ? BinaryToString($oRoot.Item("comment")) : "N/A" Local $iPieceLength = Number($oInfo.Item("piece length")) Local $bPieces = $oInfo.Item("pieces") Local $iTotalPieceBytes = BinaryLen($bPieces) Local $iPieceCount = Int($iTotalPieceBytes / 20) Local $sTorrentName = BinaryToString($oInfo.Item("name")) Local $sTorrentVersion = DetectTorrentVersion($oInfo) ; <-- Add this line ConsoleWrite("==================================================" & @CRLF) ConsoleWrite("Torrent Metadata Summary" & @CRLF) ConsoleWrite("==================================================" & @CRLF) ConsoleWrite("Name: " & $sTorrentName & @CRLF) ConsoleWrite("Version: " & $sTorrentVersion & @CRLF) ; <-- Add this line ConsoleWrite("Tracker: " & $sAnnounce & @CRLF) ConsoleWrite("Created By: " & $sCreatedBy & @CRLF) ConsoleWrite("Comment: " & $sComment & @CRLF) ConsoleWrite("Piece Length: " & FormatNumber($iPieceLength) & " bytes" & @CRLF) ConsoleWrite("Total Pieces: " & $iPieceCount & @CRLF) ConsoleWrite("--------------------------------------------------" & @CRLF) ; --- Print File Hashes --- If $oInfo.Exists("file tree") Then ConsoleWrite(@CRLF & "Expected File Hashes (BitTorrent v2 SHA-256):" & @CRLF) ConsoleWrite("--------------------------------------------------------------------------------" & @CRLF) PrintV2FileHashes($oInfo.Item("file tree")) ConsoleWrite("================================================================================" & @CRLF) Else ConsoleWrite(@CRLF & "Note: Individual per-file hashes are not available in BitTorrent v1 torrents." & @CRLF) EndIf ; Extract File List Local $sRelPath, $aFiles[0][2] ; [Path, Size] Local $aSubPaths, $oFileDict, $iTotalSize = 0 If $oInfo.Exists("files") Then ; Multi-file torrent Local $aFileList = $oInfo.Item("files") For $i = 0 To UBound($aFileList) - 1 $oFileDict = $aFileList[$i] $iLen = Number($oFileDict.Item("length")) $aSubPaths = $oFileDict.Item("path") $sRelPath = $sTorrentName For $j = 0 To UBound($aSubPaths) - 1 $sRelPath &= "\" & BinaryToString($aSubPaths[$j]) Next AddFileToList($aFiles, $sRelPath, $iLen) $iTotalSize += $iLen Next Else ; Single-file torrent $iLen = Number($oInfo.Item("length")) AddFileToList($aFiles, $sTorrentName, $iLen) $iTotalSize += $iLen EndIf ConsoleWrite("Total Size: " & FormatNumber($iTotalSize) & " bytes (" & UBound($aFiles) & " files)" & @CRLF) ConsoleWrite("--------------------------------------------------" & @CRLF) For $i = 0 To UBound($aFiles) - 1 ConsoleWrite(StringFormat("%-12s %s\n", FormatNumber($aFiles[$i][1]), $aFiles[$i][0])) Next ConsoleWrite("==================================================" & @CRLF) ; Print piece hashes if -h specified If $g_bShowHashes Then ConsoleWrite(@CRLF & "Piece SHA-1 Hashes:" & @CRLF) For $p = 0 To $iPieceCount - 1 Local $bPieceHash = BinaryMid($bPieces, ($p * 20) + 1, 20) ConsoleWrite(StringFormat("Piece %4d: %s\n", $p + 1, StringTrimLeft(Hex($bPieceHash), 0))) Next EndIf ; --- Piece Verification (-p) --- If $g_sContentPath = "" Then Exit (0) EndIf ConsoleWrite(@CRLF & "Verifying torrent piece hashes against data on disk..." & @CRLF) _Crypt_Startup() Local $bSuccess = VerifyTorrentData($aFiles, $bPieces, $iPieceLength, $iPieceCount) _Crypt_Shutdown() If $bSuccess Then ConsoleWrite(@CRLF & "RESULT: Torrent is GOOD. All files verified successfully!" & @CRLF) Exit (0) Else ConsoleWrite(@CRLF & "RESULT: Torrent verification FAILED! Hash mismatches or missing files." & @CRLF) Exit (1) EndIf EndFunc ;==>Main ; ============================================================================== ; Bencode Parser implementation ; ============================================================================== Func Bencode_Parse(ByRef $bData, ByRef $iPos) If $iPos > BinaryLen($bData) Then Return SetError(1, 0, Null) Local $sChar = BinaryToString(BinaryMid($bData, $iPos, 1)) ; Integer: i<number>e If $sChar = "i" Then $iPos += 1 Local $iEnd = _BinaryInStr($bData, Binary("e"), $iPos) ; <-- Updated here If $iEnd = 0 Then Return SetError(2, 0, Null) Local $sNum = BinaryToString(BinaryMid($bData, $iPos, $iEnd - $iPos)) $iPos = $iEnd + 1 Return Number($sNum) ; List: l<items>e ElseIf $sChar = "l" Then $iPos += 1 Local $aList[0] While $iPos <= BinaryLen($bData) If BinaryToString(BinaryMid($bData, $iPos, 1)) = "e" Then $iPos += 1 ExitLoop EndIf Local $vItem = Bencode_Parse($bData, $iPos) ReDim $aList[UBound($aList) + 1] $aList[UBound($aList) - 1] = $vItem WEnd Return $aList ; Dictionary: d<key><val>...e ElseIf $sChar = "d" Then $iPos += 1 Local $oDict = ObjCreate("Scripting.Dictionary") While $iPos <= BinaryLen($bData) If BinaryToString(BinaryMid($bData, $iPos, 1)) = "e" Then $iPos += 1 ExitLoop EndIf Local $sKey = Bencode_Parse($bData, $iPos) If IsBinary($sKey) Then $sKey = BinaryToString($sKey) Local $vVal = Bencode_Parse($bData, $iPos) If Not $oDict.Exists($sKey) Then $oDict.Add($sKey, $vVal) WEnd Return $oDict ; Byte String / Binary: <length>:<content> ElseIf StringIsDigit($sChar) Then Local $iColon = _BinaryInStr($bData, Binary(":"), $iPos) ; <-- Updated here If $iColon = 0 Then Return SetError(3, 0, Null) Local $iLen = Number(BinaryToString(BinaryMid($bData, $iPos, $iColon - $iPos))) $iPos = $iColon + 1 Local $bStr = BinaryMid($bData, $iPos, $iLen) $iPos += $iLen Return $bStr EndIf Return SetError(4, 0, Null) EndFunc ;==>Bencode_Parse ; ============================================================================== ; Torrent Piece Verification Engine ; ============================================================================== Func VerifyTorrentData(ByRef $aFiles, $bPieces, $iPieceLength, $iPieceCount) Local $iFileIdx = 0 Local $iFilePos = 0 Local $hFile = -1 Local $iErrors = 0 For $p = 0 To $iPieceCount - 1 Local $bExpectedHash = BinaryMid($bPieces, ($p * 20) + 1, 20) Local $bPieceBuffer = Binary("") Local $iBytesNeeded = $iPieceLength While $iBytesNeeded > 0 And $iFileIdx < UBound($aFiles) ; Resolve file path on disk If $hFile = -1 Then Local $sFullPath = FixPath($g_sContentPath, $aFiles[$iFileIdx][0]) If Not FileExists($sFullPath) Then ConsoleWrite(@CRLF & "Error: Missing file: " & $sFullPath & @CRLF) Return False EndIf $hFile = FileOpen($sFullPath, 16) $iFilePos = 0 EndIf Local $iFileRemain = $aFiles[$iFileIdx][1] - $iFilePos Local $iToRead = ($iBytesNeeded < $iFileRemain) ? $iBytesNeeded : $iFileRemain If $iToRead > 0 Then Local $bChunk = FileRead($hFile, $iToRead) $bPieceBuffer &= $bChunk $iFilePos += BinaryLen($bChunk) $iBytesNeeded -= BinaryLen($bChunk) EndIf ; Advance to next file if current file reached EOF If $iFilePos >= $aFiles[$iFileIdx][1] Then FileClose($hFile) $hFile = -1 $iFileIdx += 1 EndIf WEnd ; Compute SHA-1 of piece buffer Local $bCalculatedHash = _Crypt_HashData($bPieceBuffer, $CALG_SHA1) If $bCalculatedHash <> $bExpectedHash Then $iErrors += 1 ConsoleWrite(StringFormat("\nPiece %4d: [FAILED] Mismatch!", $p + 1)) If $g_bSingleMismatch Then ExitLoop ElseIf Not $g_bHideProgress Then ConsoleWrite(StringFormat("\rChecking pieces... [%d/%d]", $p + 1, $iPieceCount)) EndIf Next If $hFile <> -1 Then FileClose($hFile) If Not $g_bHideProgress Then ConsoleWrite(@CRLF) Return ($iErrors == 0) EndFunc ;==>VerifyTorrentData ; ============================================================================== ; Utility & Helper Functions ; ============================================================================== Func FixPath($sBase, $sRel) $sBase = StringRegExpReplace($sBase, "[\\/]+$", "") $sRel = StringRegExpReplace($sRel, "^[\\/]+", "") ; If user points directly to target single file or directory containing files If FileExists($sBase & "\" & $sRel) Then Return $sBase & "\" & $sRel ElseIf FileExists($sBase) And Not StringInStr(FileGetAttrib($sBase), "D") Then Return $sBase EndIf ; Strip root folder name if user passed target inside the root folder Local $iFirstSlash = StringInStr($sRel, "\") If $iFirstSlash > 0 Then Local $sStrippedRel = StringMid($sRel, $iFirstSlash + 1) If FileExists($sBase & "\" & $sStrippedRel) Then Return $sBase & "\" & $sStrippedRel EndIf EndIf Return $sBase & "\" & $sRel EndFunc ;==>FixPath Func AddFileToList(ByRef $aFiles, $sPath, $iSize) Local $iCount = UBound($aFiles) ReDim $aFiles[$iCount + 1][2] $aFiles[$iCount][0] = $sPath $aFiles[$iCount][1] = $iSize EndFunc ;==>AddFileToList Func FormatNumber($iValue) If $g_sNumberFormat = "" Then Return $iValue Local $sSep = ($g_sNumberFormat = "c") ? "," : "." Local $sStr = String($iValue) Local $sRes = "" Local $iLen = StringLen($sStr) For $i = 1 To $iLen $sRes = StringMid($sStr, $iLen - $i + 1, 1) & $sRes If Mod($i, 3) = 0 And $i < $iLen Then $sRes = $sSep & $sRes Next Return $sRes EndFunc ;==>FormatNumber Func CalculateStdinSHA1() Local $hStdin = FileOpen("*", 16) ; STDIN binary mode Local $bData = Binary("") While True Local $bChunk = FileRead($hStdin, 65536) If @error Then ExitLoop $bData &= $bChunk WEnd FileClose($hStdin) Return StringTrimLeft(Hex(_Crypt_HashData($bData, $CALG_SHA1)), 0) EndFunc ;==>CalculateStdinSHA1 Func ParseCommandLine() For $i = 1 To $CmdLine[0] Switch $CmdLine[$i] Case "-t" If $i + 1 <= $CmdLine[0] Then $g_sTorrentFile = $CmdLine[$i + 1] $i += 1 EndIf Case "-p" If $i + 1 <= $CmdLine[0] Then $g_sContentPath = $CmdLine[$i + 1] $i += 1 EndIf Case "-m" $g_bSingleMismatch = True Case "-n" $g_bHideProgress = True Case "-h" $g_bShowHashes = True Case "-c" $g_sNumberFormat = "c" Case "-d" $g_sNumberFormat = "d" Case "-sha1" $g_bSHA1Mode = True If $i + 1 <= $CmdLine[0] And Not StringStartsWith($CmdLine[$i + 1], "-") Then $g_sSHA1Target = $CmdLine[$i + 1] $i += 1 EndIf Case Else If $g_sTorrentFile = "" And Not StringStartsWith($CmdLine[$i], "-") Then $g_sTorrentFile = $CmdLine[$i] EndIf EndSwitch Next EndFunc ;==>ParseCommandLine Func StringStartsWith($sStr, $sPrefix) Return StringLeft($sStr, StringLen($sPrefix)) = $sPrefix EndFunc ;==>StringStartsWith Func _BinaryInStr($bData, $bNeedle, $iStartPos = 1) Local $sDataHex = StringTrimLeft(String($bData), 2) Local $sNeedleHex = StringTrimLeft(String($bNeedle), 2) Local $iHexStart = ($iStartPos - 1) * 2 + 1 Local $iPosHex = StringInStr($sDataHex, $sNeedleHex, 1, 1, $iHexStart) If $iPosHex = 0 Then Return 0 Return Ceiling($iPosHex / 2) EndFunc ;==>_BinaryInStr Func DetectTorrentVersion($oInfo) ; new ( not in the original source ) Local $bHasV1Pieces = $oInfo.Exists("pieces") And (BinaryLen($oInfo.Item("pieces")) > 0) Local $bHasMetaV2 = $oInfo.Exists("meta version") And (Number($oInfo.Item("meta version")) == 2) Local $bHasFileTree = $oInfo.Exists("file tree") If $bHasMetaV2 Or $bHasFileTree Then If $bHasV1Pieces Then Return "Hybrid (BitTorrent v1 + v2)" Else Return "BitTorrent v2" EndIf EndIf Return "BitTorrent v1" EndFunc ;==>DetectTorrentVersion ; Recursively prints SHA-256 pieces root for files in BitTorrent v2 / Hybrid file tree ; new ( not in the original source ) Func PrintV2FileHashes($oDict, $sCurrentPath = "") Local $aKeys = $oDict.Keys() For $sKey In $aKeys Local $vVal = $oDict.Item($sKey) If $sKey == "" Then ; Leaf node containing file metadata If IsObj($vVal) And $vVal.Exists("pieces root") Then Local $bRootHash = $vVal.Item("pieces root") Local $sHexHash = StringTrimLeft(Hex($bRootHash), 0) ConsoleWrite(StringFormat("%-64s %s\n", $sHexHash, $sCurrentPath)) Else ConsoleWrite(StringFormat("%-64s %s\n", "[No SHA-256 Root]", $sCurrentPath)) EndIf Else ; Directory or file container node Local $sNextPath = ($sCurrentPath = "") ? $sKey : $sCurrentPath & "\" & $sKey If IsObj($vVal) Then PrintV2FileHashes($vVal, $sNextPath) EndIf EndIf Next EndFunc ;==>PrintV2FileHashes Why?, ...because I had a faster direct download than the torrent but wanted to check if it was exactly the same and had no hash for the file. This worked to verify the file. Limited testing other than what I wanted to do but it should fully work ( never did compile it, just run from SciTE ). ioa747 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting Â
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now