Struktura archiwum komunikatora Tlen.pl

Znów o tlenie, nowa publikacji z serii "Tlen.pl bez tajemnic", tym razem na temat struktury archiwum komunikatora Tlen.pl.

Tlen.pl tworzy dla każdego profilu folder DataBase, umiejscowiony w katalogu danego profilu. W folderze tym są przechowywane pliki archiwum, a w nich zależnie od ustawień - wiadomości, rozmowy i smsy.

Pliki archiwum można podzielić na dwa rodzaje - pliki indeksowe i pliki danych. Pliki indeksowe, o rozszerzeniu .idx, jak sama nazwa wskazuje, składają się z indeksów przechowujących zapisane informacje o wiadomościach, rozmowach, i smsach, których treść znajduje się w plikach danych, identyfikowanych rozszerzeniem .dat.

W niniejszej publikacji postaram się omówić budowę archiwum.

Wszystkie wartości liczbowe zapisane są w formie Little-Endian, czyli zgodnie z kolejnością bajtów maszyn Intela.

Przy opisie struktur, założono, że wyrównanie do granicy rozmiaru słowa wynosi 1 bajt. Przez co należy wyłączyć w kompilatorze domyślnie włączone wyrównanie zmiennych do rozmiaru słowa danej architektury. Należy zwrócić uwagę na rozmiar typów zmiennych i kolejność bajtów.

Myślę, że nie ma potrzeby opisywania przeznaczenia poszczególnych pól zawartych w przedstawianych strukturach, ich nazwy i zawarte komentarze "mówią" same za siebie.

Pola o nieznanym lub nie do końca jasnym znaczeniu, oznaczono przedrostkiem unknown.

Jako, iż tlen.pl tworzony jest z wykorzystaniem VCLa, wszelkie dane na temat daty i czasu zostały zapisane w Borlandowskim formacie TDateTime. Informacje o prostej konwersji na bardziej ludzki format – uniksowy znacznik czasu można znależć tutaj: Convert TDateTime to Unix TimeStamp.

Wszelkie wartości offset, występujące w strukturach są liczone względem początku pliku.

Rozmowy

Rozmowy archiwizowane są w plikach chats.idx i chats.dat.

Plik indeksu zawiera listę wszystkich przeprowadzonych rozmów. Lista składa się z rekordów, które przechowują informacje o rozmowie, każdy rekord reprezentowany jest przez strukturę:

struct IDXchat {
	char name[26];		// nazwa rozmowcy
	char network[6];	// nazwa sieci (nieobsługiwane)
	double time;		// czas w TDateTime
	int flags;			// flagi
	int offset;			// pozycja pierwszej wypowiedzi
	int count;			// ilosc wypowiedzi
	int ID;				// ID rozmowy
};

Rozmiar tej struktury wynosi 56 bajtów.

Plik danych - chats.dat składa się z rekordów tworzących listę, gdzie każdy rekord reprezentuje jedną wypowiedz z rozmowy. Każda wypowiedz zapisana jest w formie struktury:

struct DATchat {
	double time;		// czas wypowiedzi w TDateTime
	int flags;			// flagi
	int size;			// rozmiar wypowiedzi
	int ID;				// ID rozmowy
	int unknown;
	char msg[];			// tresc wypowiedzi
};

Struktura ta ma rozmiar równy 24 bajty + size.

Flagi w powyższych strukturach opisują dodatkowe właściwości. Obecnie dla każdej po jednej:

#define ARCHIVE_CHAT_FLAG_SELECT	0x00001		// IDXchat / zaznaczona do usuniecia
#define ARCHIVE_CHAT_FLAG_SEND		0x00001		// DATchat / wyslana przez nas

Wzajemna relacja miedzy indeksem rozmowy a jej wypowiedziami identyfikowana jest na podstawie ID. Przy czym wypowiedzi danej rozmowy nie są zapisane w jednym ciągu kolejnych po sobie rekordów, a "rozrzucone" po całym pliku.

Wiadomości

Wiadomości przechowywane są w plikach msgs.idx i msgs.dat.

Plik indeksowy wiadomości, podobnie jak w przypadku rozmów, jest listą wszystkich odebranych i wysłanych wiadomości. Każda rozmowa zapisana jest w postaci poniższej, 56-bajtowej struktury:

struct IDXmsg {
	char name[26];		// nazwa nadawcy
	char network[6];	// nazwa sieci (nieobsługiwane)
	double time;		// czas wiadomosci w TDateTime
	int flags;			// flagi
	int offset;			// pozycja wiadomosci
	int size;			// rozmiar wiadomosci
	int unknown;
};

Flagi opisują dodatkowe właściwości wiadomości. Obecnie istnieją tylko dwie:

#define ARCHIVE_MSG_FLAG_SEND		0x00001		// wyslana przez nas
#define ARCHIVE_MSG_FLAG_SELECT		0x10000		// zaznaczona do usuniecia

Plik danych jest zwykłym plikiem tekstowym zawierającym treści wszystkich wiadomości oddzielonych od siebie spacją. Na podstawie danych zawartych w strukturach IDXmsg - offset i size można w łatwy sposób przypisać daną treść do konkretnej struktury wiadomości, odwzorowując tym samym pełną, właściwą wiadomość.

SMSy

Historia wysłanych wiadomości smsowych zawarta jest w dwóch plikach - sms.idx i sms.dat. Ich budowa jest zbliżona do budowy plików przechowywujących archiwum wiadomości.

Tak, jak pozostałe pliki indeksowe archiwum tlena, również sms.idx zawiera informacje dotyczące wszystkich wysłanych smsach w postaci listy odpowiednich struktur o rozmiarze 40 bajtów. Struktury te mają następującą budowę:

struct IDXsms {
	char tel[12];		// nr telefonu
	int unknown1;
	double time;		// czas wyslania w TDateTime
	int flags;			// flagi
	int offset;			// pozycja wiadomosci
	short size;			// rozmiar wiadomosci
	short recv;			// odebrany sms
	int unknown2;
};

Flagi opisują dodatkowe właściwości smsa:

#define ARCHIVE_SMS_FLAG_SELECT		0x00001		// zaznaczony do usuniecia

Plik danych sms.dat zawierający treści poszczególnych wiadomości sms, jest, tak jak w przypadku msgs.dat, zwykłym plikiem tekstowy. Treści oddzielone są spacją, a dostęp do nich i identyfikacja następuje na podstawie danych zawartych w indeksach.

Przy usuwaniu rozmów, wiadomości czy smsów z archiwum, tlen od razu nie usuwa fizycznie danych z plików archiwum. Specjalnie oznacza usunięte rekordy w plikach indeksowych, co by nie były brane pod uwagę przy wyświetlaniu. Fizyczne usuniecie następuje dopiero w czasie wykonywania kompaktowania archiwum.

Istnieje, więc możliwość odzyskania treści usuniętych wiadomości i smsów, a w przypadku rozmów teoretycznie jest możliwość pełnego odtworzenia rozmowy.

Oznaczenie usuniętych rekordów jest rozpoznawane po wartości pól time i size, time zostaje wyzerowane, a wartość size ustawiona na -1, pozostałe pola zawierają niezidentyfikowane dane.

Nie należy tutaj mylić "usuniętych" z polem select, które służy do czegoś zupełnie innego - informuje ono o "zaznaczeniu do usunięcia" danej pozycji w archiwum tlena.

To byłoby wszystko na temat struktury archiwum tlena.

W wolnym czasie postaram się napisać i udostępnić kilka przykładowych programów operujących na plikach archiwum. Obecnie tylko jeden "ciekawszy" program, na jaki trafiłem całkiem przypadkiem, można znaleźć na forum ekipa.tlen.pl w temacie Dostep do archiwum tlena. Niestety napisany w Delphi :(.

15 thoughts on “Struktura archiwum komunikatora Tlen.pl”

  1. Niektore z tych problemow pewnie zostana opisane w Nieoficjalnej dokumentacji.
    Nie wiem jak z NATem, ale gdzies mialem jakis kod do p2p, tyle ze tej starszej wersji.
    Poki co, obecnie nie mam zbytnio czasu na to ;)

  2. W Tlen dla Miranda jest pełen protokół przesyłania plików w wersji bez NAT, jak i rozmowy głosowe, ale przydało by się jeszcze opracować tych kilka dodatków.

    Ach, zanim zapomnę, gdzie można dostać tę bibliotekę lint? W sieci nigdzie jej nie ma. Czy jest dostępna tylko z tą książką?

  3. Cześć Marcin,

    czytałem dzisiaj Twój wpis na blogu sprzed 14 lat(!) i mam pytanie w jego kontekście. Kończysz wpis tak:

    W wolnym czasie postaram się napisać i udostępnić kilka przykładowych programów operujących na plikach archiwum. Obecnie tylko jeden „ciekawszy” program, na jaki trafiłem całkiem przypadkiem, można znaleźć na forum ekipa.tlen.pl w temacie Dostep do archiwum tlena. Niestety napisany w Delphi :(.

    Napisałeś te kilka przykładowych programów? Albo masz może jeszcze gdzieś (najlepiej skompilowany) program z forum ekipa.tlen.pl?

    Zebrało mi się na wspominki i chciałem poczytać swoje dawne rozmowy z Tlenu, ale poza napisaniem własnego programu (dzięki za taki szczegółowy opis – w ostateczności może coś wyrzeźbię (chociaż programistą nie jestem)), pozostaje mi instalacja tlenu i podmiana plików `chats.dat` i `chats.idx`. Jednak sam komunikator nie za wiele oferuje w kontekście obsługi archiwum. Brak możliwości eksportowania rozmów z danym kontaktem, etc.

    Podsumowując – dzięki za ten wpis sprzed ponad dekady, a jeśli masz jakiś program do obsługi `chats.dat` i chcesz się nim podzielić, daj znać.

    Pozdrawiam,
    Robert

    1. Niestety na szybko nic nie udało mi się znaleźć…

      Wydaje mi się, że najwygodniej archiwum poprzeglądać prosto z Tlenu (jeśli jest taka możliwość), ale tak jak piszesz chyba nie ma tam żadnej opcji do eksportu prócz ręcznego kopiowania konwersacji.

      Bazując na moim opisie bez problemu dałoby się zrobić jakiś eksporter (program, skrypt), który przemieliłby te pliki i wypluł ich zawartość w jakimś bardziej aktualnym formacie – txt, xml, json.

    2. Dzięki za odpowiedź!

      [Wolałem napisać tu, bo nie byłem pewny czy dostajesz powiadomienia o nowych komentarzach w starych wpisach.
      Jasne, jak najbardziej przenieś to pod tamten wpis.
      BTW. dodawanie wpisów coś kuleje – kilkukrotnie miałem informację o tym, że nie mogę dodać wpisu, bo jest duplikacją.]

      Gdybyś znalazł kiedyś przez przypadek czy to skrypt, czy cokolwiek innego – to jeśli byś mógł – podrzuć mi na maila (z góry dzięki). Nawet jakiś draft – łatwiej będzie mi samemu coś wypłodzić…

      Właśnie, co do napisania czegoś własnego – Twój opis jest super, ale ja nie jestem doświadczonym programistą (właściwie w ogóle nie jestem programistą – czasem coś pokoduje hobbystycznie), więc nie wiem czy dam radę. Ale na pewno spróbuję. Mam tam połowę swojego młodzieńczego życia :)

      Wczoraj czytałem te archiwum właśnie poprzez odpalenie Tlena – oj uśmiałem się z siebie (wiesz taka podróż 15 lat wstecz :) ).

      Pozdrawiam,
      Robert

    3. Jakbym na coś trafił to dam znać…
      Po uporządkowaniu bloga i migracji może będę w wolnych chwilach porządkował projekty i kody, i jakby coś się znalazło to pewnie tutaj o tym wspomnę.

      Mam podobną podroż w czasie, jak przeglądam tutaj stare wpisy ;)

      [Problemy z dodawaniem mogą wynikać z WP i jego „zabezpieczeń” z WP, ciężko powiedzieć co nie hula, za niedługo będę już statycznie ;)]

  4. A pamiętasz może jakie kodowanie było plików chats.idx/dat – bo kiedy potraktować je zwykłym cat to oprócz oczekiwanej treści wypluwa różne dziwne znaki (właśnie jakby kodowanie było nie takie).
    Na przykład:
    ” Ola=-��;��@���…. ”

    Czy źle w ogóle do tego się zabieram i nie da się w ten sposób tego czytać?

  5. Nie wiem jak się dobrać do struktur, które nie mają zdefiniowanej wielkości (struct datChat i jego msg – char [] )

    chats.idx można wyeksportować bardzo łatwo przy użyciu Pythona (nie znam tego języka – ale od czego jest Internet – kiedyś bym szukał rozwiązania tydzień po bibliotekach, dziś kilka trafnych zapytań do google’a):

    from ctypes import *

    class ChatsStruct(Structure):
    _fields_ = [(‚name’, c_char*26),
    (‚network’, c_char*6),
    (‚time’, c_double),
    (‚flags’, c_int),
    (‚offset’, c_int),
    (‚count’, c_int),
    (‚ID’, c_int)]
    with open(‚chats.idx’, ‚rb’) as file:
    result = []
    x = ChatsStruct()
    while file.readinto(x) == sizeof(x):
    result.append((„Name:”, x.name, „Time:”, x.time, „Offset:”, x.offset, „Count:”, x.count, x.ID))
    print (*result, sep = „\n”)

    Brakuje tu konwersji czasu – ale tym się zajmę na końcu.

    Niestety – skoro msg (w chats.dat) nie ma z góry wskazanej wielkości – muszę pogłówkować, jak to ugryźć.

  6. Dobra, może będę próbował to w C++ ugryźć (kiedyś coś pisałem w tym języku).

    Jako pierwszy poddałem obróbce chats.idx – wyświetliłem wszystkie imiona (name) wraz z pozycją pierwszej wypowiedzi (offset). Zrobiłem to tak:
    (Nie wiem czy masz włączony bbcode)

    [CODE]
    #include
    #include

    using namespace std;

    typedef struct IDXchat {
    char name[26];
    char network[6];
    double time;
    int flags;
    int offset;
    int count;
    int ID;
    }IDXchat;

    fstream binaryFile(„d:\\tempTlen\\chats.idx”, ios::binary | ios::in | ios::ate);
    int size = binaryFile.tellg();
    for (int i = 0; i < size / sizeof(IDXchat); i++) {
    IDXchat idxData;
    binaryFile.seekg(i * sizeof(IDXchat));
    binaryFile.read(reinterpret_cast(&idxData), sizeof(IDXchat));

    cout << idxData.name < ” << idxData.offset << endl;
    }
    binaryFile.close();

    return 0;
    [/CODE]

    Ale nadal nie wiem, jak się zabrać za strukturę DATchat – tam struktura ma zmienny rozmiar, a msg ma tablicę znaków o nieznanej długości.

    W związku z tym, że jesteś programistą, może dasz jakąś małą podpowiedź…

  7. To znowu ja.
    Poradziłem sobie ze zmienną wielkością struktury – po prostu zrobiłem spacer po bajtach ze sczytywaniem wielkości msg (tym razem w Javie ;) – ten język znam najlepiej (ale też tylko hobbystycznie)).

    Fragment kodu

    Ale napotkałem kolejny problem – w połowie pliku dat pojawiają się same zera (około 50 bajtów), a potem kolejne zapisy – ale jakby już z pominięciem przedstawionej przez Ciebie struktury.

    Czy to możliwe, że w czasie struktura się zmieniała, czy może raczej plik jest uszkodzony (to wydaje się dziwne – bo mam dwa różne archiwa – z dwóch różnych urządzeń – i w obu występuje ten sam problem :( ) ?

    1. te puste miejsca to moga byc jakies usuniete rozmowy, ale dziwne, bo wtedy i tak struktura powinna zostac zachowana, aby dalo sie bezproblemowow „łazic” po rekordach w pliku.

  8. Rozszyfrowałem offset (z chats.idx) – nie był on do końca oczywisty (przynajmniej dla mnie).

    Offset to numer bajtu, na którym zaczyna się pierwszy rekord DATchat wiadomości o ID wskazanym w rekordzie IDXchat. Hmm – nadal nie brzmi to jasno…

    Może na obrazku, będzie jaśniej.

    Marcin, nie wiem czy to nie jest zaśmiecanie Twojego forum (nie mogę edytować wcześniejszych wypowiedzi) – więc jeśli mam przerwać swoją pisaninę, daj znać – bo pewnie jeszcze trochę tu będę męczył…

    1. Spoko, usunę zbędne komentarze, jak wrzucę nową wersje bloga wkrótce ;)
      Albo posklejam co ciekawsze fragmenty, dla potomnych.

      Co do offsetu to tak jak myślisz, pozycja względem początku pliku – tutaj bajtowo. Obrazek dobrze przedstawia Twoje myśli.

      Dane rekordy (wypowiedzi rozmów) porozrzucane są po pliku, dlatego ogólnie cała idea zabawy z tym w większości przypadków wymaga wczytania wszystkiego do pamięci i odpowiedniego przetworzenia/ zapakowania w struktury.
      Na przykład jakieś listy i wektory dla wypowiedzi posegregowane pod identyfikatorach rozmów, co pozwoli szybko dostać się do danej rozmowy etc. Opcji jest wiele… ;)

      Być może ten `unknown` w `DATchat` coś znaczy, na przykład pozycje/ofsset do kolejnej wypowiedzi, ale chyba nigdy nie wpadłem na pomysł aby cos takiego sprawdzić. Wtedy dałoby się sekwencyjnie „lecieć” po całej wybranej rozmowie skacząc po pliku, bez czytania całości i sortowania po czasie. Ale jak mówiłem, nigdy tego nie sprawdziłem, teraz przyszło mi to do głowy.

      Może gdybym miał więcej czasu to bym się tym pobawił, ale nie mam aktualnie ani czasu za dużo, ani potrzeby zabawy z tym. Chociaż potraktowanie to jako małe i szybkie zadanko dla zabawy nie byłoby chyba głupim pomysłem…

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *