/* Derived from source code of TrueCrypt 7.1a, which is Copyright (c) 2008-2012 TrueCrypt Developers Association and which is governed by the TrueCrypt License 3.0. Modifications and additions to the original source code (contained in this file) and all other portions of this file are Copyright (c) 2013-2017 IDRIX and are governed by the Apache License 2.0 the full text of which is contained in the file License.txt included in VeraCrypt binary and source code distribution packages. */ #include "Language.h" #include "Dlgcode.h" #include "Dictionary.h" #include "Tcdefs.h" #include "Xml.h" #include "../Common/Resource.h" #ifdef TCMOUNT #include "../Mount/Resource.h" #endif #ifdef VOLFORMAT #include "../Format/Resource.h" #endif #ifdef SETUP #include "../Setup/Resource.h" #endif #include BOOL LocalizationActive; int LocalizationSerialNo; wchar_t UnknownString[1024] = {0}; static char *LanguageFileBuffer = NULL; static HANDLE LanguageFileFindHandle = INVALID_HANDLE_VALUE; static char PreferredLangId[6] = {0}; static char *LanguageResource = NULL; static DWORD LanguageResourceSize = 0; static char *HeaderResource[2] = {NULL, NULL}; static DWORD HeaderResourceSize[2] = {0, 0}; static char ActiveLangPackVersion[6] = {0}; static int LanguageResourceId = 0; static char *MapFirstLanguageFile () { if (LanguageFileFindHandle != INVALID_HANDLE_VALUE) { FindClose (LanguageFileFindHandle); LanguageFileFindHandle = INVALID_HANDLE_VALUE; } if (LanguageFileBuffer != NULL) { free (LanguageFileBuffer); LanguageFileBuffer = NULL; } LanguageResourceId = 0; if (LanguageResource == NULL) { DWORD size; LanguageResource = MapResource (L"Xml", IDR_LANGUAGE, &size); if (LanguageResource) LanguageResourceSize = size; } if (LanguageResource) { LanguageFileBuffer = malloc(LanguageResourceSize + 1); if (LanguageFileBuffer) { memcpy (LanguageFileBuffer, LanguageResource, LanguageResourceSize); LanguageFileBuffer[LanguageResourceSize] = 0; } } return LanguageFileBuffer; } static int IsValidLanguageFileName(const wchar_t* filename) { size_t len = wcslen(filename); // Check the base format and length directly if (_wcsnicmp(filename, L"Language.", 9) != 0 || (len != 15 && len != 18)) return 0; // Does not start with "Language." or has incorrect length // Check for the ".xml" suffix if (_wcsicmp(filename + len - 4, L".xml") != 0) return 0; // Does not end with ".xml" // Detailed checks based on the specific length if (len == 15) { // Format should be Language.xx.xml if (iswalpha(filename[9]) && iswalpha(filename[10])) return 1; // Valid format for short code } else if (len == 18) { // Format should be Language.xx-yy.xml if (iswalpha(filename[9]) && iswalpha(filename[10]) && filename[11] == L'-' && iswalpha(filename[12]) && iswalpha(filename[13])) return 1; // Valid format for long code } return 0; // If none of the conditions are met, the filename is invalid } static char *MapNextLanguageFile (int resourceid) { wchar_t f[TC_MAX_PATH*2], *t; WIN32_FIND_DATAW find; HANDLE file; DWORD read; BOOL bStatus; BOOL validFileFound = FALSE; /* free memory here to avoid leaks */ if (LanguageFileBuffer != NULL) { free (LanguageFileBuffer); LanguageFileBuffer = NULL; } if (resourceid == 0) { if (LanguageFileFindHandle == INVALID_HANDLE_VALUE) { GetModuleFileNameW (NULL, f, sizeof (f) / sizeof (f[0])); t = wcsrchr (f, L'\\'); if (t == NULL) return NULL; *t = 0; StringCbCatW (f, sizeof(f), L"\\Languages\\Language*.xml"); LanguageFileFindHandle = FindFirstFileW (f, &find); } else if (!FindNextFileW (LanguageFileFindHandle, &find)) { FindClose (LanguageFileFindHandle); LanguageFileFindHandle = INVALID_HANDLE_VALUE; return NULL; } if (LanguageFileFindHandle == INVALID_HANDLE_VALUE) return NULL; if (find.nFileSizeHigh != 0) return NULL; // Validate the file name format while (!validFileFound) { if (!IsValidLanguageFileName(find.cFileName)) { if (!FindNextFileW(LanguageFileFindHandle, &find)) { FindClose(LanguageFileFindHandle); LanguageFileFindHandle = INVALID_HANDLE_VALUE; return NULL; } } else { validFileFound = TRUE; } } LanguageFileBuffer = malloc(find.nFileSizeLow + 1); if (LanguageFileBuffer == NULL) return NULL; GetModuleFileNameW (NULL, f, sizeof (f) / sizeof(f[0])); t = wcsrchr (f, L'\\'); if (t == NULL) { free(LanguageFileBuffer); LanguageFileBuffer = NULL; return NULL; } t[1] = 0; StringCbCatW (f, sizeof(f), L"Languages\\"); StringCbCatW (f, sizeof(f),find.cFileName); file = CreateFileW (f, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (file == INVALID_HANDLE_VALUE) { free(LanguageFileBuffer); LanguageFileBuffer = NULL; return NULL; } bStatus = ReadFile (file, LanguageFileBuffer, find.nFileSizeLow, &read, NULL); CloseHandle (file); if (!bStatus || (read != find.nFileSizeLow)) { free(LanguageFileBuffer); LanguageFileBuffer = NULL; return NULL; } LanguageFileBuffer [find.nFileSizeLow] = 0; // we have allocated (find.nFileSizeLow + 1) bytes } else if (LanguageResourceId != resourceid) { DWORD size; LanguageResourceId = resourceid; LanguageResource = MapResource (L"Languages", LanguageResourceId, &size); if (LanguageResource) LanguageResourceSize = size; if (LanguageResource) { LanguageFileBuffer = malloc(LanguageResourceSize + 1); if (LanguageFileBuffer) { memcpy (LanguageFileBuffer, LanguageResource, LanguageResourceSize); LanguageFileBuffer[LanguageResourceSize] = 0; } } } return LanguageFileBuffer; } static BOOL LoadLanguageData (int resourceid, BOOL bForceSetPreferredLanguage, BOOL bForceSilent) { DWORD size; BYTE *res; char *xml, *header, *headerPtr; char langId[6] = "en", attr[32768], key[128]; BOOL defaultLangParsed = FALSE, langFound = FALSE; WCHAR wattr[32768]; int i, intKey, len; char *xmlElements[] = {"entry", 0}; #ifdef TCMOUNT int headers[] = { IDR_COMMON_RSRC_HEADER, IDR_MOUNT_RSRC_HEADER, 0 }; #endif #ifdef VOLFORMAT int headers[] = { IDR_COMMON_RSRC_HEADER, IDR_FORMAT_RSRC_HEADER, 0 }; #endif #ifdef SETUP int headers[] = { IDR_COMMON_RSRC_HEADER, IDR_SETUP_RSRC_HEADER, 0 }; #endif LocalizationActive = FALSE; ActiveLangPackVersion[0] = 0; ClearDictionaryPool (); LanguageResource = NULL; if ((resourceid == 0) && (PreferredLangId[0] != 0)) StringCbCopyA (langId, sizeof(langId), PreferredLangId); // Parse all available language files until preferred language is found for (res = MapFirstLanguageFile (); res != NULL; res = MapNextLanguageFile (resourceid)) { xml = (char *) res; xml = XmlFindElement (xml, "localization"); if (!xml) continue; // Required TrueCrypt version XmlGetAttributeText (xml, "prog-version", attr, sizeof (attr)); // Check version of external language file if (defaultLangParsed && strcmp (attr, VERSION_STRING) && strcmp (attr, "DEBUG")) { wchar_t m[2048]; StringCbPrintfW (m, sizeof(m), L"The installed language pack is incompatible with this version of VeraCrypt (the language pack is for VeraCrypt %hs). A newer version may be available at www.idrix.fr.\n\nTo prevent this message from being displayed, do any of the following:\n\n- Select 'Settings' > 'Language'; then select 'English' and click 'OK'.\n\n- Remove or replace the language pack with a compatible version (the language pack may reside e.g. in 'C:\\Program Files\\VeraCrypt' or '%%LOCALAPPDATA%%\\VirtualStore\\Program Files\\VeraCrypt', etc.)", attr); if (!bForceSilent) MessageBoxW (NULL, m, L"VeraCrypt", MB_ICONERROR); continue; } // Search language id in language file if (defaultLangParsed) { while (xml = XmlFindElement (xml, "language")) { XmlGetAttributeText (xml, "langid", attr, sizeof (attr)); if (resourceid == 0) { if (strcmp (attr, langId) == 0) { XmlGetAttributeText (xml++, "version", ActiveLangPackVersion, sizeof (ActiveLangPackVersion)); langFound = TRUE; break; } xml++; } else { StringCbCopyA (langId, sizeof (langId), attr); XmlGetAttributeText (xml++, "version", ActiveLangPackVersion, sizeof (ActiveLangPackVersion)); langFound = TRUE; break; } } if (!langFound) continue; } // Create font dictionary xml = (char *) res; while (xml = XmlFindElement (xml, "font")) { XmlGetAttributeText (xml, "lang", attr, sizeof (attr)); if (!defaultLangParsed || strcmp (attr, langId) == 0) { Font font; memset (&font, 0, sizeof (font)); XmlGetAttributeText (xml, "face", attr, sizeof (attr)); len = MultiByteToWideChar (CP_UTF8, 0, attr, -1, wattr, sizeof (wattr) / sizeof(wattr[0])); font.FaceName = AddPoolData ((void *) wattr, len * 2); XmlGetAttributeText (xml, "size", attr, sizeof (attr)); sscanf (attr, "%d", &font.Size); StringCbCopyA (attr, sizeof(attr), "font_"); XmlGetAttributeText (xml, "class", attr + 5, sizeof (attr) - 5); AddDictionaryEntry ( AddPoolData ((void *) attr, strlen (attr) + 1), 0, AddPoolData ((void *) &font, sizeof(font))); } xml++; } // Create string and control dictionaries for (i = 0; xmlElements[i] != 0; i++) { xml = (char *) res; while (xml = XmlFindElement (xml, xmlElements[i])) { void *pkey; void *text; XmlGetAttributeText (xml, "lang", attr, sizeof (attr)); if (!defaultLangParsed || strcmp (attr, langId) == 0) { if (XmlGetAttributeText (xml, "key", attr, sizeof (attr))) { pkey = AddPoolData (attr, strlen (attr) + 1); if (pkey == NULL) return FALSE; XmlGetNodeText (xml, attr, sizeof (attr)); // Parse \ escape sequences { char *in = attr, *out = attr; while (*in) { if (*in == '\\') { in++; switch (*in++) { case '\\': *out++ = '\\'; break; case 't': *out++ = '\t'; break; case 'n': *out++ = 13; *out++ = 10; break; default: if (!bForceSilent) MessageBoxA (0, pkey, "VeraCrypt: Unknown '\\' escape sequence in string", MB_ICONERROR); return FALSE; } } else *out++ = *in++; } *out = 0; } // UTF8 => wide char len = MultiByteToWideChar (CP_UTF8, 0, attr, -1, wattr, sizeof (wattr) / sizeof(wattr[0])); if (len == 0) { if (!bForceSilent) MessageBoxA (0, pkey, "VeraCrypt: Error while decoding UTF-8 string", MB_ICONERROR); return FALSE; } // Add to dictionary text = AddPoolData ((void *) wattr, len * 2); if (text == NULL) return FALSE; AddDictionaryEntry ((char *)pkey, 0, text); } } xml++; } } if (langFound) break; if (!defaultLangParsed) { defaultLangParsed = TRUE; if ((resourceid == 0) && (langId[0] == 0 || strcmp (langId, "en") == 0)) break; } } LocalizationActive = langFound && strcmp (langId, "en") != 0; LocalizationSerialNo++; if (bForceSetPreferredLanguage) StringCbCopyA (PreferredLangId, sizeof (PreferredLangId), langId); // Create control ID dictionary // Default controls AddDictionaryEntry (NULL, 1, GetString ("IDOK")); AddDictionaryEntry (NULL, 2, GetString ("IDCANCEL")); AddDictionaryEntry (NULL, 8, GetString ("IDCLOSE")); AddDictionaryEntry (NULL, 9, GetString ("IDHELP")); for (i = 0; headers[i] != 0; i++) { if (HeaderResource[i] == NULL) { HeaderResource[i] = MapResource (L"Header", headers[i], &size); if (HeaderResource[i]) HeaderResourceSize[i] = size; } headerPtr = NULL; if (HeaderResource[i]) { headerPtr = (char*) malloc (HeaderResourceSize[i] + 1); if (headerPtr) { memcpy (headerPtr, HeaderResource[i], HeaderResourceSize[i]); headerPtr [HeaderResourceSize[i]] = 0; } } header = headerPtr; if (header == NULL) return FALSE; header--; do { header++; if (sscanf (header, "#define %127s %d", key, &intKey) == 2) { WCHAR *str = GetString (key); if (str != UnknownString) AddDictionaryEntry (NULL, intKey, str); } } while ((header = strchr (header, '\n')) != NULL); free (headerPtr); } return TRUE; } BOOL LoadLanguageFile () { return LoadLanguageData (0, FALSE, FALSE); } BOOL LoadLanguageFromResource (int resourceid, BOOL bSetPreferredLanguage, BOOL bForceSilent) { return LoadLanguageData (resourceid, bSetPreferredLanguage, bForceSilent); } // lParam = 1: auto mode BOOL CALLBACK LanguageDlgProc (HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { WORD lw = LOWORD (wParam); WORD hw = HIWORD (wParam); switch (msg) { case WM_INITDIALOG: { char *xml; char attr[2048], lastLangId[10]; WCHAR wattr[2048]; int len; int langCount = 0; BOOL defaultLangFound = FALSE; LocalizeDialog (hwndDlg, "IDD_LANGUAGE"); ToHyperlink (hwndDlg, IDC_GET_LANG_PACKS); for (xml = MapFirstLanguageFile (); xml != NULL; xml = MapNextLanguageFile (0)) { while (xml = XmlFindElement (xml, "language")) { XmlGetAttributeText (xml, "name", attr, sizeof (attr)); len = MultiByteToWideChar (CP_UTF8, 0, attr, -1, wattr, sizeof (wattr) / sizeof(wattr[0])); if (len != 0 && (!defaultLangFound || wcscmp (wattr, L"English") != 0)) { int i = (int) SendDlgItemMessageW (hwndDlg, IDC_LANGLIST, LB_ADDSTRING, 0, (LPARAM)wattr); if (i >= 0) { int id; // Encode language id in LPARAM XmlGetAttributeText (xml, "langid", attr, sizeof (attr)); switch (strlen (attr)) { case 2: id = attr[0] | attr[1] << 8; break; case 5: id = attr[0] | attr[1] << 8 | attr[3] << 16 | attr[4] << 24; break; default: continue; } if (!defaultLangFound) defaultLangFound = TRUE; SendDlgItemMessage (hwndDlg, IDC_LANGLIST, LB_SETITEMDATA, i, (LPARAM) id); if (strcmp (attr, PreferredLangId) == 0) { char credits [10000]; WCHAR wcredits [10000]; WCHAR wversion [20]; wchar_t szVers [200]; int nLen; SendDlgItemMessage (hwndDlg, IDC_LANGLIST, LB_SETCURSEL, i, 0); // Language pack version if (!ActiveLangPackVersion[0] || memcmp (ActiveLangPackVersion, "0.0.0", 5) == 0) { StringCbPrintfW (szVers, sizeof(szVers), GetString("LANG_PACK_VERSION"), L"--"); } else { nLen = MultiByteToWideChar (CP_UTF8, 0, ActiveLangPackVersion, -1, wversion, sizeof (wversion) / sizeof(wversion[0])); if (nLen != 0) StringCbPrintfW (szVers, sizeof(szVers),GetString("LANG_PACK_VERSION"), wversion); } SetWindowTextW (GetDlgItem (hwndDlg, IDC_LANGPACK_VERSION), szVers); // Translator credits XmlGetAttributeText (xml, "translators", credits, sizeof (credits)); nLen = MultiByteToWideChar (CP_UTF8, 0, credits, -1, wcredits, sizeof (wcredits) / sizeof(wcredits[0])); if (nLen != 0) { SetWindowTextW (GetDlgItem (hwndDlg, IDC_LANGPACK_CREDITS), wcredits); } } StringCbCopyA (lastLangId, sizeof(lastLangId),attr); langCount++; } } xml++; } } if (lParam == 1) { // Auto mode if (langCount < 2) EndDialog (hwndDlg, IDCANCEL); if (langCount == 2) StringCbCopyA (PreferredLangId, sizeof(PreferredLangId), lastLangId); EndDialog (hwndDlg, IDOK); } return 1; } case WM_COMMAND: if (lw == IDOK || hw == LBN_DBLCLK) { int i = (int) SendDlgItemMessage (hwndDlg, IDC_LANGLIST, LB_GETCURSEL, 0, 0); if (i >= 0) { int id = (int) SendDlgItemMessage (hwndDlg, IDC_LANGLIST, LB_GETITEMDATA, i, 0); if (id != LB_ERR) { char l[6]; // Decode language id from LPARAM l[0] = (char) id; l[1] = (char) (id >> 8); l[2] = 0; if ((id & 0xffff0000) != 0) { l[2] = '-'; l[3] = (char) (id >> 16); l[4] = id >> 24; l[5] = 0; } if (SendDlgItemMessage (hwndDlg, IDC_LANGLIST, LB_GETCOUNT, 0, 0) > 1) StringCbCopyA (PreferredLangId, sizeof(PreferredLangId), l); } } EndDialog (hwndDlg, IDOK); return 1; } if (lw == IDCANCEL) { EndDialog (hwndDlg, lw); return 1; } if (lw == IDC_GET_LANG_PACKS) { char tmpstr [256]; if (strlen (ActiveLangPackVersion) > 0 && strlen (GetPreferredLangId()) > 0) StringCbPrintfA (tmpstr, sizeof(tmpstr), "&langpackversion=%s&lang=%s", ActiveLangPackVersion, GetPreferredLangId()); else tmpstr[0] = 0; Applink ("localizations"); return 1; } return 0; } return 0; } char *GetPreferredLangId () { return PreferredLangId; } void SetPreferredLangId (char *langId) { if (strlen(langId) < sizeof(PreferredLangId)) StringCbCopyA (PreferredLangId, sizeof(PreferredLangId), langId); } char *GetActiveLangPackVersion () { return ActiveLangPackVersion; } wchar_t *GetString (const char *stringId) { WCHAR *str = (WCHAR *) GetDictionaryValue (stringId); if (str != NULL) return str; StringCbPrintfW (UnknownString, sizeof(UnknownString), UNKNOWN_STRING_ID L"%hs" UNKNOWN_STRING_ID, stringId); return UnknownString; } Font *GetFont (char *fontType) { return (Font *) GetDictionaryValue (fontType); }