home *** CD-ROM | disk | FTP | other *** search
/ Chip 2002 November / Chip_2002-11_cd1.bin / zkuste / delphi / kompon / d3456 / ICQ.ZIP / ICQ / Component / ICQDb.pas < prev    next >
Pascal/Delphi Source File  |  2002-08-16  |  30KB  |  942 lines

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