Delphi, How to use BEncode to get info_hash? - delphi

I want to get info_hash of *.torrent file using Delphi.
Tried this BEncode decorder.
But it gives crazy characters when decode.
Any other working BEncode decoder in Delphi? Or anything I'm doing wrong?
This is my code:
procedure TForm.Button1Click(Sender: TObject);
var
be: TBEncoded;
fs: tfilestream;
op: string;
begin
fs := tfilestream.Create('xx.torrent', fmOpenReadWrite);
be := TBEncoded.Create(fs);
be.Encode(be.ListData.Items[0].Data, op);
showmessage(op);
be.Encode(be.ListData.FindElement('info'), op);
showmessage(op);
end;

I've just tried this decoder, it's working normally. You didn't need to use Encode procedure, its purpose (as seen from name) is to encode elements back to BEncode. That's test program that shows torrent information in TMemo:
procedure ShowDecoded(be: TBEncoded; indent: string='');
var i: Integer;
begin
with form1.Memo1.Lines do
case be.Format of
befstring: Add(indent+be.StringData);
befInteger: Add(indent+IntToStr(be.IntegerData));
befList: begin
Add(indent+'list');
for i:=0 to be.ListData.Count-1 do
ShowDecoded(be.ListData.Items[i].Data as TBEncoded,indent+' ');
Add(indent+'end of list');
end;
befDictionary: begin
Add(indent+'dict');
for i:=0 to be.ListData.Count-1 do begin
Add(indent+' '+be.ListData.Items[i].Header+'=');
ShowDecoded(be.listData.Items[i].Data as TBEncoded,indent+' ');
end;
Add(indent+'end of dict');
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var fs: TFileStream;
be: TBEncoded;
i: Integer;
begin
if OpenDialog1.Execute then begin
fs:=TFileStream.Create(OpenDialog1.FileName,fmOpenRead);
try
be:=TBEncoded.Create(fs);
ShowDecoded(be);
be.Free;
finally
fs.Free;
end;
end;
end;
That's test result:
dict
created by=
uTorrent/3.4.3
creation date=
1439626950
encoding=
UTF-8
info=
dict
length=
1345178
name=
Алябьев А., Лист Ф. - Соловей - 1987.pdf
piece length=
16384
pieces=
)Lo.Î ’üXí»IÙçsáôt£ˆb›hŒˆ*Ð誺š¤/N7’`0âÓ†nË5&T€:V•Ìפ¯9¤Ý:¦J©Ï|Œ•A¥,¼R¯þ:H:X&…¢<¸º"2îV-vÀÖˆD†¨¬ß‰ƒ,ümjà?éÛoe¬r£{¨¾]•4òØžhô†›­¼AØBeJÕÌ4³·Œ‹¶ËAG— f„\pa
end of dict
end of dict
I'd make some changes to BEncode unit, there is mess in there: raising empty exceptions, unsafe cast: TBEncoded(object) instead of "object as TBEncoded",
checking for nil object before object.free, which is tautology, but in general it works.
Update 1
Simple code to take one of the fields, 'pieces' and show in hex.
procedure FindAndShowHash(be: TBEncoded);
var i: Integer;
s: string;
infoChunk, piecesChunk: TBencoded;
begin
s:='';
infoChunk:=be.ListData.FindElement('info') as TBencoded;
piecesChunk:=infoChunk.ListData.FindElement('pieces') as TBencoded;
for i:=1 to Length(piecesChunk.StringData) do
s:=s+IntToHex(Byte(piecesChunk.StringData[i]),2);
form1.Memo1.Lines.Add('Hash function:');
form1.Memo1.Lines.Add(s);
end;
As you see, we access StringData char by char and cast it as Byte. I just showed it in hex, of course you can use these bytes for further processing.
Beware: you'll get LOADS of hex values, this is not MD5 hash or any other hash of WHOLE torrent, it's sequence of hash functions for each piece of data, usually blocks of 1 or 2 MB.
UPDATE 2
This unit can be used in newer versions of Delphi, all you need to do is to replace ALL string variables in it from 'string' to 'ANSIstring', just with Ctrl+R - ':string' replace to ':ANSIstring'.
UPDATE 3
OK, finally I get it. Here is procedure which computes info_hash and shows it in hex, this requires newer version of Delphi. Also, add IdGlobal and IdHashSHA to 'uses' section.
procedure makeInfoHash(be: TBEncoded);
var SHA1: TIdHashSHA1;
s: string;
infoChunk: TBencoded;
infoEncoded: ANSIString;
bytes: TIdBytes;
begin
infoChunk:=be.ListData.FindElement('info') as TBencoded;
TBencoded.Encode(infoChunk,infoEncoded);
bytes:=RawToBytes(infoEncoded[1],Length(infoEncoded));
SHA1:=TIdHashSHA1.Create;
try
s:=SHA1.HashBytesAsHex(bytes);
finally
SHA1.Free;
end;
Form1.Memo1.Lines.Add(s);
end;
It gives correct info_hash, the same which is displayed in uTorrent, like this:
7D0487D3D99D9C27A7C09CDCBB2F2A8034D4F9BF
You must replace all string to ANSIstring in BENcode.pas, as said in update 2. Enjoy!

Related

Delphi. filewrite, error - E2036 Variable required

I didn't use filewrite a lot of time.
I made this procedure and received the next error: E2036 Variable required (on flen variable in filewrite).
procedure TForm1.WriteFN(const PIN: integer);
var
lFile: integer;
flen : integer;
begin
flen := 2;
lFile := FileOpen('/sys/pins', fmOpenWrite);
try
if filewrite(lFile, PChar(IntToStr(PIN)), flen) = -1 then
raise Exception.CreateFmt('Cannot export PIN%d', [PIN]);
finally
fileclose(lFile);
end;
end;
How to solve this problem?
Delphi Rio, Win10.
The second parameter of FileWrite() is an untyped const. Whatever you pass to it gets passed by reference, and as such you have to give it a real variable to refer to. In this case, you can simply dereference the PChar pointer, that will let the parameter reference the 1st Char in the temporary String you are creating, eg:
FileWrite(lFile, PChar(IntToStr(PIN))^, flen)
Note, however, that FileWrite() operates on raw bytes, not on string characters. You are telling FileWrite() to write exactly 2 bytes, which may or may not work properly, depending on which version of Delphi you are using, and what value the PIN contains.
Try this instead:
procedure TForm1.WriteFN(const PIN: integer);
var
lFile: integer;
flen : integer;
s: AnsiString;
begin
s := AnsiString(IntToStr(PIN));
flen := Length(s);
lFile := FileOpen('/sys/pins', fmOpenWrite);
if lFile = -1 then
raise Exception.CreateFmt('Cannot create file to export PIN%d', [PIN]);
try
if FileWrite(lFile, PAnsiChar(s)^, flen) = -1 then
raise Exception.CreateFmt('Cannot write to file to export PIN%d', [PIN]);
finally
FileClose(lFile);
end;
end;
If you are using a modern version of Delphi, consider using TFile.WriteAllText() instead:
uses
..., System.IOUtils;
procedure TForm1.WriteFN(const PIN: integer);
begin
try
TFile.WriteAllText('/sys/pins', IntToStr(PIN));
except
Exception.RaiseOuterException(
Exception.CreateFmt('Cannot export PIN%d', [PIN])
);
end;
end;

How to hash using DCPcrypt?

I have scowered the net trying to find an example of a function, how to hash text with Sha1 and DCPcrypt.
I have the below example. Seems to pop up the whole time.
But it returns chinese characters every time. Please assist in corecting the function.
function TForm1.EncryptThis(aString : string) : string;
var
Cipher: TDCP_cast256;
KeyStr: string;
begin
KeyStr:= '';
Cipher:= TDCP_cast256.Create(Self);
Cipher.InitStr(KeyStr,TDCP_sha1);
result := Cipher.EncryptString(aString);
Cipher.Burn;
Cipher.Free;
end;
UPDATE:
Using the links and info belowe, I built these functions. But as I said, This does not make alot of sense to me. So please excuse the ignorance.
THe code however does not work. Its output is: 3F3F3F3F3F3F3F3F3F3F00000000000000000000 whereas it should be 40bd001563085fc35165329ea1ff5c5ecbdbbeef since i told the program to has 123.
Please help.
function CalcDigest(text: string): string;
var
x: TDCP_hash;
begin
x := TDCP_sha1.Create(nil);
try
x.Init;
x.UpdateStr(text);
SetLength(Result, x.GetHashSize div 8);
x.Final(Result[1]);
finally
x.Free;
end;
end;
function String2Hex(const Buffer: Ansistring): string;
begin
SetLength(result, 2*Length(Buffer));
BinToHex(#Buffer[1], PWideChar(#result[1]), Length(Buffer));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
memo2.Lines.Add(String2Hex(CalcDigest(memo1.Lines.Strings[0])));
end;
Judging by this, you can do it this way:
function CalcDigest(text: string): string;
var
x: TDCP_hash;
begin
x := TDCP_sha1.Create(nil);
try
x.Init;
x.UpdateStr(text);
SetLength(Result, x.GetHashSize div 8);
x.Final(Result[1]);
finally
x.Free;
end;
end;
You may want to encode the hash before printing, because the output is binary. See for example this question.
I am not very familiar with DCPCrypt. You can also use other libraries.
1) Indy - usually included in Delphi
function SHA1Text(const s: string): string;
begin
with TIdHashSHA1.Create do
try
Result:=LowerCase(HashStringAsHex(s));
finally
Free;
end;
end;
2) Wolfgang Ehrhardt's libraries (fastest as far as I know) from
http://www.wolfgang-ehrhardt.de/crchash_en.html
function SHA1Text(const s: string): string;
var
Context: THashContext;
SHA1Digest: TSHA1Digest;
begin
SHA1Init(Context);
SHA1Update(Context, pChar(s), length(s));
SHA1Final(Context, SHA1Digest);
Result:=HexStr(#SHA1Digest, SizeOf(SHA1Digest));
end;
NOTE: it is from Delphi 7. You will need to update it if you use unicode Delphi.

Read from Stringlist as read from a File

I have a function which works on reading data from a file like :
procedure ReadDatafromFile (const aFilename : String; aparameterList : TList );
var aIniFile : TInifile;
begin
aIniFile :=TInifile.create(aFilename);
data1 := ReadInteger(......);
.....
....
end;
Now the contents of this file exists at a different location in my program as a StringList. I would like to reuse the existing code, and I would like to program it in a way like:
...
MydataStringList.SaveToFile('c:\temp\tempfile.txt');
ReadDatafromFile( MydataStringList, myparameterList);
...
Is there any smart solution to reuse the existing function and create an overloaded version which is accepting the StringList as a parameter?
That is doable:
procedure ReadDataFromStrings(aFileStrings: TStrings; aParameterList: TList);
overload;
begin
if aParameterList <> nil then
// Add aFileStrings to aParameterList
end;
procedure ReadDataFromFile(const aFileName: TFileName; aParameterList: TList);
overload;
var
Strings: TStringList;
begin
Strings := TStringList.Create;
try
Strings.LoadFromFile(aFileName);
ReadDataFromStrings(Strings, aParameterList);
finally
Strings.Free;
end;
end;
Instead of creating this local Strings variable, you could also use the already present global MydataStringList variable, whichever suits your needs.

Base64 to Binary (Delphi)

I used Binary to Base64 function that you answered :
Binary to Base64 (Delphi)
I successfully encode a file to base64 string and write it to MsSQL2008 database, but i want to ask a question:
How can i write this file to disk again with using EncdDecd.pas?
As always, David answered sufficiently. Although I can't resist to give a slightly different solution using some of the goodies from the recent Delphi versions.
procedure DecodeFile(const base64: AnsiString; const FileName: string);
var
stream: TBytesStream;
begin
stream := TBytesStream.Create(DecodeBase64(base64));
try
stream.SaveToFile(Filename);
finally
stream.Free;
end;
end;
This function will take a base64 encoded string, decode it, and write the resulting byte array to a file.
procedure DecodeToFile(const base64: AnsiString; const FileName: string);
var
stream: TFileStream;
bytes: TBytes;
begin
bytes := DecodeBase64(base64);
stream := TFileStream.Create(FileName, fmCreate);
try
if bytes<>nil then
stream.Write(bytes[0], Length(Bytes));
finally
stream.Free;
end;
end;
To explain what is happening here, the first line
bytes := DecodeBase64(base64);
performs the decode and returns the decoded binary contents of the file in a TBytes variable. TBytes is simply an array of bytes.
The next step is to create the file. The idiomatic way to write files in Delphi is to use streams. In this case we want a TFileStream.
stream := TFileStream.Create(FileName, fmCreate);
The fmCreate option means that if the file already exists, it will be replaced and overwritten by what we write.
The final step is to write the contents of the byte array to the file
if bytes<>nil then
stream.Write(bytes[0], Length(Bytes));
The if bytes<>nil check is to handle the case where the base64 string decodes to an empty array. If we were to remove that check then the following line would result in a runtime error if you were running with range checking enabled (which you should be doing). The call to stream.Write should be self-explanatory.
After looking into Soap.EncdDecd the one can find more platform independent way, as it's DecodeBase64 uses universal (no AnsiString) methods from System.NetEncoding.
Based on Uwe's sample:
uses
...
System.Classes,
System.NetEncoding;
...
procedure DecodeFile(const base64: String; const FileName: string);
var
stream: TBytesStream;
begin
stream := TBytesStream.Create(TNetEncoding.Base64.DecodeStringToBytes(base64));
try
stream.SaveToFile(Filename);
finally
stream.Free;
end;
end;
uses
Soap.EncdDecd;
function TForm1.EncodeFile(const FileName: string): AnsiString;
var
MemStream: TMemoryStream;
begin
MemStream := TMemoryStream.Create;
try
MemStream.LoadFromFile(Filename);
Result := EncodeBase64(MemStream.Memory, MemStream.Size);
finally
MemStream.Free;
end;
end;
function TForm1.DecodeFile(const base64: AnsiString): TBytesStream;
begin
Result := TBytesStream.Create(DecodeBase64(base64));
end;
I have a very old Delphi2006(v10.0.2558.35231 Update 2) and had to decode base64 UTF8 encoded input strings. I finally figured it out and heres an example for anyone interested.
Uses
IdCoderMIME; // Indy9
var
decoder: TIdDecoderMIME;
str: WideString;
- - -
decoder := TIdDecoderMIME.Create(nil);
str := base64DecodeUTF8(decoder, b64sourcestr);
decoder.Free;
- - -
function base64DecodeUTF8(decoder:TIdDecoderMIME; str:String): WideString;
var
stream:TMemoryStream;
utf8: UTF8String;
//idx:Integer;
begin
stream := TMemoryStream.Create;
try
decoder.DecodeToStream(str, stream);
setString(utf8, PChar(stream.Memory), stream.Size);
Result := UTF8Decode(utf8);
//for idx := 0 to stream.Size-1 do begin
// Writeln(PChar(stream.Memory)[idx] + ' ' + IntToStr(ORD(PChar(stream.Memory) [idx])) );
//end;
finally
stream.Free;
end;
end;

Is a dynamic array of Char allowed when the parameter type is open array of Char?

I was looking at Delphi: array of Char and TCharArray "Incompatible Types" and started experimenting. What I discovered is rather interesting.
procedure Clear(AArray: array of Integer);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := 0;
end;
var
MyArray: array of Integer;
begin
Clear(MyArray);
end.
This simple little example shows how you can pass a Dynamic Array to a procedure using an Open Array parameter. It compiles and runs exactly as expected.
procedure Clear(AArray: array of Char);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := #0;
end;
var
MyArray: array of Char;
begin
Clear(MyArray);
end.
Here is nearly identical code the only difference being it is using an array of Char rather than Integer. It does not compile. Instead the compiler spits out:
E2010 Incompatible types: 'Array' and 'Dynamic array'
Why would this be?
After searching for a while I discovered this QC report. I'm running Delphi 2009 and its still happening.
Since the documentation specifically mentions open array parameters of type Char to be compatible with dynamic arrays, this should be a bug. From 'Open Array Parameters':
function Find(A: array of Char):
Integer; [...] Note: [...] The
previous example creates a function
that takes any array of Char elements,
including (but not limited to) dynamic
arrays. [...]
You can work with this kind of array, defining your own type:
type
TCharDynArray = array of char;
procedure Clear(AArray: TCharDynArray);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := #0;
end;
procedure test;
var
MyArray: TCharDynArray;
begin
Clear(MyArray);
end;
This code will compile fine. It doesn't do anything useful of course (the AArray parameter is not set as "var", so it's copied on the stack before assigning a #0 to every item). But at least, it compiles.
In practice, I found out more easy to define or use high-level of types for dynamic arrays (like TIntegerDynArray), because at least it allows you to pass the array as reference, using a var, therefore avoiding to make a copy on stack, and make your code faster.
About the mapping to a PChar, it's usual for all dynamic arrays: you can map a TIntegerDynArray to a pointer, then use it as a PInteger or a PIntegerArray:
procedure AddInteger(var Values: TIntegerDynArray; Value: integer);
var n: integer;
begin
n := Length(Values);
SetLength(Values,n+1);
Values[n] := Value;
end;
procedure Loop(V: PInteger);
begin
if V<>nil then
while V^<>0 do begin
write(V^,' ');
inc(V); // go to next integer in array
end;
end;
var IntArray: TIntegerDynArray;
begin
Loop(pointer(IntArray)); // will display nothing, since pointer(IntArray)=nil for IntArray=[]
AddInteger(IntArray,2);
AddInteger(IntArray,3);
AddInteger(IntArray,0);
Loop(pointer(IntArray)); // will display '2 3 '
end.
The problem is the "array of char" code beeing inconsistent with "array of integer" is certainly in compiler intrinsics, and the fact that a PChar can be type-casted to a string.
I think the reason is that array of Char is compatible with PChar, as this code does compile:
procedure Clear(AArray: array of Char);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := #0;
end;
var
MyArray: array of Char;
P: PChar;
begin
Clear(P^);
end.
That is probably for historic reasons.
Hopefully Barry Kelly or Danny Thorpe will kick in and provide some more feedback on this.
--jeroen

Resources