/* 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 "mirabilis.h" BOOL IsDuplicateEvent(HANDLE hContact, DBEVENTINFO dbei); BOOL IsProtocolLoaded(char* pszProtocolName); HANDLE HContactFromNumericID(char* pszProtoName, char* pszSetting, DWORD dwID); HANDLE AddContact(HWND hdlgProgress, char* pszProtoName, char* pszUniqueSetting, DBVARIANT* id, DBVARIANT* nick, DBVARIANT* group); // ==================== // ==================== // == IMPLEMENTATION == // ==================== // ==================== static void SearchForDatabases(HWND hdlg, const TCHAR *dbPath, const TCHAR *type) { HANDLE hFind; WIN32_FIND_DATA fd; TCHAR szSearchPath[MAX_PATH]; TCHAR szRootName[MAX_PATH],*str2; int i; wsprintf(szSearchPath, _T("%s\\*.idx"), dbPath); hFind=FindFirstFile(szSearchPath,&fd); if(hFind!=INVALID_HANDLE_VALUE) { do { lstrcpy(szRootName,fd.cFileName); str2=_tcsrchr(szRootName,'.'); if(str2!=NULL) *str2=0; if(lstrlen(szRootName)>3 && !lstrcmpi(szRootName+lstrlen(szRootName)-3,_T("tmp"))) continue; lstrcat(szRootName,type); i=SendDlgItemMessage(hdlg,IDC_LIST,LB_ADDSTRING,0,(LPARAM)szRootName); str2 = (TCHAR*)mir_alloc((lstrlen(dbPath) + 2+lstrlen(fd.cFileName))*sizeof(TCHAR)); wsprintf(str2, _T("%s\\%s"), dbPath, fd.cFileName); SendDlgItemMessage(hdlg,IDC_LIST,LB_SETITEMDATA,i,(LPARAM)str2); } while( FindNextFile( hFind, &fd )); FindClose(hFind); } } INT_PTR CALLBACK MirabilisPageProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) { switch(message) { case WM_INITDIALOG: { HKEY hKey; LONG lResult; int i; TranslateDialogDefault(hdlg); if (ERROR_SUCCESS != (lResult = RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Mirabilis\\ICQ\\DefaultPrefs"), 0, KEY_QUERY_VALUE, &hKey))) lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Software\\Mirabilis\\ICQ\\DefaultPrefs"), 0, KEY_QUERY_VALUE, &hKey); if (lResult == ERROR_SUCCESS) { TCHAR dbPath[MAX_PATH]; DWORD cch; cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("New Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (99a)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("99b Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (99b)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("2000a Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (2000a)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("2000b Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (2000b)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("2001a Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (2001a)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("2001b Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (2001b)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("2002a Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (2002a)")); cch=sizeof(dbPath); if(ERROR_SUCCESS==RegQueryValueEx(hKey,_T("2003a Database"),NULL,NULL,(LPBYTE)dbPath,&cch)) SearchForDatabases(hdlg,dbPath,_T(" (2003a)")); } for (i = 0; i < cICQAccounts; i++) { SendDlgItemMessage(hdlg, IDC_MIRABILISACCOUNT, CB_ADDSTRING, 0, (LPARAM)tszICQAccountName[i]); } SendDlgItemMessage(hdlg, IDC_MIRABILISACCOUNT, CB_SETCURSEL, 0, 0); SetTimer(hdlg,1,2000,NULL); SendMessage(hdlg,WM_TIMER,0,0); return TRUE; } case WM_TIMER: { HANDLE hMirabilisMutex; hMirabilisMutex=OpenMutexA(MUTEX_ALL_ACCESS,FALSE,"Mirabilis ICQ Mutex"); if(hMirabilisMutex!=NULL) { CloseHandle(hMirabilisMutex); ShowWindow(GetDlgItem(hdlg,IDC_MIRABILISRUNNING),SW_SHOW); } else ShowWindow(GetDlgItem(hdlg,IDC_MIRABILISRUNNING),SW_HIDE); } break; 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("Mirabilis Import"),MB_OK); break; } lstrcpy(importFile,filename); iICQAccount = SendDlgItemMessage(hdlg, IDC_MIRABILISACCOUNT, CB_GETCURSEL, 0, 0); PostMessage(GetParent(hdlg),WIZM_GOTOPAGE,IDD_OPTIONS,(LPARAM)MirabilisOptionsPageProc); 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]; int index; // TranslateTS doesnt translate \0 separated strings index = mir_sntprintf(text, 64, _T("%s (*.idx)"), TranslateT("Mirabilis ICQ database indexes")) + 1; _tcscpy(text + index, _T("*.idx")); 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.lpstrFile = str; ofn.Flags = OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_DONTADDTORECENT; ofn.nMaxFile = SIZEOF(str); ofn.lpstrDefExt = _T("idx"); if(GetOpenFileName(&ofn)) SetDlgItemText(hdlg,IDC_FILENAME,str); 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 MirabilisOptionsPageProc(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); CheckDlgButton(hdlg, IDC_RADIO_ALL, BST_CHECKED); return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_BACK: PostMessage(GetParent(hdlg), WIZM_GOTOPAGE, IDD_MIRABILISDB, (LPARAM)MirabilisPageProc); break; case IDOK: if (IsDlgButtonChecked(hdlg, IDC_RADIO_ALL)) { DoImport = MirabilisImport; nImportOption = IMPORT_ALL; nCustomOptions = IOPT_MSGSENT|IOPT_MSGRECV|IOPT_URLSENT|IOPT_URLRECV; PostMessage(GetParent(hdlg), WIZM_GOTOPAGE, IDD_PROGRESS, (LPARAM)ProgressPageProc); break; } if (IsDlgButtonChecked(hdlg, IDC_RADIO_CONTACTS)) { DoImport = MirabilisImport; nImportOption = IMPORT_CONTACTS; nCustomOptions = 0; PostMessage(GetParent(hdlg), WIZM_GOTOPAGE, IDD_PROGRESS, (LPARAM)ProgressPageProc); break; } break; case IDCANCEL: PostMessage(GetParent(hdlg), WM_CLOSE, 0, 0); break; } break; } return FALSE; } static int GetHighestIndexEntry(void) { struct TIdxIndexEntry *entry; DWORD ofs; ofs=*(PDWORD)(pIdx+12); for(;;) { entry=(struct TIdxIndexEntry*)(pIdx+ofs); if(entry->entryIdLow==(DWORD)-2) return ((struct TIdxDatEntry*)entry)->entryId; if(entry->ofsHigher>=0xF0000000) ofs=entry->ofsInHere; else ofs=entry->ofsHigher; } } static int GetIdDatOfs(DWORD id) { struct TIdxIndexEntry *entry; DWORD ofs = *(PDWORD)(pIdx+12); for(;;) { entry=(struct TIdxIndexEntry*)(pIdx+ofs); if(entry->entryIdLow==(DWORD)-2) { if(entry->entryIdHigh==id) return ((struct TIdxDatEntry*)entry)->datOfs; return 0; } if(identryIdLow) ofs=entry->ofsLower; else if(entry->ofsHigher<0xF0000000 && id>=entry->entryIdHigh) ofs=entry->ofsHigher; else ofs=entry->ofsInHere; } return 0; } static int GetDatEntryType(DWORD ofs) { return *(int*)(pDat+ofs+4); } DWORD GetDBVersion() { dwDBVersion = *(PDWORD)(pIdx+16); switch (dwDBVersion) { case DBV99A: AddMessage( LPGEN("This looks like a ICQ 99a database.")); break; case DBV99B: AddMessage( LPGEN("This looks like a ICQ 99b database.")); break; case DBV2000A: AddMessage( LPGEN("This looks like a ICQ 2000a database.")); break; case DBV2000B: AddMessage( LPGEN("This looks like a ICQ 2000b database.")); break; case DBV2001A: AddMessage( LPGEN("This looks like a ICQ 2001, 2002 or 2003a database.")); break; default: AddMessage( LPGEN("This database is an unknown version.")); return 0; } return dwDBVersion; } int GetEntryVersion(WORD wSeparatorValue) { int nVersion; if (wSeparatorValue < ENTRYV99A) nVersion = 0; // Cannot handle ICQ98 contacts else if ((wSeparatorValue >= ENTRYV99A) && (wSeparatorValue < ENTRYV99B)) nVersion = ENTRYV99A; else if ((wSeparatorValue >= ENTRYV99B) && (wSeparatorValue < ENTRYV2000A)) nVersion = ENTRYV99B; else if ((wSeparatorValue >= ENTRYV2000A) && (wSeparatorValue < ENTRYV2000B)) nVersion = ENTRYV2000A; else if ((wSeparatorValue >= ENTRYV2000B) && (wSeparatorValue < ENTRYV2001A)) nVersion = ENTRYV2000B; else if ((wSeparatorValue >= ENTRYV2001A) && (wSeparatorValue < ENTRYV2001B)) nVersion = ENTRYV2001A; else if ((wSeparatorValue >= ENTRYV2001B) && (wSeparatorValue < ENTRYV2002A)) nVersion = ENTRYV2001B; else if (wSeparatorValue >= ENTRYV2002A) nVersion = ENTRYV2002A; else nVersion = ENTRYVUNKNOWN; // Just in case... Skip undocumented contact versions return nVersion; } DWORD ReadSubList(DWORD dwOffset) { DWORD dwSubType, dwProperties, n; #ifdef _LOGGING AddMessage( LPGEN("Attempting to parse sub list at offset %u."), dwOffset); #endif // Check number of properties in sub list dwProperties = *(PDWORD)(pDat+dwOffset); dwOffset+=4; // Check sub list type dwSubType = *(PBYTE)(pDat+dwOffset); dwOffset+=1; switch (dwSubType){ case 0x6B: for(n=0;n 1) return 6 + (char*)(pDat + dwOffset); break; } else // Skip to next group dwOffset += *(PWORD)(pDat + dwOffset + 4) + 12; } break; case DBV2000A: case DBV2000B: case DBV2001A: for (n = 0; n < dwGroups; n++){ if (tmpOfs = ReadPropertyBlock(dwOffset, "GroupID", &nSearchResult)){ if (nSearchResult) { if (dwGroupID == *(PDWORD)(pDat + tmpOfs + 1)){ strGroupName = 3 + (char*)(pDat + ReadPropertyBlock(dwOffset, "GroupName", &nSearchResult)); if (nSearchResult) { if ((DWORD)*(strGroupName - 2) > 1) return strGroupName; break; } } } } // Skip to next group if ( dwOffset != ReadPropertyBlock(dwOffset, NULL, NULL)) break; } break; } // The GroupID was not found, or it was found // but the group did not have a name, or there // was an error during parsing. return 0; } // ------------------------------------------------ // Scans a group list and adds all found groups to // the Miranda contact list // ------------------------------------------------ // dwOffset must point to the number of entries in // the following group list. // Returns the number of added groups, or -1 if an error // occurred int ImportGroups() { DWORD dwGroups, n, tmpOfs, dwOffset; int nImported = 0; int nSearchResult, nFormat; WORD wSeparatorValue; if (!(dwOffset = FindMyDetails())) { AddMessage( LPGEN("ERROR: Failed to find owner information.")); return -1; } wSeparatorValue = *(PWORD)(pDat + dwOffset + 0x1c); nFormat = GetEntryVersion(wSeparatorValue); dwGroupListOfs = dwOffset = FindGroupList(dwOffset); if (!dwOffset) { AddMessage( LPGEN("ERROR: Failed to find contact list groups.")); #ifdef _LOGGING { // If this is a debug build, dump MyDetails block to disk FILE *stream; DWORD dwSize; dwOffset = FindMyDetails(); dwSize = *(PDWORD)(pDat + dwOffset); stream = fopen("import_grouplist_dump.bin", "w"); fwrite(pDat + dwOffset, 1, dwSize, stream); fclose(stream); } #endif return -1; } // Check number of groups dwGroups = *(PDWORD)(pDat + dwOffset); if (dwGroups > 0) AddMessage( LPGEN("Importing groups.")); else { AddMessage( LPGEN("This database does not contain any contact groups.")); return 0; } dwOffset += 4; // Import all groups with a name switch (nFormat) { case ENTRYV99A: case ENTRYV99B: for (n = 0; n < dwGroups; n++){ if (*(PWORD)(pDat+dwOffset+4) > 1) { if ( CreateGroup(DBVT_ASCIIZ, (char*)(pDat + dwOffset) + 6, NULL )) nImported++; dwOffset += *(PWORD)(pDat + dwOffset + 4) + 12; } } break; case ENTRYV2000A: case ENTRYV2000B: case ENTRYV2001A: case ENTRYV2001B: case ENTRYV2002A: for (n = 0; n < dwGroups; n++){ if (tmpOfs = ReadPropertyBlock(dwOffset, "GroupName", &nSearchResult)){ if (nSearchResult) { if (CreateGroup( DBVT_ASCIIZ, (char*)(pDat + tmpOfs + 3), NULL )) nImported++; } } dwOffset = ReadPropertyBlock(dwOffset, NULL, NULL); if (!dwOffset) { AddMessage( LPGEN("ERROR: An error occurred while importing groups.")); AddMessage( LPGEN("All groups may not have not been imported.")); #ifdef _LOGGING { // If this is a debug build, dump MyDetails block to disk FILE *stream; DWORD dwSize; dwOffset = FindMyDetails(); dwSize = *(PDWORD)(pDat + dwOffset); stream = fopen("import_grouplist_dump.bin", "w"); fwrite(pDat + dwOffset, 1, dwSize, stream); fclose(stream); } #endif return -1; } } break; default: return -1; } return nImported; } // Imports the contact at offset dwOffset // Returns the HANDLE of the Miranda contact // or INVALID_HANDLE_VALUE on failure HANDLE ImportContact(DWORD dwOffset) { int nContactVersion, nSearchResult; BYTE Status; WORD wSeparatorValue; DWORD dwGroup, dwUIN = 0, tmpOfs = 0; char *strNickname = 0, *strGroupName = 0; if (*(int*)(pDat + dwOffset + 4) != DATENTRY_CONTACT) return INVALID_HANDLE_VALUE; if (*(int*)(pDat + dwOffset + 0x1e) != 'USER') return INVALID_HANDLE_VALUE; #ifdef _LOGGING { // If this is a debug build, dump contact to disk FILE *stream; DWORD dwSize; dwSize = *(PDWORD)(pDat + dwOffset); stream = fopen("import_last_contact.bin", "w"); fwrite(pDat + dwOffset, 1, dwSize, stream); fclose(stream); } #endif Status = *(pDat + dwOffset + 0x22); wSeparatorValue = *(PWORD)(pDat + dwOffset + 0x1c); nContactVersion = GetEntryVersion(wSeparatorValue); dwGroup = *(PDWORD)(pDat + dwOffset + 0x26); if (dwGroup >= 1000) strGroupName = GetGroupName(dwGroup); if (Status == 5) return INVALID_HANDLE_VALUE; // Skip deleted contacts if ((Status != 2) && (Status != 3)) { AddMessage( LPGEN("Skipping inactive contact.")); return INVALID_HANDLE_VALUE; } if ((nContactVersion < ENTRYV99A) || (nContactVersion == 0)) { AddMessage( LPGEN("Skipping contact with unsupported version.")); return INVALID_HANDLE_VALUE; } switch(nContactVersion){ case ENTRYV99A: if (!(dwOffset = ReadWavList(dwOffset + 0x54))) return INVALID_HANDLE_VALUE; if (!(dwOffset = ReadPropertyBlock(dwOffset + 0x26, NULL, NULL))) return INVALID_HANDLE_VALUE; // Check for custom nickname if (*(PWORD)(pDat + dwOffset) > 1) strNickname = (char*)(dwOffset + pDat + 2); // Find UIN dwOffset += *(PWORD)(pDat + dwOffset) + 2; // Custom nick name dwOffset += *(PWORD)(pDat + dwOffset) + 2; // Nick name dwOffset += *(PWORD)(pDat + dwOffset) + 2; // First name dwOffset += *(PWORD)(pDat + dwOffset) + 2; // Last name dwOffset += *(PWORD)(pDat + dwOffset) + 2; // E-mail dwUIN = *(PDWORD)(pDat + dwOffset); // UIN break; case ENTRYV99B: case ENTRYV2000A: case ENTRYV2000B: if (!(dwOffset = ReadWavList(dwOffset + 0x2C))) return INVALID_HANDLE_VALUE; tmpOfs = ReadPropertyBlockList(dwOffset + 0x02, "UIN", &nSearchResult); if (nSearchResult) dwUIN = *(PDWORD)(pDat + tmpOfs + 1); tmpOfs = ReadPropertyBlockList(dwOffset + 0x02, "MyDefinedHandle", &nSearchResult); if (nSearchResult) strNickname = (char*)(tmpOfs + pDat + 3); break; case ENTRYV2001A: case ENTRYV2001B: tmpOfs = ReadPropertyBlockList(dwOffset + 0x2C, "MyDefinedHandle", &nSearchResult); if (nSearchResult) strNickname = (char*)(tmpOfs + pDat + 3); tmpOfs = ReadPropertyBlockList(dwOffset + 0x2C, "UIN", &nSearchResult); if (nSearchResult) dwUIN = *(PDWORD)(pDat + tmpOfs + 1); break; case ENTRYV2002A: tmpOfs = ReadPropertyBlockList(dwOffset + 0x32, "MyDefinedHandle", &nSearchResult); if (nSearchResult) strNickname = (char*)(tmpOfs + pDat + 3); tmpOfs = ReadPropertyBlockList(dwOffset + 0x32, "UIN", &nSearchResult); if (nSearchResult) dwUIN = *(PDWORD)(pDat + tmpOfs + 1); break; } if (!dwUIN) { AddMessage( LPGEN("Skipping unrecognizable contact.")); return INVALID_HANDLE_VALUE; } if (dwUIN < 10000) { AddMessage( LPGEN("Skipping non-ICQ contact %u."), dwUIN ); return INVALID_HANDLE_VALUE; } if (HContactFromNumericID( szICQModuleName[ iICQAccount ], "UIN", dwUIN) == INVALID_HANDLE_VALUE) { DBVARIANT id, nick, group; id.type = DBVT_DWORD; id.dVal = dwUIN; if ( strNickname != NULL && strlen(strNickname) > 0 ) nick.type = DBVT_ASCIIZ, nick.pszVal = strNickname; else nick.type = DBVT_DELETED; group.type = DBVT_ASCIIZ, group.pszVal = strGroupName; return AddContact(hdlgProgress, szICQModuleName[ iICQAccount ], "UIN", &id, &nick, &group); } else { if ((strNickname != NULL) && (strlen(strNickname) > 0)) AddMessage( LPGEN("Skipping duplicate ICQ contact %u, %s"), dwUIN, strNickname); else AddMessage( LPGEN("Skipping duplicate ICQ contact %u"), dwUIN); } // Failure return INVALID_HANDLE_VALUE; } BOOL ImportMessage(DWORD dwOffset) { struct TDatMessage *msg = (struct TDatMessage*)(pDat + dwOffset); struct TDatEntryFooter *footer; DBEVENTINFO dbei; HANDLE hContact; int nUCTOffset; TIME_ZONE_INFORMATION TimeZoneInformation; int nHistoryCount = 0; // Get timestamp offset. In ICQ, event timestamps are stored // as UTC + (0-TZ offset). YES! That's the negation of the // timezone offset, only God and Mirabilis knows why. GetTimeZoneInformation(&TimeZoneInformation); nUCTOffset = -TimeZoneInformation.Bias * 60; // Ignore messages in 'Deleted' folder if (msg->filingStatus&FILING_DELETED) return FALSE; // Skip messages from non-icq contacts if (msg->uin < 10000) { AddMessage( LPGEN("Ignoring msg from user %d at ofs %d."), msg->uin, dwOffset ); return FALSE; } // Ignore received messages? if (( msg->filingStatus & FILING_RECEIVED ) && !( nCustomOptions & IOPT_MSGRECV )) return FALSE; // Ignores sent messages? if ( !(msg->filingStatus & FILING_RECEIVED) && !( nCustomOptions & IOPT_MSGSENT )) return FALSE; // Check if contact exists in Miranda database hContact = HistoryImportFindContact(hdlgProgress, szICQModuleName[ iICQAccount ], msg->uin, nCustomOptions&IOPT_ADDUNKNOWN); if (hContact == INVALID_HANDLE_VALUE) return FALSE; // Contact couldn't be found/added // Convert the event to a Miranda dbevent footer = (struct TDatEntryFooter*)(pDat + dwOffset + msg->textLen + offsetof(struct TDatMessage, text)); ZeroMemory(&dbei, sizeof(dbei)); dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = footer->sent == 1 ? DBEF_SENT : DBEF_READ; dbei.szModule = szICQModuleName[ iICQAccount ]; // Convert timestamp dbei.timestamp = footer->timestamp + nUCTOffset; dbei.cbBlob = msg->textLen; dbei.pBlob = (PBYTE)alloca(msg->textLen); CopyMemory(dbei.pBlob, msg->text, dbei.cbBlob); dbei.pBlob[dbei.cbBlob - 1] = 0; // Check for duplicate entries if (IsDuplicateEvent(hContact, dbei)) { nDupes++; } else { if (CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei)) nMessagesCount++; } return TRUE; } BOOL ImportExtendedMessage(DWORD dwOffset) { struct TDatMessage *msg = (struct TDatMessage*)(pDat + dwOffset); struct TDatEntryFooter *footer; DBEVENTINFO dbei; HANDLE hContact; int nUCTOffset; TIME_ZONE_INFORMATION TimeZoneInformation; int nHistoryCount = 0; char* pszText = 0; DWORD dwRichTextOffset = 0; DWORD wRichTextLength = 0; DWORD wLength = 0; BOOL bFreeMe = FALSE; // Get timestamp offset. In ICQ, event timestamps are stored // as UTC + (0-TZ offset). YES! That's the negation of the // timezone offset, only God and Mirabilis knows why. GetTimeZoneInformation(&TimeZoneInformation); nUCTOffset = -TimeZoneInformation.Bias * 60; // Ignore messages in 'Deleted' folder if (msg->filingStatus&FILING_DELETED) return FALSE; // Skip messages from non-icq contacts if (msg->uin < 10000) { AddMessage( LPGEN("Ignoring msg from user %d at ofs %d."), msg->uin, dwOffset ); return FALSE; } // Ignore received messages? if (( msg->filingStatus & FILING_RECEIVED) && !( nCustomOptions & IOPT_MSGRECV )) return FALSE; // Ignore sent messages? if ( !( msg->filingStatus & FILING_RECEIVED ) && !( nCustomOptions & IOPT_MSGSENT )) return FALSE; // Check if contact exists in Miranda database hContact = HistoryImportFindContact(hdlgProgress, szICQModuleName[ iICQAccount ], msg->uin, nCustomOptions&IOPT_ADDUNKNOWN); if (hContact == INVALID_HANDLE_VALUE) return FALSE; // Contact couldn't be found/added // Find a piece of usable text content if (msg->textLen <= 1) { // Skip past the RTF segment wRichTextLength = *(PWORD)(pDat + dwOffset + 0x2A + msg->textLen + 0x21); dwRichTextOffset = dwOffset + 0x2A + msg->textLen + 0x23; // Use the UTF-8 text segment wLength = *(PWORD)(pDat + dwRichTextOffset + wRichTextLength); if (wLength <= 1) { AddMessage( LPGEN("Ignoring msg with no text from %d ofs %d."), msg->uin, dwOffset ); return FALSE; } pszText = _strdup(pDat + dwRichTextOffset + wRichTextLength + 2); bFreeMe = TRUE; mir_utf8decode(pszText, NULL); wLength = (DWORD)strlen(pszText)+1; } else { // Use the ANSI text segment wLength = msg->textLen; pszText = pDat + dwOffset + 0x2A; } // Convert the event to a Miranda dbevent footer = (struct TDatEntryFooter*)(pDat + dwOffset + msg->textLen + offsetof(struct TDatMessage, text)); ZeroMemory(&dbei, sizeof(dbei)); dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = footer->sent == 1 ? DBEF_SENT : DBEF_READ; dbei.szModule = szICQModuleName[ iICQAccount ]; // Convert timestamp dbei.timestamp = footer->timestamp + nUCTOffset; dbei.cbBlob = wLength; dbei.pBlob = (PBYTE)calloc(wLength,1); CopyMemory(dbei.pBlob, pszText, dbei.cbBlob); dbei.pBlob[dbei.cbBlob - 1] = 0; // Check for duplicate entries if (IsDuplicateEvent(hContact, dbei)) { nDupes++; } else { if (CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei)) nMessagesCount++; } free(dbei.pBlob); if (bFreeMe) free(pszText); return TRUE; } BOOL ImportURLMessage(DWORD dwOffset) { struct TDatMessage *msg = (struct TDatMessage*)(pDat + dwOffset); struct TDatEntryFooter *footer; DBEVENTINFO dbei; HANDLE hContact; int nUCTOffset; TIME_ZONE_INFORMATION TimeZoneInformation; int nHistoryCount = 0; char *pSeparator; // Get timestamp offset. In ICQ, event timestamps are stored // as UTC + (0-TZ offset). YES! That's the negation of the // timezone offset, only God and Mirabilis knows why. GetTimeZoneInformation(&TimeZoneInformation); nUCTOffset = -TimeZoneInformation.Bias * 60; // Ignore URLs in 'Deleted' folder if (msg->filingStatus&FILING_DELETED) return FALSE; // Skip URLs from non-icq contacts if (msg->uin < 10000) { AddMessage( LPGEN("Ignoring msg from user %d at ofs %d."), msg->uin, dwOffset ); return FALSE; } // Ignore received URLs? if (( msg->filingStatus & FILING_RECEIVED ) && !( nCustomOptions & IOPT_URLRECV )) return FALSE; // Ignores sent URLs? if ( !( msg->filingStatus & FILING_RECEIVED ) && !( nCustomOptions & IOPT_URLSENT )) return FALSE; // Check if contact exists in Miranda database hContact = HistoryImportFindContact(hdlgProgress, szICQModuleName[ iICQAccount ], msg->uin, nCustomOptions&IOPT_ADDUNKNOWN); if (hContact == INVALID_HANDLE_VALUE) return FALSE; // Contact couldn't be found/added // Convert the event to a Miranda dbevent footer = (struct TDatEntryFooter*)(pDat + dwOffset + msg->textLen + offsetof(struct TDatMessage, text)); ZeroMemory(&dbei, sizeof(dbei)); dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_URL; dbei.flags = footer->sent == 1 ? DBEF_SENT : DBEF_READ; dbei.szModule = szICQModuleName[ iICQAccount ]; // Convert timestamp dbei.timestamp = footer->timestamp + nUCTOffset; dbei.cbBlob = msg->textLen; dbei.pBlob = (PBYTE)alloca(msg->textLen); CopyMemory(dbei.pBlob, msg->text, dbei.cbBlob); dbei.pBlob[dbei.cbBlob - 1] = 0; // Separate URL and description pSeparator = strchr((char*)dbei.pBlob, 0xFE); if (pSeparator != NULL) *pSeparator = 0; // Check for duplicate entries if (IsDuplicateEvent(hContact, dbei)) nDupes++; else if (CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei)) nMessagesCount++; return TRUE; } BOOL ImportEvent(DWORD dwOffset) { struct TDatMessage *msg = (struct TDatMessage*)(pDat + dwOffset); // Events have IDs > 2000 if (msg->hdr.entryId < 2001) { AddMessage( LPGEN("Skipping event with ID < 2001.")); return FALSE; } // Separate code paths based on the event signature switch (msg->hdr.subType) { case SUBTYPE_MESSAGE: // All kinds of messages switch (msg->type) { case 1: // Normal message if ((nCustomOptions&IOPT_MSGRECV) || (nCustomOptions&IOPT_MSGSENT)) { return ImportMessage(dwOffset); } break; case 4: // URL if ((nCustomOptions&IOPT_URLSENT) || (nCustomOptions&IOPT_URLRECV)) { return ImportURLMessage(dwOffset); } break; case 6: // Request for authorization #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Request for auth.' msg, ofs %d."), dwOffset ); #endif break; case 7: // Authorization request denied #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Auth. denied' msg, ofs %d."), dwOffset ); #endif break; case 8: // Authorization request accepted #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Auth. accepted' msg, ofs %d."), dwOffset ); #endif break; case 9: // System message #ifdef _LOGGING AddMessage( LPGEN("Skipping 'System message', ofs %d."), dwOffset ); #endif break; case 12: // You were added #ifdef _LOGGING AddMessage( LPGEN("Skipping 'You were added' msg, ofs %d."), dwOffset ); #endif break; case 13: // WWWPager ? #ifdef _LOGGING AddMessage( LPGEN("Skipping 'WWW Pager' msg, ofs %d."), dwOffset ); #endif break; case 14: // Email Express ? #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Email Express' msg, ofs %d."), dwOffset ); #endif break; case 19: // Contact list #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Contact' msg, ofs %d."), dwOffset ); #endif break; case 21: // Phonecall request? #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Phonecall' msg (?), ofs %d."), dwOffset ); #endif break; case 26: // SMS request? #ifdef _LOGGING AddMessage( LPGEN("Skipping 'SMS' msg (?), ofs %d."), dwOffset ); #endif break; case 29: // Active list invitation ?? #ifdef _LOGGING AddMessage( LPGEN("Skipping 29 msg, ofs %d."), dwOffset ); #endif break; case 30: // Birthday reminder #ifdef _LOGGING AddMessage( LPGEN("Skipping 'Birthday' msg (?), ofs %d."), dwOffset ); #endif break; case 32: // Unknown (Tomer) #ifdef _LOGGING AddMessage( LPGEN("Skipping 32 msg, ofs %d."), dwOffset ); #endif break; default: AddMessage( LPGEN("Skipping unknown 0xE0 subtype (%d), ofs %d."), msg->type, dwOffset ); #ifdef _LOGGING { // If this is a debug build, dump entry to disk FILE *stream; DWORD dwSize = *(PDWORD)(pDat + dwOffset); wsprintfA(str, "import_unknown_E0subtype_%u-%u.bin", msg->type, dwOffset); stream = fopen(str, "w"); fwrite(pDat + dwOffset, 1, dwSize, stream); fclose(stream); } #endif return FALSE; } break; case SUBTYPE_CHATREQUEST: // 0xE1 #ifdef _LOGGING if (nImportOption != IMPORT_CONTACTS) AddMessage( LPGEN("Skipping 'Chat request' msg, ofs %d."), dwOffset ); #endif break; case SUBTYPE_FILEREQUEST: // 0xE2 #ifdef _LOGGING if (nImportOption != IMPORT_CONTACTS) AddMessage( LPGEN("Skipping file message offset %d."), dwOffset ); #endif break; case 0xE3: // External (IPhone, Battlecom) Maybe general voice calls? #ifdef _LOGGING if (nImportOption != IMPORT_CONTACTS) AddMessage( LPGEN("Skipping message type 0xE3 at offset %d."), dwOffset ); #endif break; case 0xE4: // My details break; case 0xE5: // Contact break; case 0xE6: // Reminder break; case 0xE7: // Addressbook break; case 0xEC: // Voice message break; case 0xED: // Unknown, something to do with chatting and .CHT files // if (importHistory) { // wsprintf(str, "Skipping message type 0xED at offset %d.", dwOffset); // AddMessage( LPGEN(str); // } break; case 0xEE: // Note break; case 0xEF: // Event folder break; // case 0xF0: // Unknown // if (importHistory) { // wsprintf(str, "Skipping message type 0xF0 at offset %d.", dwOffset); // AddMessage( LPGEN(str); // } // break; case 0xF1: // Server list break; // case 0xF6: // Unknown // if (importHistory) { // wsprintf(str, "Skipping message type 0xF6 at offset %d.", dwOffset); // AddMessage( LPGEN(str); // } // break; case 0x50: // Extended message, ICQ 2000a+? if (nImportOption != IMPORT_CONTACTS) { return ImportExtendedMessage(dwOffset); } break; case 0xA0: // URL message type 2 if (nImportOption != IMPORT_CONTACTS) { if ((msg->filingStatus&FILING_RECEIVED) || (nCustomOptions&IOPT_URLRECV)) { return ImportURLMessage(dwOffset); } } break; default: if (nImportOption != IMPORT_CONTACTS) { AddMessage( LPGEN("Skipping unknown event type %d at offset %d."), msg->hdr.subType, dwOffset ); #ifdef _LOGGING { // If this is a debug build, dump entry to disk FILE *stream; DWORD dwSize; dwSize = *(PDWORD)(pDat + dwOffset); wsprintfA(str, "import_unknown_eventtype_%u-%u.bin", msg->hdr.subType, dwOffset); stream = fopen(str, "w"); fwrite(pDat + dwOffset, 1, dwSize, stream); fclose(stream); } #endif } break; } return FALSE; } static void MirabilisImport(HWND hdlgProgressWnd) { HANDLE hIdx, hDat, hIdxMapping, hDatMapping; DWORD i, ofs, highestIndexEntry; TCHAR datFilename[MAX_PATH]; MSG msg; DWORD dwTimer; int status = 0; hdlgProgress = hdlgProgressWnd; nDupes = nContactsCount = nMessagesCount = 0; SetProgress(0); lstrcpy(datFilename, importFile); { TCHAR* str2; str2 = _tcsrchr(datFilename,'.'); if ( str2 != NULL ) lstrcpy(str2, _T(".dat")); } hIdx = CreateFile(importFile, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hIdx == INVALID_HANDLE_VALUE) { AddMessage( LPGEN("Failed to open index file")); AddMessage( LPGEN("Import aborted")); SetProgress(100); return; } hDat = CreateFile(datFilename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDat == INVALID_HANDLE_VALUE) { AddMessage( LPGEN("Failed to open database file")); AddMessage( LPGEN("Import aborted")); SetProgress(100); return; } // Creating file mappings hIdxMapping = CreateFileMapping(hIdx, NULL, PAGE_READONLY, 0, 0, NULL); hDatMapping = CreateFileMapping(hDat, NULL, PAGE_READONLY, 0, 0, NULL); // Mapping views of files pIdx = (PBYTE)MapViewOfFile(hIdxMapping, FILE_MAP_READ, 0, 0, 0); pDat = (PBYTE)MapViewOfFile(hDatMapping, FILE_MAP_READ, 0, 0, 0); // Is this a supported format? if (GetDBVersion()) { AddMessage( "" ); highestIndexEntry = GetHighestIndexEntry(); // Import groups nGroupsCount = ImportGroups(); if (nGroupsCount < 0) { AddMessage( LPGEN("Group import was not completed.")); nGroupsCount = 0; } AddMessage( "" ); // Start benchmark timer dwTimer = time(NULL); if ( !IsProtocolLoaded( szICQModuleName[iICQAccount] )) { AddMessage( LPGEN("ICQ account is not installed.")); AddMessage( LPGEN("No ICQ contacts or history will be imported.")); AddMessage( "" ); } else { // Configure database for fast writing CallService(MS_DB_SETSAFETYMODE, FALSE, 0); // Import contacts AddMessage( LPGEN("Importing contacts")); for (i = 2001; i <= highestIndexEntry; i++) { //event ids start at 2001 if (!(i%10)) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } if (!(i%100)) SetProgress(100 * (i - 2001) / (highestIndexEntry - 2001)); ofs = GetIdDatOfs(i); if (ofs != 0) { if (ImportContact(ofs) != INVALID_HANDLE_VALUE) nContactsCount++; } } AddMessage( "" ); // Import history if (nImportOption != IMPORT_CONTACTS) { AddMessage( LPGEN("Importing history (this may take a while)")); for (i = 2001; i <= highestIndexEntry; i++) { //event ids start at 2001 if (!(i%10)) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } if (!(i%100)) SetProgress(100 * (i - 2001) / (highestIndexEntry - 2001)); ofs = GetIdDatOfs(i); if (ofs != 0) ImportEvent(ofs); } AddMessage( "" ); } // Restore database writing mode CallService(MS_DB_SETSAFETYMODE, TRUE, 0); } dwTimer = time(NULL) - dwTimer; AddMessage( LPGEN("Import completed in %d seconds."), dwTimer ); SetProgress(100); AddMessage( LPGEN("Added %d contacts and %d groups."), nContactsCount, nGroupsCount ); if ( nImportOption != IMPORT_CONTACTS ) AddMessage( LPGEN("Added %d events and skipped %d duplicates."), nMessagesCount, nDupes ); } UnmapViewOfFile(pDat); UnmapViewOfFile(pIdx); CloseHandle(hDatMapping); CloseHandle(hIdxMapping); CloseHandle(hDat); CloseHandle(hIdx); }