⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 icqdb.pas

📁 本程序是转载的
💻 PAS
📖 第 1 页 / 共 2 页
字号:
unit ICQDb {v 1.19};
{(C) Alex Demchenko(alex@ritlabs.com)}
{$R-}

interface
uses
  Windows, Messages, ICQWorks, SysUtils, Classes, ICQLang;

const
  {Database versions}
  DB_99A = 10;   {99a}
  DB_99B = 14;   {99b}
  DB_2000a = 17; {2000a}
  DB_2000b = 18; {2000b}
  DB_2001a = 19; {2001a, 2001b, 2002a, 2003a}
  DB_MIRANDA121 = $00000700; {Miranda 1.2.1, 1.2.0}

const
  {Miranda-icq signatures}
  DBHEADER_SIGNATURE: array[0..15] of Char = ('M', 'i', 'r', 'a', 'n', 'd', 'a', ' ',   'I', 'C', 'Q', ' ', 'D', 'B', #$00, #$1a);
  DBCONTACT_SIGNATURE: LongWord         = $43DECADE;
  DBMODULENAME_SIGNATURE: LongWord      = $4DDECADE;
  DBCONTACTSETTINGS_SIGNATURE: LongWord = $53DECADE;
  DBEVENT_SIGNATURE: LongWord           = $45DECADE;

  {Miranda-icq data types}
  DBVT_DELETED = 0;    //this setting just got deleted, no other values are valid
  DBVT_BYTE    = 1;    //bVal and cVal are valid
  DBVT_WORD    = 2;    //wVal and sVal are valid
  DBVT_DWORD   = 4;    //dVal and lVal are valid
  DBVT_ASCIIZ  = 255;  //pszVal is valid
  DBVT_BLOB    = 254;  //cpbVal and pbVal are valid
  DBVTF_VARIABLELENGTH = $80;

  {Miranda-icq database flags}
  DBEF_FIRST   = 1;    //this is the first event in the chain;
  DBEF_SENT    = 2;    //this event was sent by the user. If not set this
  DBEF_READ    = 4;    //event has been read by the user. It does not need

  {Miranda-icq event types}
  EVENTTYPE_MESSAGE     = 0;           //Message
  EVENTTYPE_URL         = 1;           //URL
  EVENTTYPE_ADDED       = 1000;        //v0.1.1.0+: these used to be module-
  EVENTTYPE_AUTHREQUEST = 1001;        //specific codes, hence the module-
  EVENTTYPE_FILE        = 1002;        //specific limit has been raised to 2000


type
  TOnErrorEvent = procedure(Sender: TObject; Reason: Word; ReasonStr: String) of object;
  TOnProgress = procedure(Sender: TObject; Progress: Byte) of object;
  TOnContact = procedure(Sender: TObject; UIN: LongWord; NickName, FirstName, LastName,
    Email: String; Age, Gender: Byte; LastUpdate: String; LastUpdateStamp: LongWord) of object;
  TOnSelfInfo = procedure(Sender: TObject; UIN: LongWord; NickName, FirstName, LastName,
    Email, Password: String; Age, Gender: Byte; LastUpdate: String; LastUpdateStamp: LongWord) of object;
  TOnMessage = procedure(Sender: TObject; UIN: LongWord; Incoming: Boolean; Msg, RecvTime: String; RecvTimeStamp: LongWord) of object;
  TOnUrl = procedure(Sender: TObject; UIN: LongWord; Incoming: Boolean; Description, URL, RecvTime: String; RecvTimeStamp: LongWord) of object;
  TOnAdvMessage = procedure(Sender: TObject; UIN: LongWord; Incoming: Boolean; PlainText, RichText, UTF8Text, RecvTime: String; RecvTimeStamp: LongWord) of object;

  {Index record}
  TIdxRec = record
    Code,                                       //If entry is valid the it's set to -2
    Number,                                     //DAT entry number
    Next,                                       //Next IdxRec offset
    Prev,                                       //Previous IdxRec offset
    DatPos: LongInt;                            //Offset in .dat file
  end;

  {Dat header record}
  TDatRec = record
    Length,
    FillType,
    Number: LongInt;
    Command: Byte;
    Signature: array[0..14] of Byte;
  end;

  {Miranda .dat header}
  TMirandaHdr = record
    Signature: array[0..15] of Byte;
    Version: LongWord;
    ofsFileEnd: LongWord;
    slackSpace: LongWord;
    contactCount: LongWord;
    ofsFirstContact: LongWord;
    ofsUser: LongWord;
    ofsFirstModuleName: LongWord;
  end;

  {Miranda's contact entry}
  TMirandaContact = record
    Signature: DWord;
    ofsNext: DWord;
    ofsFirstSettings: DWord;
    eventCount: DWord;
    ofsFirstEvent, ofsLastEvent: DWord;
    ofsFirstUnreadEvent: DWord;
    timestampFirstUnread: DWord;
  end;

  {Miranda's contact settings}
  TDBContactSettings = record
    Signature: LongWord;
    ofsNext: LongWord;
    ofsModuleName: LongWord;
    cbBlob: LongWord
  end;

  {Miranda's event}
  TDBEvent = packed record
    Signature: LongWord;
    ofsPrev: LongWord;
    ofsNext: LongWord;
    ofsModuleName: LongWord;
    Timestamp: LongWord;
    Flags: LongWord;
    eventType: Word;
    cbBlob: LongWord;
  end;

  {Component}
  TICQDb = class(TComponent)
  private
    FIdxFile, FDatFile: String;
    FHandle: THandle;                           //Main .idx file handle
    FDHandle: THandle;                          //Main .dat file handle
    FIdxRoot: LongWord;                         //Root .idx entry
    FIdxEntries: LongWord;                      //Count of idx entries
    FDbVersion: LongWord;                       //Database version extracted from .idx file
    FMirandaHdr: TMirandaHdr;
    {-=-=-=-=-}
    FOnError: TOnErrorEvent;
    FOnParsingStarted: TNotifyEvent;
    FOnParsingFinished: TNotifyEvent;
    FOnProgress: TOnProgress;
    FOnContact: TOnContact;
    FOnSelfInfo: TOnSelfInfo;
    FOnMessage: TOnMessage;
    FOnURL: TOnUrl;
    FOnAdvMessage: TOnAdvMessage;
    FDbType: TDbType;
    FErrLang: TICQLangType;
    function ReadInt(Handle: THandle; Len: ShortInt): LongWord;
    function ReadBuf(Handle: THandle; Len: LongWord; var Buf): LongWord;
    function ReadStr(Handle: THandle; Len: LongWord): String;
    function ReadLNTS(Handle: THandle): String;
    procedure Skip(Handle: THandle; Len: LongWord);
    function Seek(Handle: THandle; Pos: LongWord): Boolean;
    function GetPos(Handle: THandle): LongWord;
    function OpenIdx(const FileName: String): Boolean;
    procedure CloseIdx;
    function OpenDat(const FileName: String): Boolean;
    procedure CloseDat;
    function IsMiranda: Boolean;
    function IsICQ: Boolean;
    function ReadHeader: Boolean;
    function ReadIdxChunk(var IdxRec: TIdxRec): Boolean;
    procedure ParseIndexes;
    procedure ParseDatEntry;
    procedure ParseMirandaDatFile;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure StartParsing;
    property DbType: TDbType read FDbType;
  published
    property DatFile: String read FDatFile write FDatFile;
    property OnError: TOnErrorEvent read FOnError write FOnError;
    property DbVersion: LongWord read FDbVersion;
    property OnParsingStarted: TNotifyEvent read FOnParsingStarted write FOnParsingStarted;
    property OnParsingFinished: TNotifyEvent read FOnParsingFinished write FOnParsingFinished;
    property OnProgress: TOnProgress read FOnProgress write FOnProgress;
    property OnContactFound: TOnContact read FOnContact write FOnContact;
    property OnSelfInfoFound: TOnSelfInfo read FOnSelfInfo write FOnSelfInfo;
    property OnMessageFound: TOnMessage read FOnMessage write FOnMessage;
    property OnURLFound: TOnUrl read FOnUrl write FOnUrl;
    property OnAdvMessageFound: TOnAdvMessage read FOnAdvMessage write FOnAdvMessage;
    property ErrorLanguage: TICQLangType read FErrLang write FErrLang default LANG_EN;
  end;

procedure Register;

implementation
function TimeStamp2Str(Timestamp: LongWord): String;
var
  DelphiTime: Double;
begin
  DelphiTime := EncodeDate(1970, 1, 1) + (TimeStamp / 86400);
  Result := DateTimeToStr(DelphiTime);
end;

constructor TICQDb.Create;
begin
  inherited;
  FHandle := INVALID_HANDLE_VALUE;
  FDHandle := INVALID_HANDLE_VALUE;
end;

destructor TICQDb.Destroy;
begin
  CloseIdx; CloseDat;
  inherited;
end;

function TICQDb.IsMiranda: Boolean;
begin
  FDbType := DB_MIRANDA;
  Result := OpenDat(FDatFile);
  if (Result) and (not ReadHeader) then begin
    Result := False;
    CloseDat;
  end;
end;

function TICQDb.IsICQ: Boolean;
begin
  FDbType := DB_ICQ;
  Result := OpenIdx(FIdxFile) and OpenDat(FDatFile);
  if (Result) and (not ReadHeader) then begin
    Result := False; 
    CloseDat; CloseIdx;
    Exit;
  end;
end;

procedure TICQDb.StartParsing;
begin
  FIdxFile := Copy(FDatFile, 0, Pos('.', FDatFile) - 1) + '.idx';
  if (not IsICQ) and (not IsMiranda) then begin
    if Assigned(OnError) then
      FOnError(Self, IMSG_EDB_EFILEOPEN, ICQLanguages[FErrLang].Translate(IMSG_EDB_EFILEOPEN));
    Exit;
  end;
  if (FDbVersion <> DB_2001a) and (FDbVersion <> DB_2000a) and
     (FDbVersion <> DB_2000b) and (FDbVersion <> DB_MIRANDA121)
  then begin
    CloseDat; CloseIdx;
    if Assigned(OnError) then
      FOnError(Self, IMSG_EDB_EDBVERNOTSUPPORTED, ICQLanguages[FErrLang].Translate(IMSG_EDB_EDBVERNOTSUPPORTED));
     Exit;
  end;
  if FDbType = DB_ICQ then ParseIndexes else ParseMirandaDatFile;
end;

function TICQDb.ReadInt(Handle: THandle; Len: ShortInt): LongWord;
var
  buf: array[0..3] of Byte;
  read: LongWord;
begin
  Result := 0;
  if (Len < 0) or (Len > 4) then
    Exit;
  FillChar(buf, SizeOf(buf), 0);
  ReadFile(Handle, buf, Len, read, nil);
  if read < 1 then Exit;
  Result := PLongWord(@buf)^;
end;

function TICQDb.ReadBuf(Handle: THandle; Len: LongWord; var Buf): LongWord;
begin
  if Len = 0 then Exit;
  ReadFile(Handle, Buf, Len, Result, nil);
end;

function TICQDb.ReadStr(Handle: THandle; Len: LongWord): String;
var
  buf: Pointer;
  read: LongWord;
begin
  Result := '';
  GetMem(buf, Len);
  if Len = 0 then Exit;
  ReadFile(Handle, buf^, Len, read, nil);
  if read < 1 then begin
    FreeMem(buf);
    Exit;
  end;
  Result := Copy(PChar(buf), 0, Len);
  FreeMem(buf);
end;

function TICQDb.ReadLNTS(Handle: THandle): String;
begin
  Result := ReadStr(Handle, ReadInt(Handle, 2));
end;

procedure TICQDb.Skip(Handle: THandle; Len: LongWord);
begin
  SetFilePointer(Handle, SetFilePointer(Handle, 0, nil, 1) + Len, nil, 0)
end;

function TICQDb.Seek(Handle: THandle; Pos: LongWord): Boolean;
begin
  Result := SetFilePointer(Handle, Pos, nil, 0) <> LongWord(-1);
end;

function TICQDb.GetPos(Handle: THandle): LongWord;
begin
  Result := SetFilePointer(Handle, 0, nil, 1);
end;

function TICQDb.OpenIdx(const FileName: String): Boolean;
begin
  Result := False;
  CloseIdx;
  FHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  if FHandle = INVALID_HANDLE_VALUE then Exit;
  if SetFilePointer(FHandle, 0, nil, 0) = LongWord(-1) then begin
    CloseIdx;
    Exit;
  end;
  Result := True;
end;

procedure TICQDb.CloseIdx;
begin
  if FHandle <> INVALID_HANDLE_VALUE then
    CloseHandle(FHandle);
  FHandle := INVALID_HANDLE_VALUE;
end;

function TICQDb.OpenDat(const FileName: String): Boolean;
begin
  Result := False;
  CloseDat;
  FDHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  if FDHandle = INVALID_HANDLE_VALUE then Exit;
  if SetFilePointer(FDHandle, 0, nil, 0) = LongWord(-1) then begin
    CloseDat;
    Exit;
  end;
  Result := True;
end;

procedure TICQDb.CloseDat;
begin
  if FDHandle <> INVALID_HANDLE_VALUE then
    CloseHandle(FDHandle);
  FDHandle := INVALID_HANDLE_VALUE;
end;

function TICQDb.ReadHeader: Boolean;
var
  Size: LongWord;
begin
  Result := False;
  if DbType = DB_ICQ then begin
    Size := FileSize(FIdxFile);
    if Size <> INVALID_FILE_SIZE then
      FIdxEntries := (Size - 20) div (SizeOf(TIdxRec) shl 4)
    else
      Exit;
    if FHandle = INVALID_HANDLE_VALUE then Exit;
    if (ReadInt(FHandle, 4) <> 4) or (ReadInt(FHandle, 4) <> 20) or
       (ReadInt(FHandle, 4) <> 8) then
      Exit;
    FIdxRoot := ReadInt(FHandle, 4);
    FDbVersion := ReadInt(FHandle, 4);
  end else begin
    Size := FileSize(FDatFile);
    if Size = INVALID_FILE_SIZE then Exit;
    if ReadBuf(FDHandle, SizeOf(TMirandaHdr), FMirandaHdr) <> SizeOf(TMirandaHdr) then Exit;
    FDbVersion := FMirandaHdr.Version;
    if not CompareMem(@FMirandaHdr.Signature, @DBHEADER_SIGNATURE, 16) then
      Exit;
  end;
  Result := True;
end;

function TICQDb.ReadIdxChunk(var IdxRec: TIdxRec): Boolean;
begin
  Result := False;
  if FHandle = INVALID_HANDLE_VALUE then Exit;
  if IdxRec.Next = -1 then Exit;
  if SetFilePointer(FHandle, IdxRec.Next, nil, 0) = LongWord(-1) then
    Exit;
  if FHandle = INVALID_HANDLE_VALUE then Exit;
  if ReadBuf(FHandle, SizeOf(TIdxRec), IdxRec) <> SizeOf(TIdxRec) then
    Exit;
  Result := True;
end;

procedure TICQDb.ParseIndexes;
var
  idx: TIdxRec;
  i: LongWord;
begin
  if Assigned(OnParsingStarted) then
    FOnParsingStarted(Self);
  idx.Next := FIdxRoot;
  i := 0;
  while ReadIdxChunk(idx) do begin
    if idx.Code = -2 then
    begin
      if idx.DatPos <> -1 then                    {if it's not a root entry}
        if not Seek(FDhandle, idx.DatPos) then
          Break
        else
          ParseDatEntry;
    end;
    Inc(i);
    if (Assigned(OnProgress)) and (FIdxEntries > 0) then
      FOnProgress(Self, Round((i / FIdxEntries) * 100));
  end;
  CloseIdx; CloseDat;
  if Assigned(OnProgress) then
    FOnProgress(Self, 100);
  if Assigned(OnParsingFinished) then
    FOnParsingFinished(Self);
end;

procedure TICQDb.ParseDatEntry;
function Read64h: Char;
begin
  Result := Chr(ReadInt(FDHandle, 1));
end;

function Read65h: Byte;
begin
  Result := ReadInt(FDHandle, 1);
end;

function Read66h: Word;
begin
  Result := ReadInt(FDHandle, 2);
end;

function Read67h: Integer;
begin
  Result := ReadInt(FDHandle, 2);
end;

function Read68h: LongWord;
begin
  Result := ReadInt(FDHandle, 4);
end;

function Read69h: LongInt;
begin
  Result := ReadInt(FDHandle, 4);
end;

function Read6bh: String;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -