ICQDb.pas
上传用户:hndmjx
上传日期:2014-09-16
资源大小:3369k
文件大小:29k
源码类别:

Delphi控件源码

开发平台:

Delphi

  1. unit ICQDb {v 1.17};
  2. {(C) Alex Demchenko(alex@ritlabs.com)}
  3. {$R-}
  4. interface
  5. uses
  6.   Windows, Messages, ICQWorks, SysUtils, Classes;
  7. const
  8.   {Database versions}
  9.   DB_99A = 10;   {99a}
  10.   DB_99B = 14;   {99b}
  11.   DB_2000a = 17; {2000a}
  12.   DB_2000b = 18; {2000b}
  13.   DB_2001a = 19; {2001a, 2001b, 2002a}
  14.   DB_MIRANDA121 = $00000700; {Miranda 1.2.1}
  15.   {Error constants}
  16.   ERR_FILEOPEN = $100;          {Could not open .idx or .dat file}
  17.   ERR_NOTICQDB = $101;          {Not an ICQ database}
  18.   ERR_DBVERNOTSUPPORTED = $102; {Database version not supported}
  19. const
  20.   {Miranda-icq signatures}
  21.   DBHEADER_SIGNATURE: array[0..15] of Char = ('M', 'i', 'r', 'a', 'n', 'd', 'a', ' ',   'I', 'C', 'Q', ' ', 'D', 'B', #$00, #$1a);
  22.   DBCONTACT_SIGNATURE: LongWord         = $43DECADE;
  23.   DBMODULENAME_SIGNATURE: LongWord      = $4DDECADE;
  24.   DBCONTACTSETTINGS_SIGNATURE: LongWord = $53DECADE;
  25.   DBEVENT_SIGNATURE: LongWord           = $45DECADE;
  26.   {Miranda-icq data types}
  27.   DBVT_DELETED = 0;    //this setting just got deleted, no other values are valid
  28.   DBVT_BYTE    = 1;    //bVal and cVal are valid
  29.   DBVT_WORD    = 2;    //wVal and sVal are valid
  30.   DBVT_DWORD   = 4;    //dVal and lVal are valid
  31.   DBVT_ASCIIZ  = 255;  //pszVal is valid
  32.   DBVT_BLOB    = 254;  //cpbVal and pbVal are valid
  33.   DBVTF_VARIABLELENGTH = $80;
  34.   {Miranda-icq database flags}
  35.   DBEF_FIRST   = 1;    //this is the first event in the chain;
  36.   DBEF_SENT    = 2;    //this event was sent by the user. If not set this
  37.   DBEF_READ    = 4;    //event has been read by the user. It does not need
  38.   {Miranda-icq event types}
  39.   EVENTTYPE_MESSAGE     = 0;           //Message
  40.   EVENTTYPE_URL         = 1;           //URL
  41.   EVENTTYPE_ADDED       = 1000;        //v0.1.1.0+: these used to be module-
  42.   EVENTTYPE_AUTHREQUEST = 1001;        //specific codes, hence the module-
  43.   EVENTTYPE_FILE        = 1002;        //specific limit has been raised to 2000
  44. type
  45.   TOnErrorEvent = procedure(Sender: TObject; Reason: Word) of object;
  46.   TOnProgress = procedure(Sender: TObject; Progress: Byte) of object;
  47.   TOnContact = procedure(Sender: TObject; UIN: LongWord; NickName, FirstName, LastName,
  48.     Email: String; Age, Gender: Byte; LastUpdate: String; LastUpdateStamp: LongWord) of object;
  49.   TOnSelfInfo = procedure(Sender: TObject; UIN: LongWord; NickName, FirstName, LastName,
  50.     Email, Password: String; Age, Gender: Byte; LastUpdate: String; LastUpdateStamp: LongWord) of object;
  51.   TOnMessage = procedure(Sender: TObject; UIN: LongWord; Incoming: Boolean; Msg, RecvTime: String; RecvTimeStamp: LongWord) of object;
  52.   TOnUrl = procedure(Sender: TObject; UIN: LongWord; Incoming: Boolean; Description, URL, RecvTime: String; RecvTimeStamp: LongWord) of object;
  53.   TOnAdvMessage = procedure(Sender: TObject; UIN: LongWord; Incoming: Boolean; PlainText, RichText, UTF8Text, RecvTime: String; RecvTimeStamp: LongWord) of object;
  54.   {Index record}
  55.   TIdxRec = record
  56.     Code,                                       //If entry is valid the it's set to -2
  57.     Number,                                     //DAT entry number
  58.     Next,                                       //Next IdxRec offset
  59.     Prev,                                       //Previous IdxRec offset
  60.     DatPos: LongInt;                            //Offset in .dat file
  61.   end;
  62.   {Dat header record}
  63.   TDatRec = record
  64.     Length,
  65.     FillType,
  66.     Number: LongInt;
  67.     Command: Byte;
  68.     Signature: array[0..14] of Byte;
  69.   end;
  70.   {Miranda .dat header}
  71.   TMirandaHdr = record
  72.     Signature: array[0..15] of Byte;
  73.     Version: LongWord;
  74.     ofsFileEnd: LongWord;
  75.     slackSpace: LongWord;
  76.     contactCount: LongWord;
  77.     ofsFirstContact: LongWord;
  78.     ofsUser: LongWord;
  79.     ofsFirstModuleName: LongWord;
  80.   end;
  81.   {Miranda's contact entry}
  82.   TMirandaContact = record
  83.     Signature: DWord;
  84.     ofsNext: DWord;
  85.     ofsFirstSettings: DWord;
  86.     eventCount: DWord;
  87.     ofsFirstEvent, ofsLastEvent: DWord;
  88.     ofsFirstUnreadEvent: DWord;
  89.     timestampFirstUnread: DWord;
  90.   end;
  91.   {Miranda's contact settings}
  92.   TDBContactSettings = record
  93.     Signature: LongWord;
  94.     ofsNext: LongWord;
  95.     ofsModuleName: LongWord;
  96.     cbBlob: LongWord
  97.   end;
  98.   {Miranda's event}
  99.   TDBEvent = packed record
  100.     Signature: LongWord;
  101.     ofsPrev: LongWord;
  102.     ofsNext: LongWord;
  103.     ofsModuleName: LongWord;
  104.     Timestamp: LongWord;
  105.     Flags: LongWord;
  106.     eventType: Word;
  107.     cbBlob: LongWord;
  108.   end;
  109.   {Component}
  110.   TICQDb = class(TComponent)
  111.   private
  112.     FIdxFile, FDatFile: String;
  113.     FHandle: THandle;                           //Main .idx file handle
  114.     FDHandle: THandle;                          //Main .dat file handle
  115.     FIdxRoot: LongWord;                         //Root .idx entry
  116.     FIdxEntries: LongWord;                      //Count of idx entries
  117.     FDbVersion: LongWord;                       //Database version extracted from .idx file
  118.     FMirandaHdr: TMirandaHdr;
  119.     {-=-=-=-=-}
  120.     FOnError: TOnErrorEvent;
  121.     FOnParsingStarted: TNotifyEvent;
  122.     FOnParsingFinished: TNotifyEvent;
  123.     FOnProgress: TOnProgress;
  124.     FOnContact: TOnContact;
  125.     FOnSelfInfo: TOnSelfInfo;
  126.     FOnMessage: TOnMessage;
  127.     FOnURL: TOnUrl;
  128.     FOnAdvMessage: TOnAdvMessage;
  129.     FDbType: TDbType;
  130.     function ReadInt(Handle: THandle; Len: ShortInt): LongWord;
  131.     function ReadBuf(Handle: THandle; Len: LongWord; var Buf): LongWord;
  132.     function ReadStr(Handle: THandle; Len: LongWord): String;
  133.     function ReadLNTS(Handle: THandle): String;
  134.     procedure Skip(Handle: THandle; Len: LongWord);
  135.     function Seek(Handle: THandle; Pos: LongWord): Boolean;
  136.     function GetPos(Handle: THandle): LongWord;
  137.     function OpenIdx(const FileName: String): Boolean;
  138.     procedure CloseIdx;
  139.     function OpenDat(const FileName: String): Boolean;
  140.     procedure CloseDat;
  141.     function ReadHeader: Boolean;
  142.     function ReadIdxChunk(var IdxRec: TIdxRec): Boolean;
  143.     procedure ParseIndexes;
  144.     procedure ParseDatEntry;
  145.     procedure ParseMirandaDatFile;
  146.   public
  147.     constructor Create(AOwner: TComponent); override;
  148.     destructor Destroy; override;
  149.     procedure StartParsing;
  150.   published
  151.     property IdxFile: String read FIdxFile write FIdxFile;
  152.     property DatFile: String read FDatFile write FDatFile;
  153.     property OnError: TOnErrorEvent read FOnError write FOnError;
  154.     property DbVersion: LongWord read FDbVersion;
  155.     property OnParsingStarted: TNotifyEvent read FOnParsingStarted write FOnParsingStarted;
  156.     property OnParsingFinished: TNotifyEvent read FOnParsingFinished write FOnParsingFinished;
  157.     property OnProgress: TOnProgress read FOnProgress write FOnProgress;
  158.     property OnContactFound: TOnContact read FOnContact write FOnContact;
  159.     property OnSelfInfoFound: TOnSelfInfo read FOnSelfInfo write FOnSelfInfo;
  160.     property OnMessageFound: TOnMessage read FOnMessage write FOnMessage;
  161.     property OnURLFound: TOnUrl read FOnUrl write FOnUrl;
  162.     property OnAdvMessageFound: TOnAdvMessage read FOnAdvMessage write FOnAdvMessage;
  163.     property DbType: TDbType read FDbType write FDbType;
  164.   end;
  165. function DbErrorToStr(Error: Word): String;
  166. procedure Register;
  167. implementation
  168. function TimeStamp2Str(Timestamp: LongWord): String;
  169. var
  170.   DelphiTime: Double;
  171. begin
  172.   DelphiTime := EncodeDate(1970, 1, 1) + (TimeStamp / 86400);
  173.   Result := DateTimeToStr(DelphiTime);
  174. end;
  175. constructor TICQDb.Create;
  176. begin
  177.   inherited;
  178.   FHandle := INVALID_HANDLE_VALUE;
  179.   FDHandle := INVALID_HANDLE_VALUE;
  180. end;
  181. destructor TICQDb.Destroy;
  182. begin
  183.   CloseIdx;
  184.   CloseDat;
  185.   inherited;
  186. end;
  187. procedure TICQDb.StartParsing;
  188. begin
  189.   if DbType = DB_ICQ then
  190.   begin
  191.     if (not OpenIdx(FIdxFile)) or (not OpenDat(FDatFile)) then
  192.     begin
  193.       if Assigned(OnError) then
  194.         FOnError(Self, ERR_FILEOPEN);
  195.       Exit;
  196.     end;
  197.   end else
  198.   begin
  199.     if not OpenDat(FDatFile) then
  200.     begin
  201.       if Assigned(OnError) then
  202.         FOnError(Self, ERR_FILEOPEN);
  203.       Exit;
  204.     end;
  205.   end;
  206.   if not ReadHeader then
  207.   begin
  208.     if Assigned(OnError) then
  209.       FOnError(Self, ERR_NOTICQDB);
  210.     Exit;
  211.   end;
  212.   if (FDbVersion <> DB_2001a) and (FDbVersion <> DB_2000a) and
  213.      (FDbVersion <> DB_2000b) and (FDbVersion <> DB_MIRANDA121)
  214.   then
  215.   begin
  216.     if Assigned(OnError) then
  217.       FOnError(Self, ERR_DBVERNOTSUPPORTED);
  218.      Exit;
  219.   end;
  220.   if FDbType <> DB_MIRANDA then
  221.     ParseIndexes
  222.   else if FDbType = DB_MIRANDA then
  223.     ParseMirandaDatFile;
  224. end;
  225. function TICQDb.ReadInt(Handle: THandle; Len: ShortInt): LongWord;
  226. var
  227.   buf: array[0..3] of Byte;
  228.   read: LongWord;
  229. begin
  230.   Result := 0;
  231.   if (Len < 0) or (Len > 4) then
  232.     Exit;
  233.   FillChar(buf, SizeOf(buf), 0);
  234.   ReadFile(Handle, buf, Len, read, nil);
  235.   if read < 1 then Exit;
  236.   Result := PLongWord(@buf)^;
  237. end;
  238. function TICQDb.ReadBuf(Handle: THandle; Len: LongWord; var Buf): LongWord;
  239. begin
  240.   if Len = 0 then Exit;
  241.   ReadFile(Handle, Buf, Len, Result, nil);
  242. end;
  243. function TICQDb.ReadStr(Handle: THandle; Len: LongWord): String;
  244. var
  245.   buf: Pointer;
  246.   read: LongWord;
  247. begin
  248.   Result := '';
  249.   GetMem(buf, Len);
  250.   if Len = 0 then Exit;
  251.   ReadFile(Handle, buf^, Len, read, nil);
  252.   if read < 1 then
  253.   begin
  254.     FreeMem(buf);
  255.     Exit;
  256.   end;
  257.   Result := Copy(PChar(buf), 0, Len);
  258.   FreeMem(buf);
  259. end;
  260. function TICQDb.ReadLNTS(Handle: THandle): String;
  261. begin
  262.   Result := ReadStr(Handle, ReadInt(Handle, 2));
  263. end;
  264. procedure TICQDb.Skip(Handle: THandle; Len: LongWord);
  265. begin
  266.   SetFilePointer(Handle, SetFilePointer(Handle, 0, nil, 1) + Len, nil, 0)
  267. end;
  268. function TICQDb.Seek(Handle: THandle; Pos: LongWord): Boolean;
  269. begin
  270.   Result := SetFilePointer(Handle, Pos, nil, 0) <> LongWord(-1);
  271. end;
  272. function TICQDb.GetPos(Handle: THandle): LongWord;
  273. begin
  274.   Result := SetFilePointer(Handle, 0, nil, 1);
  275. end;
  276. function TICQDb.OpenIdx(const FileName: String): Boolean;
  277. begin
  278.   Result := False;
  279.   CloseIdx;
  280.   FHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_ALWAYS, 0, 0);
  281.   if FHandle = INVALID_HANDLE_VALUE then Exit;
  282.   if SetFilePointer(FHandle, 0, nil, 0) = LongWord(-1) then
  283.   begin
  284.     CloseIdx;
  285.     Exit;
  286.   end;
  287.   Result := True;
  288. end;
  289. procedure TICQDb.CloseIdx;
  290. begin
  291.   if FHandle <> INVALID_HANDLE_VALUE then
  292.     CloseHandle(FHandle);
  293.   FHandle := INVALID_HANDLE_VALUE;
  294. end;
  295. function TICQDb.OpenDat(const FileName: String): Boolean;
  296. begin
  297.   Result := False;
  298.   CloseDat;
  299.   FDHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_ALWAYS, 0, 0);
  300.   if FDHandle = INVALID_HANDLE_VALUE then Exit;
  301.   if SetFilePointer(FDHandle, 0, nil, 0) = LongWord(-1) then
  302.   begin
  303.     CloseDat;
  304.     Exit;
  305.   end;
  306.   Result := True;
  307. end;
  308. procedure TICQDb.CloseDat;
  309. begin
  310.   if FDHandle <> INVALID_HANDLE_VALUE then
  311.     CloseHandle(FDHandle);
  312.   FDHandle := INVALID_HANDLE_VALUE;
  313. end;
  314. function TICQDb.ReadHeader: Boolean;
  315. var
  316.   Size: LongWord;
  317. begin
  318.   Result := False;
  319.   if DbType = DB_ICQ then
  320.   begin
  321.     Size := FileSize(FIdxFile);
  322.     if Size <> INVALID_FILE_SIZE then
  323.       FIdxEntries := (Size - 20) div (SizeOf(TIdxRec) shl 4)
  324.     else
  325.       Exit;
  326.     if FHandle = INVALID_HANDLE_VALUE then Exit;
  327.     if (ReadInt(FHandle, 4) <> 4) or (ReadInt(FHandle, 4) <> 20) or
  328.        (ReadInt(FHandle, 4) <> 8) then
  329.       Exit;
  330.     FIdxRoot := ReadInt(FHandle, 4);
  331.     FDbVersion := ReadInt(FHandle, 4);
  332.   end else
  333.   begin
  334.     Size := FileSize(FDatFile);
  335.     if Size = INVALID_FILE_SIZE then Exit;
  336.     if ReadBuf(FDHandle, SizeOf(TMirandaHdr), FMirandaHdr) <> SizeOf(TMirandaHdr) then Exit;
  337.     FDbVersion := FMirandaHdr.Version;
  338.     if not CompareMem(@FMirandaHdr.Signature, @DBHEADER_SIGNATURE, 16) then
  339.     begin
  340.       if Assigned(OnError) then
  341.         FOnError(Self, ERR_NOTICQDB);
  342.       Exit;
  343.     end;
  344.   end;
  345.   Result := True;
  346. end;
  347. function TICQDb.ReadIdxChunk(var IdxRec: TIdxRec): Boolean;
  348. begin
  349.   Result := False;
  350.   if FHandle = INVALID_HANDLE_VALUE then Exit;
  351.   if IdxRec.Next = -1 then Exit;
  352.   if SetFilePointer(FHandle, IdxRec.Next, nil, 0) = LongWord(-1) then
  353.     Exit;
  354.   if FHandle = INVALID_HANDLE_VALUE then Exit;
  355.   if ReadBuf(FHandle, SizeOf(TIdxRec), IdxRec) <> SizeOf(TIdxRec) then
  356.     Exit;
  357.   Result := True;
  358. end;
  359. procedure TICQDb.ParseIndexes;
  360. var
  361.   idx: TIdxRec;
  362.   i: LongWord;
  363. begin
  364.   if Assigned(OnParsingStarted) then
  365.     FOnParsingStarted(Self);
  366.   idx.Next := FIdxRoot;
  367.   i := 0;
  368.   while ReadIdxChunk(idx) do
  369.   begin
  370.     if idx.Code = -2 then
  371.     begin
  372.       if idx.DatPos <> -1 then                    {if it's not a root entry}
  373.         if not Seek(FDhandle, idx.DatPos) then
  374.           Break
  375.         else
  376.           ParseDatEntry;
  377.     end;
  378.     Inc(i);
  379.     if Assigned(OnProgress) then
  380.     begin
  381.       if FIdxEntries <> 0 then
  382.         FOnProgress(Self, Round((i / FIdxEntries) * 100));
  383.     end;
  384.   end;
  385.   CloseIdx; CloseDat;
  386.   if Assigned(OnProgress) then
  387.     FOnProgress(Self, 100);
  388.   if Assigned(OnParsingFinished) then
  389.     FOnParsingFinished(Self);
  390. end;
  391. procedure TICQDb.ParseDatEntry;
  392. function Read64h: Char;
  393. begin
  394.   Result := Chr(ReadInt(FDHandle, 1));
  395. end;
  396. function Read65h: Byte;
  397. begin
  398.   Result := ReadInt(FDHandle, 1);
  399. end;
  400. function Read66h: Word;
  401. begin
  402.   Result := ReadInt(FDHandle, 2);
  403. end;
  404. function Read67h: Integer;
  405. begin
  406.   Result := ReadInt(FDHandle, 2);
  407. end;
  408. function Read68h: LongWord;
  409. begin
  410.   Result := ReadInt(FDHandle, 4);
  411. end;
  412. function Read69h: LongInt;
  413. begin
  414.   Result := ReadInt(FDHandle, 4);
  415. end;
  416. function Read6bh: String;
  417. begin
  418.   Result := ReadStr(FDHandle, ReadInt(FDHandle, 2));
  419. end;
  420. {Global variables in ParseDatEntry procedure}
  421. var
  422.   FNickName: String;
  423.   FFirstName: String;
  424.   FLastName: String;
  425.   FEmail: String;
  426.   FLastUpdate: String;
  427.   FAge, FGender: Byte;
  428.   FUIN: LongWord;
  429.   FMsg, FMsg2, FMsg3: String;
  430.   FFlag: LongWord;
  431.   FSeparator: Word;
  432.   FSubType: Word;
  433.   FTStamp: LongWord;
  434.   FPassword: String;
  435.   FCryptIV: LongWord;
  436. procedure ReadProperty;
  437. var
  438.   Len: Word;
  439.   AName: String;
  440.   Num, PropNum, i, n: LongWord;
  441.   CType: Byte;
  442.   Cmd: Byte;
  443. begin
  444.   Len := ReadInt(FDHandle, 2);
  445.   AName := ReadStr(FDHandle, Len);
  446.   Cmd := ReadInt(FDHandle, 1);
  447.   case Cmd of
  448.     $64: {Char}
  449.     begin
  450.       Read64h;
  451.     end;
  452.     $65: {Byte}
  453.     begin
  454.       if AName = 'Age' then
  455.         FAge := Read65h
  456.       else if AName = 'Gender' then
  457.         FGender := Read65h
  458.       else
  459.         Read65h;
  460.     end;
  461.     $66: {Word}
  462.     begin
  463.       Read66h;
  464.     end;
  465.     $67: {Integer}
  466.     begin
  467.       Read67h;
  468.     end;
  469.     $68: {DWord}
  470.     begin
  471.       if AName = '99BCryptIV' then
  472.         FCryptIV := Read68h
  473.       else
  474.         Read68h;
  475.     end;
  476.     $69: {LongInt}
  477.     begin
  478.       if AName = 'UIN' then
  479.         FUIN := Read69h
  480.       else
  481.         Read69h;
  482.     end;
  483.     $6b: {LNTS}
  484.     begin
  485.       if AName = 'NickName' then
  486.         FNickName := Read6bh
  487.       else if AName = 'FirstName' then
  488.         FFirstName := Read6bh
  489.       else if AName = 'LastName' then
  490.         FLastName := Read6bh
  491.       else if AName = 'PrimaryEmail' then
  492.         FEmail := Read6bh
  493.       else if AName = 'Password' then
  494.       begin
  495.         if FPassword = '' then                  //For some unknown reasons, password is stored many times with null value
  496.           FPassword := Read6bh
  497.         else
  498.           Read6bh
  499.       end
  500.       else
  501.         Read6bh;
  502.     end;
  503.     $6d: {Sublist}
  504.     begin
  505.       Num := ReadInt(FDHandle, 4);
  506.       CType := ReadInt(FDHandle, 1);
  507.       if Num > 0 then
  508.         for i := 0 to Num - 1 do
  509.           case CType of
  510.             $6b:
  511.             begin
  512.               Skip(FDHandle, ReadInt(FDHandle, 2));
  513.             end;
  514.             $6e:
  515.             begin
  516.               Skip(FDHandle, 2);                //Separator value
  517.               PropNum := ReadInt(FDHandle, 4);  //Number of properties
  518.               if PropNum > 0 then
  519.                 for n := 0 to PropNum - 1 do
  520.                   ReadProperty;                 //Parse each property (call recursively)
  521.             end;
  522.           end;
  523.     end;
  524.     $6f: {DWORD (length) + BYTE array}
  525.     begin
  526.       Skip(FDHandle, ReadInt(FDHandle, 4));
  527.     end;
  528.   end;
  529. end;
  530. procedure ReadPropertyBlock;
  531. var
  532.   Num, i: LongWord;
  533. begin
  534.   Skip(FDHandle, 2);                            //Separator value
  535.   Num := ReadInt(FDHandle, 4);                  //Number of user properties
  536.   if Num > 0 then
  537.     for i := 0 to Num - 1 do
  538.       ReadProperty;
  539. end;
  540. procedure ReadWavEntry;
  541. begin
  542.   Skip(FDHandle, 2);                            //Separator value
  543.   Skip(FDHandle, 4);                            //User event for which Wav will be played
  544.   Skip(FDHandle, 4);                            //0: play default WAV, 1: play the user-specified WAV
  545.   ReadLNTS(FDHandle);                           //Full path and file name of WAV
  546. end;
  547. var
  548.   Dat: TDatRec;
  549.   Num: LongWord;
  550.   i: LongWord;
  551.   FURL, FDesc: String;
  552. begin
  553.   if FDHandle = INVALID_HANDLE_VALUE then Exit;
  554.   if ReadBuf(FDHandle, SizeOf(Dat), Dat) <> SizeOf(Dat) then Exit;
  555.   case Dat.Command of
  556.     $e0, $a0: {Short Message & URL Format (ICQ 99a-2002a)}
  557.     begin
  558.       Skip(FDHandle, 2);                        //Separator
  559.       Skip(FDHandle, 4);                        //Filing flags
  560.       FSubType := ReadInt(FDHandle, 2);         //Entry sub type: 1: Message; 4: URL; 19: Contacts
  561.       if (FSubType <> 1) and (FSubType <> 4) then
  562.         Exit;
  563.       FUIN := ReadInt(FDHandle, 4);             //UIN of sender/receiver
  564.       FMsg := ReadStr(FDHandle, ReadInt(FDHandle, 2));
  565.       Skip(FDHandle, 4);                        //Status of receiving user
  566.       FFlag := ReadInt(FDHandle, 4);            //Sent or received: 0: Received, 1: Sent
  567.       Skip(FDHandle, 2);                        //Separator value
  568.       FTStamp := ReadInt(FDHandle, 4);          //Timestamp, time of last update
  569.       FLastUpdate := TimeStamp2Str(FTStamp);
  570.       if FSubType = 1 then
  571.       begin
  572.         if Assigned(OnMessageFound) then
  573.           FOnMessage(Self, FUIN, FFlag = 0, FMsg, FLastUpdate, FTStamp);
  574.       end else
  575.       if FSubType = 4 then
  576.       begin
  577.         FDesc := Copy(FMsg, 0, Pos(#$fe, FMsg) - 1);
  578.         FURL := Copy(FMsg, Pos(#$fe, FMsg) + 1, Length(FMsg) - Pos(#$fe, FMsg));
  579.         if Assigned(OnUrlFound) then
  580.           FOnUrl(Self, FUIN, FFlag = 0, FDesc, FURL, FLastUpdate, FTStamp);
  581.       end;
  582.     end;
  583.     $e4: {My details}
  584.     begin
  585.       if Dat.Number <> 1005 then Exit;
  586.       FNickName := ''; FFirstName := ''; FLastName := '';  FEmail := '';
  587.       FPassword := ''; FAge := 0; FGender := 0; FUIN := 0;
  588.       FSeparator := ReadInt(FDHandle, 2);       //Separator
  589.       if ReadStr(FDHandle, 4) <> 'RESU' then    //Label   = 55534552h ('USER')
  590.         Exit;
  591.       if ReadInt(FDHandle, 4) <> 6 then Exit;   //User entry status: 6 = "My Details"
  592.       Skip(FDHandle, 4);                        //0 (Unknown, most likely an unused group entry)
  593.       Skip(FDHandle, 2);                        //Separator value
  594.       {Some modifications in ICQ2000x}
  595.       if (FDbVersion = DB_2000a) or (FDbVersion = DB_2000b) then
  596.       begin
  597.         Num := ReadInt(FDHandle, 4);            //Number of user event WAV entries
  598.         if Num > 0 then
  599.           for i := 0 to Num - 1 do
  600.             ReadWavEntry;
  601.         Skip(FDHandle, 2);                      //Separator value
  602.       end;
  603.       {Some modifications in ICQ2002a}
  604.       if (FSeparator >= 533) and (FDbVersion = DB_2001a) then
  605.       begin
  606.         Skip(FDHandle, 4);                      //0 (Unknown, if this can be longer than a long it will most likely crash the importer
  607.         Skip(FDHandle, 2);                      //Separator value
  608.       end;
  609.       Num := ReadInt(FDHandle, 4);              //Number of property blocks
  610.       if Num > 0 then
  611.         for i := 0 to Num - 1 do
  612.           ReadPropertyBlock;
  613.       Skip(FDHandle, 2);                        //Separator value
  614.       FTStamp := ReadInt(FDHandle, 4);          //Timestamp, time of last update
  615.       FLastUpdate := TimeStamp2Str(FTStamp);
  616.       FPassword := Decrypt99bPassword(FUIN, FCryptIV, FPassword);
  617.       if Assigned(OnSelfInfoFound) then
  618.         FOnSelfInfo(Self, FUIN, FNickName, FFirstName, FLastName, FEmail, FPassword, FAge, FGender, FLastUpdate, FTStamp);
  619.     end;
  620.     $e5: {Contact entry}
  621.     begin
  622.       FNickName := ''; FFirstName := ''; FLastName := '';  FEmail := '';
  623.       FAge := 0; FGender := 0; FUIN := 0;
  624.       FSeparator := ReadInt(FDHandle, 2);       //Separator
  625.       if ReadStr(FDHandle, 4) <> 'RESU' then    //Label   = 55534552h ('USER')
  626.         Exit;
  627.       ReadInt(FDHandle, 4);                     //User entry status
  628.       ReadInt(FDHandle, 4);                     //GroupID of contact group containing user
  629.       Skip(FDHandle, 2);                        //Separator value
  630.       {Some modifications in ICQ2000x}
  631.       if (FDbVersion = DB_2000a) or (FDbVersion = DB_2000b) then
  632.       begin
  633.         Num := ReadInt(FDHandle, 4);            //Number of user event WAV entries
  634.         if Num > 0 then
  635.           for i := 0 to Num - 1 do
  636.             ReadWavEntry;
  637.         Skip(FDHandle, 2);                      //Separator value
  638.       end;
  639.       {Some modifications in ICQ2002a}
  640.       if (FSeparator >= 533) and (FDbVersion = DB_2001a) then
  641.       begin
  642.         Skip(FDHandle, 4);                      //Unknown, 0
  643.         Skip(FDHandle, 2);                      //Separator value
  644.       end;
  645.       Num := ReadInt(FDHandle, 4);              //Number of property blocks
  646.       if Num > 0 then
  647.         for i := 0 to Num - 1 do
  648.           ReadPropertyBlock;
  649.       Skip(FDHandle, 2);                        //Separator value
  650.       FTStamp := ReadInt(FDHandle, 4);          //Timestamp, time of last update
  651.       FLastUpdate := TimeStamp2Str(FTStamp);
  652.       if Assigned(OnContactFound) then
  653.         FOnContact(Self, FUIN, FNickName, FFirstName, FLastName, FEmail, FAge, FGender, FLastUpdate, FTStamp);
  654.     end;
  655.     $50: {Long Message Format (ICQ 99a-2002a)}
  656.     begin
  657.       Skip(FDHandle, 2);                        //Separator
  658.       Skip(FDHandle, 4);                        //Filing flags
  659.       Skip(FDHandle, 2);                        //Entry sub type
  660.       FUIN := ReadInt(FDHandle, 4);             //UIN of sender/receiver
  661.       FMsg := ReadLNTS(FDHandle);               //ANSI text
  662.       Skip(FDHandle, 4);                        //Status of receiving user
  663.       FFlag := ReadInt(FDHandle, 4);            //Sent or received: 0: Received, 1: Sent
  664.       Skip(FDHandle, 2);                        //Separator value
  665.       FTStamp := ReadInt(FDHandle, 4);          //Timestamp, time of last update
  666.       FLastUpdate := TimeStamp2Str(FTStamp);
  667.       Skip(FDHandle, 19);                       //Zeroes
  668.       FMsg2 := ReadLNTS(FDHandle);              //Rich Text
  669.       FMsg3 := ReadLNTS(FDHandle);              //UTF-8 Text
  670.       if Assigned(OnAdvMessageFound) then
  671.         FOnAdvMessage(Self, FUIN, FFlag = 0, FMsg, FMsg2, FMsg3, FLastUpdate, FTStamp);
  672.     end;
  673.   end;
  674. end;
  675. procedure TICQDb.ParseMirandaDatFile;
  676. {Global variables in ParseMirandaDatFile procedure}
  677. var
  678.   FNickName: String;
  679.   FFirstName: String;
  680.   FLastName: String;
  681.   FEmail: String;
  682.   FLastUpdate: String;
  683.   FAge, FGender: Byte;
  684.   FUIN: LongWord;
  685.   FMsg: String;
  686.   FPassword: String;
  687. function GetModuleName(Ofs: LongWord): String;
  688. type
  689.   TDBModuleName = record
  690.     Signature: LongWord;
  691.     ofsNext: LongWord;
  692.     cbName: Byte;
  693.   end;
  694. var
  695.   FMod: TDbModuleName;
  696.   FCurrOff: LongWord;
  697. begin
  698.   Result := '';
  699.   FCurrOff := GetPos(FDHandle);
  700.   if not Seek(FDHandle, Ofs) then Exit;
  701.   if ReadBuf(FDHandle, SizeOf(FMod), FMod) <> SizeOf(FMod) then Exit;
  702.   Result := ReadStr(FDHandle, FMod.cbName);
  703.   Seek(FDHandle, FCurrOff);
  704. end;
  705. function ReadContactSettings(Ofs: LongWord): Boolean;
  706. function ReadByte: Byte;
  707. begin
  708.   Result := ReadInt(FDHandle, 1);
  709. end;
  710. function ReadWord: Word;
  711. begin
  712.   Result := ReadInt(FDHandle, 2);
  713. end;
  714. function ReadDWord: LongWord;
  715. begin
  716.   Result := ReadInt(FDHandle, 4);
  717. end;
  718. function ReadASCIIZ: String;
  719. begin
  720.   Result := ReadStr(FDHandle, ReadWord);
  721. end;
  722. procedure ReadParams(Len: LongWord);
  723. var
  724.   FName: String;
  725.   __pos: LongWord;
  726. begin
  727.   __pos := GetPos(FDHandle);
  728.   while True do
  729.   begin
  730.     FName := ReadStr(FDHandle, ReadByte);
  731.     if FName = '' then Break;                   //We acheived end of property list
  732.     case ReadByte of
  733.       DBVT_DELETED: Exit;                       //This setting just got deleted, no other values are valid
  734.       DBVT_BYTE:
  735.       begin
  736.         if FName = 'Gender' then
  737.         begin
  738.           FGender := ReadByte;
  739.           if Chr(FGender) = 'M' then
  740.             FGender := GEN_MALE
  741.           else if Chr(FGender) = 'F' then
  742.             FGender := GEN_FEMALE
  743.           else
  744.             FGender := 0;
  745.         end else
  746.           ReadByte;
  747.       end;
  748.       DBVT_WORD:
  749.       begin
  750.         if FName = 'age' then
  751.           FAge := ReadWord
  752.         else
  753.           ReadWord;
  754.       end;
  755.       DBVT_DWORD:
  756.         if FName = 'UIN' then
  757.           FUIN := ReadDWord
  758.         else
  759.           ReadDWord;
  760.       DBVT_ASCIIZ:
  761.       begin
  762.         if FName = 'Nick' then
  763.           FNickName :=  ReadASCIIZ
  764.         else if FName = 'FirstName' then
  765.           FFirstName := ReadASCIIZ
  766.         else if FName = 'LastName' then
  767.           FLastName := ReadASCIIZ
  768.         else if FName = 'e-mail' then
  769.           FEmail := ReadASCIIZ
  770.         else if FName = 'Password' then
  771.           FPassword := DecryptMirandaPassword(ReadASCIIZ)
  772.         else
  773.           ReadASCIIZ;
  774.       end;
  775.       DBVT_BLOB:
  776.         Skip(FDHandle, ReadDWord);
  777.       DBVTF_VARIABLELENGTH:
  778.         Exit;
  779.     else
  780.       Exit;
  781.     end;
  782.     if GetPos(FDHandle) >= __pos + Len then Break;
  783.   end;
  784. end;
  785. var
  786.   FDbset: TDBContactSettings;
  787.   FModName: String;
  788. begin
  789.   FNickName := ''; FFirstName := ''; FLastName := '';
  790.   FEmail := ''; FLastUpdate := ''; FAge := 0;
  791.   FGender := 0; FUIN := 0; FMsg := ''; Result := False;
  792.   if not Seek(FDHandle, Ofs) then Exit;
  793.   while True do
  794.   begin
  795.     if ReadBuf(FDHandle, SizeOf(FDbSet), FDbSet) <> SizeOf(FDbSet) then Break;
  796.     FModName := GetModuleName(FDbSet.ofsModuleName);
  797.     if FModName = '' then                       //Do not parse any module settings
  798.       ReadParams(FDbSet.cbBlob);                //Parse contact params
  799.     if FDbSet.ofsNext = 0 then Break;
  800.     if not Seek(FDHandle, FDbSet.ofsNext) then Break;
  801.   end;
  802.   Result := True;
  803. end;
  804. procedure ReadEvents(Ofs: LongWord);
  805. var
  806.   FDbEvent: TDbEvent;
  807.   FDesc: String;
  808.   FURL: String;
  809. begin
  810.   if not Seek(FDHandle, Ofs) then Exit;
  811.   while True do
  812.   begin
  813.     if ReadBuf(FDHandle, SizeOf(TDbEvent), FDbEvent) <> SizeOf(TDbEvent) then Break;
  814.     if FDbEvent.Signature <> DBEVENT_SIGNATURE then Break;
  815.     if GetModuleName(FDbEvent.ofsModuleName) = '' then  //Parse only miranda's events
  816.       if (FDbEvent.eventType = EVENTTYPE_MESSAGE) or
  817.          (FDbEvent.eventType = EVENTTYPE_URL) then
  818.       begin
  819.         FMsg := ReadStr(FDHandle, FDbEvent.cbBlob);
  820.         if FDbEvent.eventType = EVENTTYPE_MESSAGE then
  821.         begin
  822.           if Assigned(OnMessageFound) then
  823.             FOnMessage(Self, FUIN, FDbEvent.flags and DBEF_SENT <> DBEF_SENT, FMsg, TimeStamp2Str(FDbEvent.Timestamp), FDbEvent.Timestamp);
  824.         end else
  825.         begin
  826.           FDesc := Copy(FMsg, 0, Pos(#$fe, FMsg) - 1);
  827.           FURL := Copy(FMsg, Pos(#$fe, FMsg) + 1, Length(FMsg) - Pos(#$fe, FMsg));
  828.           if Assigned(OnUrlFound) then
  829.             FOnUrl(Self, FUIN, FDbEvent.flags and DBEF_SENT <> DBEF_SENT, FDesc, FURL, TimeStamp2Str(FDbEvent.Timestamp), FDbEvent.Timestamp);
  830.         end;
  831.       end;
  832.     if FDbEvent.ofsNext = 0 then Break;
  833.     if not Seek(FDHandle, FDbEvent.ofsNext) then Break;
  834.   end;
  835. end;
  836. var
  837.   FContact: TMirandaContact;
  838. begin
  839.   if Assigned(OnParsingStarted) then
  840.     FOnParsingStarted(Self);
  841.   if Assigned(OnProgress) then
  842.     FOnProgress(Self, 0);
  843.   if not Seek(FDHandle, FMirandaHdr.ofsFirstContact) then Exit;
  844.   while True do
  845.   begin
  846.     if ReadBuf(FDHandle, SizeOf(FContact), FContact) <> SizeOf(FContact) then Break;
  847.     if ReadContactSettings(FContact.ofsFirstSettings) then
  848.       if Assigned(OnContactFound) then                  //It's called here because of same property reader for the self info
  849.         FOnContact(Self, FUIN, FNickName, FFirstName, FLastName, FEmail, FAge, FGender, '', 0);
  850.     ReadEvents(FContact.ofsFirstEvent);
  851.     if FContact.ofsNext = 0 then Break;
  852.     if not Seek(FDhandle, FContact.ofsNext) then Break;
  853.   end;
  854.   if (FMirandaHdr.ofsUser = 0) or (not Seek(FDHandle, FMirandaHdr.ofsUser)) then Exit;
  855.   if ReadBuf(FDHandle, SizeOf(FContact), FContact) <> SizeOf(FContact) then Exit;
  856.   FPassword := '';
  857.   if ReadContactSettings(FContact.ofsFirstSettings) then
  858.     if Assigned(OnSelfInfoFound) then
  859.       FOnSelfInfo(Self, FUIN, FNickName, FFirstName, FLastName, FEmail, FPassword, FAge, FGender, '', 0);
  860.   if Assigned(OnProgress) then
  861.     FOnProgress(Self, 100);
  862.   if Assigned(OnParsingFinished) then
  863.     FOnParsingFinished(Self);
  864. end;
  865. function DbErrorToStr(Error: Word): String;
  866. begin
  867.   case Error of
  868.     ERR_FILEOPEN: Result := 'Could not open database files';
  869.     ERR_NOTICQDB: Result := 'Not an ICQ database';
  870.     ERR_DBVERNOTSUPPORTED: Result := 'Dat version not supported';
  871.   else
  872.     Result := '';
  873.   end;
  874. end;
  875. procedure Register;
  876. begin
  877.   RegisterComponents('Standard', [TICQDb]);
  878. end;
  879. end.