/* Import plugin for Miranda IM Copyright (C) 2001-2005 Martin Öberg, Richard Hughes, Roland Rabien & Tristan Van de Vreede This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // ============== // == INCLUDES == // ============== #include "import.h" #include "mirandadb0700.h" // ====================== // == GLOBAL FUNCTIONS == // ====================== HANDLE HContactFromNumericID(char* pszProtoName, char* pszSetting, DWORD dwID); HANDLE HContactFromID(char* pszProtoName, char* pszSetting, char* pszID); HANDLE AddContact(HWND hdlgProgress, char* pszProtoName, char* pszUniqueSetting, DBVARIANT* id, DBVARIANT* nick, DBVARIANT* group); BOOL IsProtocolLoaded(char* pszProtocolName); BOOL IsDuplicateEvent(HANDLE hContact, DBEVENTINFO dbei); INT_PTR CALLBACK ImportTypePageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam); INT_PTR CALLBACK FinishedPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam); INT_PTR CALLBACK ProgressPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam); INT_PTR CALLBACK MirandaOptionsPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam); INT_PTR CALLBACK MirandaAdvOptionsPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam); // ===================== // == LOCAL FUNCTIONS == // ===================== void MirandaImport(HWND hdlgProgress); int CheckFileFormat(HANDLE hFile); static HANDLE ImportContact(HANDLE hDbFile, struct DBContact Contact); static void ImportHistory(HANDLE hDbFile, struct DBContact Contact, PROTOCOLDESCRIPTOR **protocol, int protoCount); static int ImportGroups(HANDLE hDbFile, struct DBHeader *pdbHeader); // Comment: The Find* functions only return a file offset. // The Get* functions actually reads the requested // data from the file and gives you a pointer to a structure // containing the data. DWORD FindFirstContact(struct DBHeader* pDbHeader); DWORD FindNextContact(struct DBContact* pDbContact); DWORD FindNextEvent(HANDLE hDbFile, DWORD dwOffset); DWORD FindOwnerContact(struct DBHeader* pDbHeader); int GetContactCount(struct DBHeader* pDbHeader); BOOL GetContact(HANDLE hDbFile, DWORD dwOffset, struct DBContact* pDbContact); BOOL GetSetting(HANDLE hDbFile, struct DBContact* pDbContact, char* pszModuleName, char* pszSettingName, DBVARIANT* pValue); char* GetNextSetting(char* pDbSetting); BOOL GetSettings(HANDLE hDbFile, DWORD dwOffset, struct DBContactSettings** pDbSettings); struct DBContactSettings* GetSettingsGroupByModuleName(HANDLE hdbFile, struct DBContact* pDbContact, char* pszName); DWORD GetBlobSize(struct DBContactSettings* pDbSettings); int GetSettingByName(struct DBContactSettings* pDbSettings, char* pszSettingName, DBVARIANT* pValue); int GetSettingValue(char* pBlob,DBVARIANT* pValue); BOOL GetEvent(HANDLE hDbFile, DWORD dwOffset, DBEVENTINFO* pDBEI); char* GetName(HANDLE hDbFile, DWORD dwOffset); // ====================== // == GLOBAL VARIABLES == // ====================== extern void (*DoImport)(HWND); extern int nImportOption; extern int nCustomOptions; // ===================== // == LOCAL VARIABLES == // ===================== TCHAR importFile[MAX_PATH]; HWND hdlgProgress; DWORD dwFileSize; DWORD nDupes; DWORD nContactsCount; DWORD nMessagesCount; DWORD nGroupsCount; DWORD nSkippedEvents; DWORD nSkippedContacts; time_t dwSinceDate = 0; // ============= // == DEFINES == // ============= #define EVENTTYPE_MESSAGE 0 #define EVENTTYPE_URL 1 #define EVENTTYPE_FILE 1002 // Supported database versions #define DB_INVALID 0x00000000 // Unknown or corrupted DAT #define DB_000700 0x00000700 // Miranda 0.1.0.0 - 0.1.2.2+ // DAT file signature struct DBSignature { char name[15]; BYTE eof; }; static struct DBSignature dbSignature={"Miranda ICQ DB",0x1A}; // Unsane: Secured signature static struct DBSignature dbSignatureEncrypted={"Miranda ICQ SD",0x1A}; // ==================== // ==================== // == IMPLEMENTATION == // ==================== // ==================== static void SearchForLists(HWND hdlg, const TCHAR *mirandaPath, const TCHAR *mirandaProf, const TCHAR *pattern, const TCHAR *type) { HANDLE hFind; WIN32_FIND_DATA fd; TCHAR szSearchPath[MAX_PATH]; TCHAR szRootName[MAX_PATH]; TCHAR* str2; int i; mir_sntprintf(szSearchPath, SIZEOF(szSearchPath), _T("%s\\%s"), mirandaPath, pattern); hFind = FindFirstFile(szSearchPath, &fd); if (hFind != INVALID_HANDLE_VALUE) { do { _tcscpy(szRootName, fd.cFileName); str2 = _tcsrchr(szRootName, '.'); if (str2 != NULL) *str2 = 0; if (mirandaProf == NULL || _tcsicmp(mirandaProf, szRootName)) { _tcscat(szRootName, type); i = SendDlgItemMessage(hdlg, IDC_LIST, LB_ADDSTRING, 0, (LPARAM)szRootName); str2 = (TCHAR*)mir_alloc((_tcslen(mirandaPath) + 2 + _tcslen(fd.cFileName)) * sizeof(TCHAR)); wsprintf(str2, _T("%s\\%s"), mirandaPath, fd.cFileName); SendDlgItemMessage(hdlg, IDC_LIST, LB_SETITEMDATA, i, (LPARAM)str2); } } while( FindNextFile( hFind, &fd )); FindClose( hFind ); } } INT_PTR CALLBACK MirandaPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) { switch(message) { case WM_INITDIALOG: TranslateDialogDefault(hdlg); { TCHAR *pfd, *pfd1, *pfd2, *pfn; REPLACEVARSDATA dat = {0}; dat.cbSize = sizeof(dat); dat.dwFlags = RVF_TCHAR; pfd = (TCHAR*)CallService(MS_UTILS_REPLACEVARS, (WPARAM)_T("%miranda_path%\\Profiles"), (LPARAM)&dat); pfd1 = (TCHAR*)CallService(MS_UTILS_REPLACEVARS, (WPARAM)_T("%miranda_path%"), (LPARAM)&dat); pfd2 = (TCHAR*)CallService(MS_UTILS_REPLACEVARS, (WPARAM)_T("%miranda_profile%"), (LPARAM)&dat); pfn = (TCHAR*)CallService(MS_UTILS_REPLACEVARS, (WPARAM)_T("%miranda_profilename%"), (LPARAM)&dat); SearchForLists(hdlg, pfd2, pfn, _T("*.dat"), _T(" (Miranda IM v0.x)")); // Unsane: check for exclude equal or used profiles if (lstrcmpi(pfd1, pfd2)) SearchForLists(hdlg, pfd1, NULL, _T("*.dat"), _T(" (Miranda IM v0.x)")); if (lstrcmpi(pfd, pfd2)) SearchForLists(hdlg, pfd, NULL, _T("*.dat"), _T(" (Miranda IM v0.x)")); mir_free(pfn); mir_free(pfd2); mir_free(pfd1); mir_free(pfd); return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_BACK: PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_IMPORTTYPE,(LPARAM)ImportTypePageProc); break; case IDOK: { TCHAR filename[MAX_PATH]; GetDlgItemText(hdlg, IDC_FILENAME, filename, SIZEOF(filename)); if (_taccess(filename, 4)) { MessageBox(hdlg, TranslateT("The given file does not exist. Please check that you have entered the name correctly."), TranslateT("Miranda Import"), MB_OK); break; } lstrcpy(importFile, filename); PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_OPTIONS,(LPARAM)MirandaOptionsPageProc); } break; case IDCANCEL: PostMessage(GetParent(hdlg),WM_CLOSE,0,0); break; case IDC_LIST: if(HIWORD(wParam)==LBN_SELCHANGE) { int sel = SendDlgItemMessage(hdlg, IDC_LIST, LB_GETCURSEL, 0, 0); if (sel == LB_ERR) break; SetDlgItemText(hdlg, IDC_FILENAME, (TCHAR*)SendDlgItemMessage(hdlg, IDC_LIST, LB_GETITEMDATA, sel, 0)); } break; case IDC_OTHER: { OPENFILENAME ofn; TCHAR str[MAX_PATH], text[256]; TCHAR *pfd; int index; pfd = Utils_ReplaceVarsT(_T("%miranda_profile%")); // TranslateTS doesnt translate \0 separated strings index = mir_sntprintf(text, 64, _T("%s (*.dat)"), TranslateT("Miranda IM database")) + 1; _tcscpy(text + index, _T("*.dat")); index += 6; index += mir_sntprintf(text + index, 64, _T("%s (*.*)"), TranslateT("All Files")) + 1; _tcscpy(text + index, _T("*.*")); index += 4; text[index] = 0; GetDlgItemText(hdlg, IDC_FILENAME, str, SIZEOF(str)); ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; ofn.hwndOwner = hdlg; ofn.lpstrFilter = text; ofn.lpstrDefExt = _T("dat"); ofn.lpstrFile = str; ofn.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_DONTADDTORECENT; ofn.nMaxFile = SIZEOF(str); ofn.lpstrInitialDir = pfd; if (GetOpenFileName(&ofn)) SetDlgItemText(hdlg,IDC_FILENAME,str); mir_free(pfd); break; } } break; case WM_DESTROY: { int i; for(i=SendDlgItemMessage(hdlg,IDC_LIST,LB_GETCOUNT,0,0)-1;i>=0;i--) mir_free((char*)SendDlgItemMessage(hdlg,IDC_LIST,LB_GETITEMDATA,i,0)); break; } } return FALSE; } INT_PTR CALLBACK MirandaOptionsPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) { switch(message) { case WM_INITDIALOG: TranslateDialogDefault(hdlg); EnableWindow(GetDlgItem(hdlg,IDC_RADIO_ALL), TRUE); EnableWindow(GetDlgItem(hdlg,IDC_STATIC_ALL), TRUE); EnableWindow(GetDlgItem(hdlg,IDC_RADIO_CONTACTS), TRUE); EnableWindow(GetDlgItem(hdlg,IDC_STATIC_CONTACTS), TRUE); EnableWindow(GetDlgItem(hdlg,IDC_RADIO_CUSTOM), TRUE); EnableWindow(GetDlgItem(hdlg,IDC_STATIC_CUSTOM), TRUE); CheckDlgButton(hdlg,IDC_RADIO_ALL,BST_UNCHECKED); return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_BACK: PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_MIRANDADB,(LPARAM)MirandaPageProc); break; case IDOK: if(IsDlgButtonChecked(hdlg,IDC_RADIO_ALL)) { nImportOption = IMPORT_ALL; nCustomOptions = 0;//IOPT_MSGSENT|IOPT_MSGRECV|IOPT_URLSENT|IOPT_URLRECV; DoImport = MirandaImport; PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_PROGRESS,(LPARAM)ProgressPageProc); break; } if(IsDlgButtonChecked(hdlg,IDC_RADIO_CONTACTS)) { nImportOption = IMPORT_CONTACTS; nCustomOptions = 0; DoImport = MirandaImport; PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_PROGRESS,(LPARAM)ProgressPageProc); break; } if(IsDlgButtonChecked(hdlg,IDC_RADIO_CUSTOM)) { PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_ADVOPTIONS,(LPARAM)MirandaAdvOptionsPageProc); break; } break; case IDCANCEL: PostMessage(GetParent(hdlg), WM_CLOSE, 0, 0); break; } break; } return FALSE; } static const UINT InControls[]={IDC_IN_MSG,IDC_IN_URL,IDC_IN_FT,IDC_IN_OTHER}; static const UINT OutControls[]={IDC_OUT_MSG,IDC_OUT_URL,IDC_OUT_FT,IDC_OUT_OTHER}; static const UINT SysControls[]={IDC_CONTACTS, IDC_SYSTEM}; INT_PTR CALLBACK MirandaAdvOptionsPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) { switch(message) { case WM_INITDIALOG: TranslateDialogDefault(hdlg); { struct tm *TM = NULL; struct _SYSTEMTIME ST = {0}; dwSinceDate = DBGetContactSettingDword(NULL,IMPORT_MODULE,"ImportSinceTS",time(NULL)); TM = localtime(&dwSinceDate); ST.wYear = TM->tm_year + 1900; ST.wMonth = TM->tm_mon + 1; ST.wDay = TM->tm_mday; DateTime_SetSystemtime(GetDlgItem(hdlg,IDC_DATETIMEPICKER),GDT_VALID,&ST); } return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_BACK: PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_OPTIONS,(LPARAM)MirandaOptionsPageProc); break; case IDOK: DoImport = MirandaImport; nImportOption = IMPORT_CUSTOM; nCustomOptions = 0; if (IsDlgButtonChecked(hdlg,IDC_CONTACTS)) nCustomOptions |= IOPT_CONTACTS | IOPT_GROUPS; if (IsDlgButtonChecked(hdlg,IDC_SYSTEM)) nCustomOptions |= IOPT_SYSTEM; // incoming if (IsDlgButtonChecked(hdlg,IDC_IN_MSG)) nCustomOptions |= IOPT_MSGRECV; if (IsDlgButtonChecked(hdlg,IDC_IN_URL)) nCustomOptions |= IOPT_URLRECV; if (IsDlgButtonChecked(hdlg,IDC_IN_FT)) nCustomOptions |= IOPT_FILERECV; if (IsDlgButtonChecked(hdlg,IDC_IN_OTHER)) nCustomOptions |= IOPT_OTHERRECV; // outgoing if (IsDlgButtonChecked(hdlg,IDC_OUT_MSG)) nCustomOptions |= IOPT_MSGSENT; if (IsDlgButtonChecked(hdlg,IDC_OUT_URL)) nCustomOptions |= IOPT_URLSENT; if (IsDlgButtonChecked(hdlg,IDC_OUT_FT)) nCustomOptions |= IOPT_FILESENT; if (IsDlgButtonChecked(hdlg,IDC_OUT_OTHER)) nCustomOptions |= IOPT_OTHERSENT; // since date dwSinceDate = 0; if ( IsDlgButtonChecked( hdlg, IDC_SINCE )) { struct _SYSTEMTIME ST = {0}; if (DateTime_GetSystemtime(GetDlgItem(hdlg,IDC_DATETIMEPICKER), &ST) == GDT_VALID) { struct tm TM = {0}; TM.tm_mday = ST.wDay; TM.tm_mon = ST.wMonth - 1; TM.tm_year = ST.wYear - 1900; dwSinceDate = mktime(&TM); DBWriteContactSettingDword(NULL,IMPORT_MODULE,"ImportSinceTS",dwSinceDate); } } if (nCustomOptions) PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_PROGRESS,(LPARAM)ProgressPageProc); break; case IDCANCEL: PostMessage(GetParent(hdlg), WM_CLOSE, 0, 0); break; case IDC_SINCE: EnableWindow(GetDlgItem(hdlg, IDC_DATETIMEPICKER), IsDlgButtonChecked(hdlg, IDC_SINCE)); break; if (HIWORD(wParam) != STN_CLICKED) break; case IDC_ALL: case IDC_INCOMING: case IDC_OUTGOING: { int i; if (LOWORD(wParam) == IDC_ALL) for (i = 0; i < sizeof(SysControls)/sizeof(SysControls[0]); i++) CheckDlgButton(hdlg,SysControls[i], !IsDlgButtonChecked(hdlg,SysControls[i])); if (LOWORD(wParam) != IDC_OUTGOING) for (i = 0; i < sizeof(InControls)/sizeof(InControls[0]); i++) CheckDlgButton(hdlg,InControls[i], !IsDlgButtonChecked(hdlg,InControls[i])); if (LOWORD(wParam) != IDC_INCOMING) for (i = 0; i < sizeof(OutControls)/sizeof(OutControls[0]); i++) CheckDlgButton(hdlg,OutControls[i], !IsDlgButtonChecked(hdlg,OutControls[i])); } break; case IDC_MSG: CheckDlgButton(hdlg,IDC_IN_MSG, !IsDlgButtonChecked(hdlg,IDC_IN_MSG)); CheckDlgButton(hdlg,IDC_OUT_MSG, !IsDlgButtonChecked(hdlg,IDC_OUT_MSG)); break; case IDC_URL: CheckDlgButton(hdlg,IDC_IN_URL, !IsDlgButtonChecked(hdlg,IDC_IN_URL)); CheckDlgButton(hdlg,IDC_OUT_URL, !IsDlgButtonChecked(hdlg,IDC_OUT_URL)); break; case IDC_FT: CheckDlgButton(hdlg,IDC_IN_FT, !IsDlgButtonChecked(hdlg,IDC_IN_FT)); CheckDlgButton(hdlg,IDC_OUT_FT, !IsDlgButtonChecked(hdlg,IDC_OUT_FT)); break; case IDC_OTHER: CheckDlgButton(hdlg,IDC_IN_OTHER, !IsDlgButtonChecked(hdlg,IDC_IN_OTHER)); CheckDlgButton(hdlg,IDC_OUT_OTHER, !IsDlgButtonChecked(hdlg,IDC_OUT_OTHER)); break; } break; } return FALSE; } #ifndef INVALID_SET_FILE_POINTER #define INVALID_SET_FILE_POINTER ((DWORD)-1) #endif // Read header from file, returns null on failure struct DBHeader* GetHeader(HANDLE hDbFile) { struct DBHeader* pdbHeader; DWORD dwBytesRead; if (( pdbHeader = (DBHeader *)calloc(1, sizeof(struct DBHeader))) == NULL ) return NULL; // Goto start of file if (SetFilePointer(hDbFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return FALSE; // Read header if ( !ReadFile(hDbFile, pdbHeader, sizeof(struct DBHeader), &dwBytesRead, NULL ) || dwBytesRead != sizeof(struct DBHeader)) return NULL; // Return pointer to header return pdbHeader; } // Unsane: database crypted status BOOL bEncrypted; // Unsane: crypted database work fuction int CheckPassword(WORD version, WORD cryptorUID, TCHAR * szDBName); void InitSecurity(); int CheckFileFormat(HANDLE hDbFile) { struct DBHeader* pdbHeader; TCHAR* tszDbName; // Read header if (( pdbHeader = GetHeader(hDbFile)) == NULL ) return DB_INVALID; // Check header signature bEncrypted = FALSE; if (memcmp(pdbHeader->signature, &dbSignature, sizeof(pdbHeader->signature))) { // Unsane: if not encrypted signature if (memcmp(pdbHeader->signature, &dbSignatureEncrypted, sizeof(pdbHeader->signature))) { AddMessage( LPGEN("Signature mismatch" )); return DB_INVALID; } AddMessage(LPGEN("Database is Secured MMAP database")); // Unsane: check password InitSecurity(); tszDbName = _tcsrchr(importFile, _T('\\')) + 1; if (CheckPassword(pdbHeader->checkWord, pdbHeader->cryptorUID, tszDbName)) { AddMessage(LPGEN("Secured MMAP: authorization successful")); bEncrypted = TRUE; } else { AddMessage(LPGEN("You are not authorized for access to Database")); return DB_INVALID; } } // Determine Miranda version switch (pdbHeader->checkWord) { case DB_000700: AddMessage( LPGEN("This looks like a Miranda database, version 0.1.0.0 or above." )); free(pdbHeader); return DB_000700; default: if (!bEncrypted){ AddMessage( LPGEN("Version mismatch" )); free(pdbHeader); return DB_INVALID; } break; } return 1; } // High level Miranda DB access functions // Returns true if pValue points to the requested value BOOL GetSetting(HANDLE hDbFile, struct DBContact* pDbContact, char* pszModuleName, char* pszSettingName, DBVARIANT* pValue) { struct DBContactSettings* pDbSettings; if ( pDbSettings = GetSettingsGroupByModuleName(hDbFile, pDbContact, pszModuleName)) { if ( GetSettingByName( pDbSettings, pszSettingName, pValue )) { free(pDbSettings); return TRUE; } #ifdef _LOGGING AddMessage( LPGEN("Failed to find setting %s" ), pszSettingName ); #endif free(pDbSettings); } #ifdef _LOGGING else AddMessage( LPGEN("Failed to find module %s" ), pszModuleName ); #endif // Search failed pValue->type = 0; return FALSE; } // ** // ** CONTACT CHAIN // ** // Return offset to first contact DWORD FindFirstContact(struct DBHeader* pDbHeader) { if (!pDbHeader) return 0; return pDbHeader->ofsFirstContact; } DWORD FindOwnerContact(struct DBHeader* pDbHeader) { if (!pDbHeader) return 0; return pDbHeader->ofsUser; } // Return offset to next contact DWORD FindNextContact(struct DBContact* pDbContact) { if (!pDbContact) return 0; if (pDbContact->signature != DBCONTACT_SIGNATURE) return 0; return pDbContact->ofsNext; } // Read the contact at offset 'dwOffset' // Returns true if successful and pDbContact points to the contact struct // pDbContact must point to allocated struct BOOL GetContact(HANDLE hDbFile, DWORD dwOffset, struct DBContact* pDbContact) { DWORD dwBytesRead; // Early reject if (dwOffset == 0 || dwOffset >= dwFileSize) return FALSE; // ** Read and verify the struct if (SetFilePointer(hDbFile, (LONG)dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return FALSE; if ((!ReadFile(hDbFile, pDbContact, sizeof(struct DBContact), &dwBytesRead, NULL)) || (dwBytesRead != sizeof(struct DBContact))) return FALSE; if ((pDbContact->signature != DBCONTACT_SIGNATURE) || (pDbContact->ofsNext >= dwFileSize)) return FALSE; // Contact corrupted return TRUE; } // Return ptr to next setting in settings struct char* GetNextSetting(char* pDbSetting) { // Get next setting pDbSetting = pDbSetting + *pDbSetting+1; // Skip name switch( *(BYTE*)pDbSetting ) { case DBVT_BYTE: pDbSetting = pDbSetting+1+1; break; case DBVT_WORD: pDbSetting = pDbSetting+1+2; break; case DBVT_DWORD: pDbSetting = pDbSetting+1+4; break; case DBVT_ASCIIZ: case DBVT_UTF8: case DBVT_BLOB: case DBVTF_VARIABLELENGTH: pDbSetting = pDbSetting + 3 + *(WORD*)(pDbSetting+1); break; case DBVT_DELETED: AddMessage( LPGEN("DEBUG: Deleted setting treated as 0-length setting")); pDbSetting = pDbSetting+1; break; default: // Unknown datatype assert AddMessage( LPGEN("ERROR: Faulty settings chain")); return NULL; } return pDbSetting; } // ** // ** SETTINGS CHAIN // ** // Return the settings at offset 'dwOffset' BOOL GetSettingsGroup(HANDLE hDbFile, DWORD dwOffset, struct DBContactSettings** pDbSettings) { DWORD dwBytesRead, dwBlobSize, dwHead; struct DBContactSettings pSettings; // Early reject if (dwOffset == 0 || dwOffset >= dwFileSize) return FALSE; // ** Read and verify the struct if (SetFilePointer(hDbFile, dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return FALSE; dwHead = offsetof(struct DBContactSettings, blob); if ((!ReadFile(hDbFile, &pSettings, dwHead, &dwBytesRead, NULL)) || (dwBytesRead != dwHead)) return FALSE; if (pSettings.signature != DBCONTACTSETTINGS_SIGNATURE) return FALSE; // Setttings corrupted // ** Read the struct and the following blob dwBlobSize = pSettings.cbBlob; if (!(*pDbSettings = (DBContactSettings *)calloc(1, sizeof(struct DBContactSettings) + dwBlobSize))) return FALSE; memcpy(*pDbSettings, &pSettings, dwHead ); if ((!ReadFile(hDbFile, (*pDbSettings)->blob, sizeof(struct DBContactSettings) - dwHead + dwBlobSize, &dwBytesRead, NULL)) || (dwBytesRead != sizeof(struct DBContactSettings) - dwHead + dwBlobSize)) { free(*pDbSettings); return FALSE; } return TRUE; } // pDbContact is a ptr to a struct DBContact // Returns pointer to a struct DBContactSettings or NULL struct DBContactSettings* GetSettingsGroupByModuleName(HANDLE hDbFile, struct DBContact* pDbContact, char* pszName) { char* pszGroupName; struct DBContactSettings* pSettingsGroup; DWORD dwGroupOfs; // Get ptr to first settings group if (!(dwGroupOfs = pDbContact->ofsFirstSettings)) return NULL; // No settings exists in this contact // Loop over all settings groups while (dwGroupOfs && dwGroupOfs < dwFileSize) { pSettingsGroup = NULL; // Read and verify the struct if (!GetSettingsGroup(hDbFile, dwGroupOfs, &pSettingsGroup)) return NULL; // Bad struct // Struct OK, now get the name if ((pszGroupName = GetName(hDbFile, pSettingsGroup->ofsModuleName))) { // Is it the right one? if (strcmp(pszGroupName, pszName) == 0) { #ifdef _LOGGING AddMessage( LPGEN("Found module: %s"), pszGroupName ); #endif return pSettingsGroup; } #ifdef _LOGGING else AddMessage( LPGEN("Ignoring module: %s"), pszGroupName ); #endif } else AddMessage( LPGEN("Warning: Found module with no name")); dwGroupOfs = pSettingsGroup->ofsNext; if (pSettingsGroup) free(pSettingsGroup); } // Search failed return NULL; } // pDbSettings must point to a complete DBContactSettings struct in memory int GetSettingByName(struct DBContactSettings* pDbSettings, char* pszSettingName, DBVARIANT* dbv) { char pszName[256]; // We need at least one setting to start with char* pDbSetting = (char *)pDbSettings->blob; if ( !pDbSetting ) return FALSE; // ** pDbSettings now points to the first setting in this module // Loop over all settings while (pDbSetting && *pDbSetting) { memcpy(pszName, pDbSetting+1, *pDbSetting); pszName[*pDbSetting] = 0; // Is this the right one? if (strcmp(pszSettingName, pszName) == 0) { return GetSettingValue(pDbSetting, dbv); } #ifdef _LOGGING AddMessage( LPGEN("Ignoring setting: %s"), pszName ); #endif pDbSetting = GetNextSetting(pDbSetting); } // Search failed return FALSE; } // dwSettingpointer points to a valid DBSettings struct int GetSettingValue(char* pBlob, DBVARIANT* dbv) { #ifdef _LOGGING { char* pszName = calloc((*pBlob)+1, 1); memcpy(pszName, pBlob+1, *pBlob); AddMessage( LPGEN("Getting type %u value for setting: %s"), (BYTE)*(pBlob+(*pBlob)+1), pszName ); free(pszName); } #endif // Skip name pBlob = pBlob + (*pBlob)+1; dbv->type = ( BYTE )*pBlob++; // Check what type it is switch( dbv->type ) { case DBVT_BYTE: dbv->bVal = *pBlob; return TRUE; case DBVT_WORD: if (bEncrypted) DecodeMemory((BYTE *)pBlob, sizeof(pBlob)); dbv->wVal = *(WORD*)pBlob; return TRUE; case DBVT_DWORD: if (bEncrypted) DecodeMemory((BYTE *)pBlob, sizeof(pBlob)); dbv->dVal = *(DWORD*)pBlob; return TRUE; case DBVT_ASCIIZ: case DBVT_UTF8: dbv->cchVal = *(WORD*)pBlob; dbv->pszVal = (char *)calloc( dbv->cchVal+1, sizeof( char )); if (bEncrypted) DecodeCopyMemory((BYTE *)dbv->pszVal, pBlob+2, dbv->cchVal); else memcpy( dbv->pszVal, pBlob+2, dbv->cchVal ); dbv->pszVal[ dbv->cchVal ] = 0; return TRUE; case DBVTF_VARIABLELENGTH: case DBVT_BLOB: dbv->cpbVal = *(WORD*)pBlob; dbv->pbVal = (BYTE *)calloc( dbv->cpbVal+1, sizeof( char )); if (bEncrypted) DecodeCopyMemory((BYTE *)dbv->pszVal, pBlob+2, dbv->cchVal); else memcpy( dbv->pbVal, pBlob+2, dbv->cpbVal ); dbv->pbVal[ dbv->cpbVal ] = 0; return TRUE; case DBVT_DELETED: AddMessage( LPGEN("DEBUG: Deleted setting treated as 0-length setting")); default: dbv->type = DBVT_DELETED; } return FALSE; } void FreeVariant( DBVARIANT* dbv ) { switch( dbv->type ) { case DBVT_ASCIIZ: case DBVT_UTF8: if ( dbv->pszVal ) free( dbv->pszVal ); break; case DBVTF_VARIABLELENGTH: case DBVT_BLOB: if ( dbv->pbVal ) free( dbv->pbVal ); break; } dbv->type = 0; } void WriteVariant( HANDLE hContact, const char* module, const char* var, DBVARIANT* dbv ) { DBCONTACTWRITESETTING dbw; dbw.szModule = module; dbw.szSetting = var; dbw.value = *dbv; CallService( MS_DB_CONTACT_WRITESETTING, (WPARAM)hContact, (LPARAM)&dbw ); } // Returns true if pDBEI has been filled in with nice values // Don't forget to free those pointers! BOOL GetEvent(HANDLE hDbFile, DWORD dwOffset, DBEVENTINFO* pDBEI) { DWORD dwBytesRead; struct DBEvent pEvent; static char pBlob[65536]; // Early reject if (dwOffset == 0 || dwOffset >= dwFileSize) return FALSE; // ** Read and verify the struct if (SetFilePointer(hDbFile, dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return FALSE; if (!ReadFile(hDbFile, &pEvent, offsetof(struct DBEvent, blob), &dwBytesRead, NULL) || (dwBytesRead != offsetof(struct DBEvent, blob))) return FALSE; if (pEvent.signature != DBEVENT_SIGNATURE) return FALSE; // Event corrupted // ** Read the blob if ((!ReadFile(hDbFile, pBlob, pEvent.cbBlob, &dwBytesRead, NULL)) || (dwBytesRead != pEvent.cbBlob)) { return FALSE; } // ** Copy the static part to the event info struct pDBEI->timestamp = pEvent.timestamp; pDBEI->eventType = pEvent.eventType; pDBEI->cbSize = sizeof(DBEVENTINFO); pDBEI->cbBlob = pEvent.cbBlob; pDBEI->pBlob = (PBYTE)pBlob; pDBEI->flags = (pEvent.flags & ~(DBEF_SENT+DBEF_READ)) + ((pEvent.flags & DBEF_SENT) ? DBEF_SENT : DBEF_READ ); // Imported events are always marked READ if (!(pDBEI->szModule = GetName(hDbFile, pEvent.ofsModuleName))) { return FALSE; } // Unsane: encrypt dbevent if (bEncrypted) DecodeMemory(pDBEI->pBlob, pDBEI->cbBlob); return TRUE; } // Returns a pointer to a string with the name // from a DBModuleName struct if given a file offset // Returns NULL on failure char* GetName(HANDLE hDbFile, DWORD dwOffset) { static DWORD dwLastOffset = 0; static HANDLE hLastDbFile = NULL; static char szName[256] = {0}; DWORD dwBytesRead; struct DBModuleName pModule; // Early reject if (dwOffset == 0 || dwOffset >= dwFileSize) return FALSE; // Quick lookup if (dwOffset == dwLastOffset && hDbFile == hLastDbFile) return szName; // ** Read and verify the name struct if (SetFilePointer(hDbFile, dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return NULL; if ((!ReadFile(hDbFile, &pModule, offsetof(struct DBModuleName, name), &dwBytesRead, NULL)) || (dwBytesRead != offsetof(struct DBModuleName, name))) return NULL; if (pModule.signature != DBMODULENAME_SIGNATURE) { AddMessage( LPGEN("Modulename corrupted")); return NULL; // ModuleName corrupted } // ** Name struct OK, now read name into string buffer if ((!ReadFile(hDbFile, szName, pModule.cbName, &dwBytesRead, NULL)) || (dwBytesRead != pModule.cbName)) { return NULL; } // terminate string szName[pModule.cbName] = 0; // update last offset dwLastOffset = dwOffset; hLastDbFile = hDbFile; return szName; } DWORD FindNextEvent(HANDLE hDbFile, DWORD dwOffset) { DWORD dwBytesRead; struct DBEvent pEvent; // Early reject if (dwOffset == 0 || dwOffset >= dwFileSize) return FALSE; // ** Read and verify the struct if (SetFilePointer(hDbFile, dwOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) return FALSE; if ((!ReadFile(hDbFile, &pEvent, sizeof(struct DBEvent), &dwBytesRead, NULL)) || (dwBytesRead != sizeof(struct DBEvent))) return FALSE; if ( pEvent.signature != DBEVENT_SIGNATURE || pEvent.ofsNext > dwFileSize ) return FALSE; // Event corrupted return pEvent.ofsNext; } int ImportGroups(HANDLE hDbFile, struct DBHeader* pdbHeader) { struct DBContactSettings* pDbSettings; struct DBContact DbContact; char* pSetting; DWORD dwOffset; int nGroups = 0; // Find owner data dwOffset = pdbHeader->ofsUser; if (!GetContact(hDbFile, dwOffset, &DbContact)) { AddMessage( LPGEN("No owner found.")); return -1; } // Find the module with the groups, and import them all if ( pDbSettings = GetSettingsGroupByModuleName( hDbFile, &DbContact, "CListGroups" )) { /*if (bEncrypted) DecodeMemory(pDbSettings->blob, pDbSettings->cbBlob);*/ pSetting = (char *)pDbSettings->blob; while ( pSetting && *pSetting ) { DBVARIANT dbv; if ( GetSettingValue( pSetting, &dbv )) { if ( CreateGroup( dbv.type, dbv.pszVal+1, NULL )) nGroups++; FreeVariant( &dbv ); } pSetting = GetNextSetting(pSetting); } free(pDbSettings); /*if (bEncrypted) EncodeMemory(pDbSettings->blob, pDbSettings->cbBlob);*/ } return nGroups; } HANDLE ImportContact(HANDLE hDbFile, struct DBContact Contact) { HANDLE hContact; DBVARIANT group, nick, dbv; char* pszProtoName; char* pszUniqueSetting; char* pszUserName; char id[ 40 ]; // Check what protocol this contact belongs to if ( !GetSetting( hDbFile, &Contact, "Protocol", "p", &dbv )) { AddMessage( LPGEN("Skipping contact with no protocol")); return INVALID_HANDLE_VALUE; } pszProtoName = NEWSTR_ALLOCA( dbv.pszVal ); FreeVariant( &dbv ); if ( !IsProtocolLoaded( pszProtoName )) { AddMessage( LPGEN("Skipping contact, %s not installed."), pszProtoName ); return INVALID_HANDLE_VALUE; } // Skip protocols with no unique id setting (some non IM protocols return NULL) pszUniqueSetting = (char*)CallProtoService(pszProtoName, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0); if ( !pszUniqueSetting || (INT_PTR)pszUniqueSetting == CALLSERVICE_NOTFOUND ) { AddMessage( LPGEN("Skipping non-IM contact (%s)"), pszProtoName ); return INVALID_HANDLE_VALUE; } if ( !GetSetting(hDbFile, &Contact, pszProtoName, pszUniqueSetting, &dbv )) { AddMessage( LPGEN("Skipping %s contact, ID not found"), pszProtoName ); return INVALID_HANDLE_VALUE; } // Does the contact already exist? if ( dbv.type == DBVT_DWORD ) { pszUserName = _ltoa( dbv.dVal, id, 10 ); hContact = HContactFromNumericID( pszProtoName, pszUniqueSetting, dbv.dVal ); } else { pszUserName = NEWSTR_ALLOCA( dbv.pszVal ); hContact = HContactFromID( pszProtoName, pszUniqueSetting, dbv.pszVal ); } if ( hContact != INVALID_HANDLE_VALUE ) { AddMessage( LPGEN("Skipping duplicate %s contact %s"), pszProtoName, pszUserName ); FreeVariant( &dbv ); return INVALID_HANDLE_VALUE; } // No, add contact and copy some important settings GetSetting(hDbFile, &Contact, "CList", "Group", &group); if ( !GetSetting( hDbFile, &Contact, "CList", "MyHandle", &nick )) GetSetting(hDbFile, &Contact, pszProtoName, "Nick", &nick ); hContact = AddContact( hdlgProgress, pszProtoName, pszUniqueSetting, &dbv, &nick, &group ); if ( hContact != INVALID_HANDLE_VALUE) { // Hidden? if ( GetSetting( hDbFile, &Contact, "CList", "Hidden", &dbv )) { WriteVariant( hContact, "CList", "Hidden", &dbv ); FreeVariant( &dbv ); } // Ignore settings if ( GetSetting( hDbFile, &Contact, "Ignore", "Mask1", &dbv )) { WriteVariant( hContact, "Ignore", "Mask1", &dbv ); FreeVariant( &dbv ); } // Apparent mode if ( GetSetting( hDbFile, &Contact, pszProtoName, "ApparentMode", &dbv )) { WriteVariant( hContact, pszProtoName, "ApparentMode", &dbv ); FreeVariant( &dbv ); } // Nick if ( GetSetting( hDbFile, &Contact, pszProtoName, "Nick", &dbv )) { WriteVariant( hContact, pszProtoName, "Nick", &dbv ); FreeVariant( &dbv ); } // Myhandle if ( GetSetting( hDbFile, &Contact, pszProtoName, "MyHandle", &dbv )) { WriteVariant( hContact, pszProtoName, "MyHandle", &dbv ); FreeVariant( &dbv ); } // First name if ( GetSetting( hDbFile, &Contact, pszProtoName, "FirstName", &dbv )) { WriteVariant( hContact, pszProtoName, "FirstName", &dbv ); FreeVariant( &dbv ); } // Last name if ( GetSetting( hDbFile, &Contact, pszProtoName, "LastName", &dbv )) { WriteVariant( hContact, pszProtoName, "LastName", &dbv ); FreeVariant( &dbv ); } // About if ( GetSetting( hDbFile, &Contact, pszProtoName, "About", &dbv )) { WriteVariant( hContact, pszProtoName, "About", &dbv ); FreeVariant( &dbv ); } } else AddMessage( LPGEN("Unknown error while adding %s contact %s"), pszProtoName, pszUserName ); return hContact; } // This function should always be called after contact import. That is // why there are no messages for errors related to contacts. Those // would only be a repetition of the messages printed during contact // import. static void ImportHistory(HANDLE hDbFile, struct DBContact Contact, PROTOCOLDESCRIPTOR **protocol, int protoCount) { HANDLE hContact = INVALID_HANDLE_VALUE; DWORD dwOffset; MSG msg; DBVARIANT proto; int i, skipAll, bIsVoidContact; // Is it contats history import? if ( protoCount == 0 ) { // Check what protocol this contact belongs to if ( GetSetting( hDbFile, &Contact, "Protocol", "p", &proto )) { // Protocol installed? if ( IsProtocolLoaded( proto.pszVal )) { // Is contact in database? char* pszUniqueSetting = (char*)CallProtoService( proto.pszVal, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0); // Skip protocols with no unique id setting (some non IM protocols return NULL) if ( pszUniqueSetting && ( INT_PTR )pszUniqueSetting != CALLSERVICE_NOTFOUND ) { DBVARIANT dbv; if ( GetSetting( hDbFile, &Contact, proto.pszVal, pszUniqueSetting, &dbv )) { if ( dbv.type == DBVT_DWORD ) hContact = HContactFromNumericID( proto.pszVal, pszUniqueSetting, dbv.dVal ); else hContact = HContactFromID( proto.pszVal, pszUniqueSetting, dbv.pszVal ); FreeVariant( &dbv ); } } } FreeVariant( &proto ); } } else hContact = NULL; //system history import // OK to import this chain? if (hContact == INVALID_HANDLE_VALUE) { nSkippedContacts++; return; } i = skipAll = 0; bIsVoidContact = CallService( MS_DB_EVENT_GETCOUNT, ( WPARAM )hContact, 0 ) == 0; // Get the start of the event chain dwOffset = Contact.ofsFirstEvent; while (dwOffset) { int skip = 0; // Copy the event and import it DBEVENTINFO dbei = { 0 }; if (GetEvent(hDbFile, dwOffset, &dbei)) { // check protocols during system history import if (hContact == NULL) { int i; skipAll = 1; for(i = 0; i < protoCount; i++) if (!strcmp(dbei.szModule, protocol[i]->szName)) { //&& protocol[i]->type == PROTOTYPE_PROTOCOL) skipAll = 0; break; } skip = skipAll; } // custom filtering if (!skip && nImportOption == IMPORT_CUSTOM) { BOOL sent = (dbei.flags&DBEF_SENT); if (dbei.timestamp < (DWORD)dwSinceDate) skip = 1; if (!skip) { if (hContact) { skip = 1; switch(dbei.eventType) { case EVENTTYPE_MESSAGE: if ((sent?IOPT_MSGSENT:IOPT_MSGRECV)&nCustomOptions) skip = 0; break; case EVENTTYPE_FILE: if ((sent?IOPT_FILESENT:IOPT_FILERECV)&nCustomOptions) skip = 0; break; case EVENTTYPE_URL: if ((sent?IOPT_URLSENT:IOPT_URLRECV)&nCustomOptions) skip = 0; break; default: if ((sent?IOPT_OTHERSENT:IOPT_OTHERRECV)&nCustomOptions) skip = 0; break; } } else if ( !( nCustomOptions & IOPT_SYSTEM )) skip = 1; } if (skip) nSkippedEvents++; } if (!skip) { // Check for duplicate entries if ( !IsDuplicateEvent( hContact, dbei )) { // Add dbevent if (!bIsVoidContact) dbei.flags &= ~DBEF_FIRST; if (CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei)) nMessagesCount++; else AddMessage( LPGEN("Failed to add message")); } else nDupes++; } } if ( !( i%10 )) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // skip this chain if needed if ( skipAll ) break; // Get next event dwOffset = FindNextEvent(hDbFile, dwOffset); i++; } } static void MirandaImport(HWND hdlg) { int nDBVersion; int i; int nNumberOfContacts = 0; MSG msg; DWORD dwTimer; DWORD dwOffset; HANDLE hFile; char* pszModuleName = NULL; struct DBHeader* pdbHeader = NULL; struct DBContact Contact; // Just to keep the macros happy hdlgProgress = hdlg; // Reset statistics nSkippedEvents = 0; nDupes = 0; nContactsCount = 0; nMessagesCount = 0; nGroupsCount = 0; nSkippedContacts = 0; SetProgress(0); // Open database hFile = CreateFile(importFile, GENERIC_READ, // open for reading 0, // do not share NULL, // no security OPEN_EXISTING, // existing file only FILE_ATTRIBUTE_NORMAL, // normal file NULL); // no attr. template // Read error if (hFile == INVALID_HANDLE_VALUE) { AddMessage( LPGEN("Could not open file.")); SetProgress(100); return; } // Check filesize dwFileSize = GetFileSize(hFile, NULL) ; if ((dwFileSize == INVALID_FILE_SIZE) || (dwFileSize < sizeof(struct DBHeader))) { AddMessage( LPGEN("This is not a valid Miranda IM database.")); SetProgress(100); CloseHandle(hFile); return; } // Check header and database version nDBVersion = CheckFileFormat(hFile); if (nDBVersion == DB_INVALID) { AddMessage( LPGEN("This is not a valid Miranda IM database.")); SetProgress(100); CloseHandle(hFile); return; } // Load database header if (!(pdbHeader = GetHeader(hFile))) { AddMessage( LPGEN("Read failure.")); SetProgress(100); CloseHandle(hFile); return; } // Get number of contacts nNumberOfContacts = pdbHeader->contactCount; AddMessage( LPGEN("Number of contacts in database: %d"), nNumberOfContacts ); AddMessage( "" ); // Configure database for fast writing CallService(MS_DB_SETSAFETYMODE, FALSE, 0); // Start benchmark timer dwTimer = time(NULL); // Import Groups if (nImportOption == IMPORT_ALL || (nCustomOptions & IOPT_GROUPS)) { AddMessage( LPGEN("Importing groups.")); nGroupsCount = ImportGroups(hFile, pdbHeader); if (nGroupsCount == -1) AddMessage( LPGEN("Group import failed.")); AddMessage( "" ); } // End of Import Groups // Import Contacts if (nImportOption != IMPORT_CUSTOM || (nCustomOptions & IOPT_CONTACTS)) { AddMessage( LPGEN("Importing contacts.")); i = 1; dwOffset = FindFirstContact(pdbHeader); while (dwOffset && (dwOffset < dwFileSize)) { if (!GetContact(hFile, dwOffset, &Contact)) { AddMessage( LPGEN("ERROR: Chain broken, no valid contact at %d"), dwOffset ); SetProgress(100); break; } if (ImportContact(hFile, Contact) != INVALID_HANDLE_VALUE) nContactsCount++; // Update progress bar SetProgress(100 * i / nNumberOfContacts); i++; // Process queued messages if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } // Get next contact in chain dwOffset = FindNextContact(&Contact); } } else AddMessage( LPGEN("Skipping new contacts import.")); AddMessage( "" ); // End of Import Contacts // Import history if (nImportOption != IMPORT_CONTACTS) { // Import NULL contact message chain if (nImportOption == IMPORT_ALL || (nCustomOptions & IOPT_SYSTEM)) { AddMessage( LPGEN("Importing system history.")); dwOffset = FindOwnerContact(pdbHeader); if (!GetContact(hFile, dwOffset, &Contact)) { AddMessage( LPGEN("ERROR: Chain broken, no valid contact at %d"), dwOffset ); SetProgress(100); } else { PROTOCOLDESCRIPTOR **protocol; int protoCount; CallService(MS_PROTO_ENUMPROTOCOLS, (WPARAM)&protoCount, (LPARAM)&protocol); if (protoCount > 0) ImportHistory(hFile, Contact, protocol, protoCount); } } else AddMessage( LPGEN("Skipping system history import.")); AddMessage( "" ); // Import other contact messages if (nImportOption == IMPORT_ALL || (nCustomOptions & 2046)) { // 2 - 1024 types AddMessage( LPGEN("Importing history.")); dwOffset = FindFirstContact(pdbHeader); for(i=1; i <= nNumberOfContacts; i++) { if (!GetContact(hFile, dwOffset, &Contact)) { AddMessage( LPGEN("ERROR: Chain broken, no valid contact at %d"), dwOffset ); SetProgress(100); break; } ImportHistory(hFile, Contact, NULL, 0); SetProgress(100 * i / nNumberOfContacts); dwOffset = FindNextContact(&Contact); } } else AddMessage( LPGEN("Skipping history import.")); AddMessage( "" ); } // End of Import History // Restore database writing mode CallService(MS_DB_SETSAFETYMODE, TRUE, 0); // Clean up before exit CloseHandle(hFile); free(pdbHeader); // Stop timer dwTimer = time(NULL) - dwTimer; // Print statistics AddMessage( LPGEN("Import completed in %d seconds."), dwTimer ); SetProgress(100); AddMessage((nImportOption == IMPORT_CONTACTS) ? LPGEN("Added %d contacts and %d groups.") : LPGEN("Added %d contacts, %d groups and %d events."), nContactsCount, nGroupsCount, nMessagesCount); if ( nImportOption != IMPORT_CONTACTS ) { if (nSkippedContacts) AddMessage( LPGEN("Skipped %d contacts."), nSkippedContacts ); AddMessage((nImportOption == IMPORT_CUSTOM) ? LPGEN("Skipped %d duplicates and %d filtered events.") : LPGEN("Skipped %d duplicates."), nDupes, nSkippedEvents); } }