Skip to content

Instantly share code, notes, and snippets.

@LFriede
Created March 5, 2017 12:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LFriede/84ffced3b6c2df1c873a6d366be77e6c to your computer and use it in GitHub Desktop.
Save LFriede/84ffced3b6c2df1c873a6d366be77e6c to your computer and use it in GitHub Desktop.
Teamspeak 3 Bookmark decoder
unit ts3bookmarks;
interface
uses
System.SysUtils, Classes, Windows, ShlObj, pbInput, pbPublic;
type
TFavorit = record
Name:String;
Host:String;
Port:Word;
IconPath:String;
end;
function ParseProtobufBookmark(bytes:TArray<Byte>; var item:TFavorit):Boolean;
implementation
function ChangeEndian(value:Word):Word; overload;
asm
{$IFDEF CPUX64}
mov rax,rcx
{$ENDIF}
XCHG al,ah
end;
function ChangeEndian(value:Integer):Integer; overload;
asm
{$IFDEF CPUX64}
mov rax,rcx
{$ENDIF}
bswap eax
end;
function ReadStr(f:TFileStream; len:Integer):string;
var
I:Integer;
w:Word;
begin
Result := '';
for I := 0 to len-1 do begin
f.Read(w, 2);
Result := Result + Chr(ChangeEndian(w));
if f.Position >= f.Size then break;
end;
end;
function SearchIconCache(IconID:string):string;
var
f:TFileStream;
len:Integer;
id, s:string;
Buffer:Array[0..MAX_PATH] Of Char;
begin
Result := '';
try
SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, @Buffer[0]);
f := TFileStream.Create(Buffer+'\TS3Client\cache\servericons.dat', fmOpenRead);
f.Seek(12, soFromBeginning);
while (f.Position < f.Size) do begin
f.Read(len, 4);
len := ChangeEndian(len) shr 1;
id := ReadStr(f, len);
f.Read(len, 4);
if (len <> -1) then begin
len := ChangeEndian(len) shr 1;
s := ReadStr(f, len);
if (id = IconID) then begin
Result := StringReplace(s, '/', '\', [rfReplaceAll]);
f.Free;
Exit;
end;
end;
end;
f.Free;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end;
function ParseProtobufBookmark(bytes:TArray<Byte>; var item:TFavorit):Boolean;
var
Buffer:TProtoBufInput;
tag, wireType, fieldNumber, msgtype:Integer;
msg:AnsiString;
IconID:String;
begin
Result := False;
item.IconPath := '';
Buffer := nil;
try
Buffer := TProtoBufInput.Create(PAnsiChar(@bytes[0]), Length(bytes));
msgtype := -1;
msg := '';
tag := Buffer.readTag;
while tag <> 0 do begin
wireType := getTagWireType(tag);
fieldNumber := getTagFieldNumber(tag);
case fieldNumber of
6: begin
Assert(wireType = WIRETYPE_VARINT);
msgtype := Buffer.readInt32;
end;
16: begin
Assert(wireType = WIRETYPE_LENGTH_DELIMITED);
msg := Buffer.readString;
end
else Buffer.skipField(tag);
end;
if ((msgtype = 0) and (Length(msg) > 0)) then begin
Result := True;
Buffer.Free;
Buffer := TProtoBufInput.Create(PAnsiChar(msg), Length(msg));
tag := Buffer.readTag;
while tag <> 0 do begin
wireType := getTagWireType(tag);
fieldNumber := getTagFieldNumber(tag);
case fieldNumber of
1: begin
// Name
Assert(wireType = WIRETYPE_LENGTH_DELIMITED);
try
item.Name := string(Buffer.readString);
except
item.Name := '';
end;
end;
// Host
2: begin
Assert(wireType = WIRETYPE_LENGTH_DELIMITED);
try
item.Host := string(Buffer.readString);
except
item.Name := '';
end;
end;
// Port
3: begin
Assert(wireType = WIRETYPE_VARINT);
item.Port := Buffer.readInt32;
end;
// IconID
15: begin
Assert(wireType = WIRETYPE_LENGTH_DELIMITED);
try
IconID := string(Buffer.readString);
item.IconPath := SearchIconCache(IconID);
except
end;
end;
else Buffer.skipField(tag);
end;
tag := Buffer.readTag;
end;
Break;
end;
tag := Buffer.readTag;
end;
if (item.Name = '') then begin
with item do begin
Name := Host + ':' + IntToStr(Port);
end;
end;
finally
if (Buffer <> nil) then Buffer.Free;
end;
end;
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment