/* * lang.cpp * * This file is a part of NSIS. * * Copyright (C) 1999-2019 Nullsoft and Contributors * * Licensed under the zlib/libpng license (the "License"); * you may not use this file except in compliance with the License. * * Licence details can be found in the file COPYING. * * This software is provided 'as-is', without any express or implied * warranty. * * Unicode support and Doxygen comments by Jim Park -- 07/25/2007 */ #include "Platform.h" #include #include #include "tchar.h" #include "build.h" #include "util.h" #include "DialogTemplate.h" #include "exehead/resource.h" #include #include "tstring.h" #include "utf.h" using namespace std; // Default English strings. Should match NSIS_DEFAULT_LANG // Do not change the first string in every item, it's the LangString // name for usage in scripts. typedef enum { NONE_STATIC = 0, INSTALL_STATIC = 1, UNINSTALL_STATIC = 2, BOTH_STATIC = 3 } STATICID; struct NLFString { const TCHAR *szLangStringName; const TCHAR *szDefault; STATICID eStaticID; }; NLFString NLFStrings[NLF_STRINGS] = { {_T("^Branding"), _T("Nullsoft Install System %s"), BOTH_STATIC}, {_T("^SetupCaption"), _T("$(^Name) Setup"), INSTALL_STATIC}, {_T("^UninstallCaption"), _T("$(^Name) Uninstall"), UNINSTALL_STATIC}, {_T("^LicenseSubCaption"), _T(": License Agreement"), NONE_STATIC}, {_T("^ComponentsSubCaption"), _T(": Installation Options"), NONE_STATIC}, {_T("^DirSubCaption"), _T(": Installation Folder"), NONE_STATIC}, {_T("^InstallingSubCaption"), _T(": Installing"), NONE_STATIC}, {_T("^CompletedSubCaption"), _T(": Completed"), NONE_STATIC}, {_T("^UnComponentsSubCaption"), _T(": Uninstallation Options"), NONE_STATIC}, {_T("^UnDirSubCaption"), _T(": Uninstallation Folder"), NONE_STATIC}, {_T("^ConfirmSubCaption"), _T(": Confirmation"), NONE_STATIC}, {_T("^UninstallingSubCaption"), _T(": Uninstalling"), NONE_STATIC}, {_T("^UnCompletedSubCaption"), _T(": Completed"), NONE_STATIC}, {_T("^BackBtn"), _T("< &Back"), NONE_STATIC}, {_T("^NextBtn"), _T("&Next >"), NONE_STATIC}, {_T("^AgreeBtn"), _T("I &Agree"), NONE_STATIC}, {_T("^AcceptBtn"), _T("I &accept the terms of the License Agreement"), NONE_STATIC}, {_T("^DontAcceptBtn"), _T("I &do not accept the terms of the License Agreement"), NONE_STATIC}, {_T("^InstallBtn"), _T("&Install"), NONE_STATIC}, {_T("^UninstallBtn"), _T("&Uninstall"), NONE_STATIC}, {_T("^CancelBtn"), _T("Cancel"), NONE_STATIC}, {_T("^CloseBtn"), _T("&Close"), NONE_STATIC}, {_T("^BrowseBtn"), _T("B&rowse..."), NONE_STATIC}, {_T("^ShowDetailsBtn"), _T("Show &details"), NONE_STATIC}, {_T("^ClickNext"), _T("Click Next to continue."), NONE_STATIC}, {_T("^ClickInstall"), _T("Click Install to start the installation."), NONE_STATIC}, {_T("^ClickUninstall"), _T("Click Uninstall to start the uninstallation."), NONE_STATIC}, {_T("^Name"), _T("Name"), BOTH_STATIC}, {_T("^NameDA"), 0, NONE_STATIC}, // virtual {_T("^Completed"), _T("Completed"), NONE_STATIC}, {_T("^LicenseText"), _T("Please review the license agreement before installing $(^NameDA). If you accept all terms of the agreement, click I Agree."), NONE_STATIC}, {_T("^LicenseTextCB"), _T("Please review the license agreement before installing $(^NameDA). If you accept all terms of the agreement, click the check box below. $_CLICK"), NONE_STATIC}, {_T("^LicenseTextRB"), _T("Please review the license agreement before installing $(^NameDA). If you accept all terms of the agreement, select the first option below. $_CLICK"), NONE_STATIC}, {_T("^UnLicenseText"), _T("Please review the license agreement before uninstalling $(^NameDA). If you accept all terms of the agreement, click I Agree."), NONE_STATIC}, {_T("^UnLicenseTextCB"), _T("Please review the license agreement before uninstalling $(^NameDA). If you accept all terms of the agreement, click the check box below. $_CLICK"), NONE_STATIC}, {_T("^UnLicenseTextRB"), _T("Please review the license agreement before uninstalling $(^NameDA). If you accept all terms of the agreement, select the first option below. $_CLICK"), NONE_STATIC}, {_T("^LicenseData"), 0, NONE_STATIC}, // virtual - not processed {_T("^Custom"), _T("Custom"), NONE_STATIC}, {_T("^ComponentsText"), _T("Check the components you want to install and uncheck the components you don't want to install. $_CLICK"), NONE_STATIC}, {_T("^ComponentsSubText1"), _T("Select the type of install:"), NONE_STATIC}, {_T("^ComponentsSubText2_NoInstTypes"), _T("Select components to install:"), NONE_STATIC}, {_T("^ComponentsSubText2"), _T("Or, select the optional components you wish to install:"), NONE_STATIC}, {_T("^UnComponentsText"), _T("Check the components you want to uninstall and uncheck the components you don't want to uninstall. $_CLICK"), NONE_STATIC}, {_T("^UnComponentsSubText1"), _T("Select the type of uninstall:"), NONE_STATIC}, {_T("^UnComponentsSubText2_NoInstTypes"), _T("Select components to uninstall:"), NONE_STATIC}, {_T("^UnComponentsSubText2"), _T("Or, select the optional components you wish to uninstall:"), NONE_STATIC}, {_T("^DirText"), _T("Setup will install $(^NameDA) in the following folder. To install in a different folder, click Browse and select another folder. $_CLICK"), NONE_STATIC}, {_T("^DirSubText"), _T("Destination Folder"), NONE_STATIC}, {_T("^DirBrowseText"), _T("Select the folder to install $(^NameDA) in:"), NONE_STATIC}, {_T("^UnDirText"), _T("Setup will uninstall $(^NameDA) from the following folder. To uninstall from a different folder, click Browse and select another folder. $_CLICK"), NONE_STATIC}, {_T("^UnDirSubText"), _T(""), NONE_STATIC}, {_T("^UnDirBrowseText"), _T("Select the folder to uninstall $(^NameDA) from:"), NONE_STATIC}, {_T("^SpaceAvailable"), _T("Space available: "), BOTH_STATIC}, {_T("^SpaceRequired"), _T("Space required: "), BOTH_STATIC}, {_T("^UninstallingText"), _T("This wizard will uninstall $(^NameDA) from your computer. $_CLICK"), NONE_STATIC}, {_T("^UninstallingSubText"), _T("Uninstalling from:"), NONE_STATIC}, {_T("^FileError"), _T("Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Abort to stop the installation,\r\nRetry to try again, or\r\nIgnore to skip this file."), NONE_STATIC}, {_T("^FileError_NoIgnore"), _T("Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Retry to try again, or\r\nCancel to stop the installation."), NONE_STATIC}, {_T("^CantWrite"), _T("Can't write: "), BOTH_STATIC}, {_T("^CopyFailed"), _T("Copy failed"), BOTH_STATIC}, {_T("^CopyTo"), _T("Copy to "), BOTH_STATIC}, {_T("^Registering"), _T("Registering: "), NONE_STATIC}, {_T("^Unregistering"), _T("Unregistering: "), NONE_STATIC}, {_T("^SymbolNotFound"), _T("Could not find symbol: "), BOTH_STATIC}, {_T("^CouldNotLoad"), _T("Could not load: "), BOTH_STATIC}, {_T("^CreateFolder"), _T("Create folder: "), BOTH_STATIC}, {_T("^CreateShortcut"), _T("Create shortcut: "), BOTH_STATIC}, {_T("^CreatedUninstaller"), _T("Created uninstaller: "), BOTH_STATIC}, {_T("^Delete"), _T("Delete file: "), BOTH_STATIC}, {_T("^DeleteOnReboot"), _T("Delete on reboot: "), BOTH_STATIC}, {_T("^ErrorCreatingShortcut"), _T("Error creating shortcut: "), BOTH_STATIC}, {_T("^ErrorCreating"), _T("Error creating: "), BOTH_STATIC}, {_T("^ErrorDecompressing"), _T("Error decompressing data! Corrupted installer?"), BOTH_STATIC}, {_T("^ErrorRegistering"), _T("Error registering DLL"), BOTH_STATIC}, {_T("^ExecShell"), _T("ExecShell: "), BOTH_STATIC}, {_T("^Exec"), _T("Execute: "), BOTH_STATIC}, {_T("^Extract"), _T("Extract: "), BOTH_STATIC}, {_T("^ErrorWriting"), _T("Extract: error writing to file "), BOTH_STATIC}, {_T("^InvalidOpcode"), _T("Installer corrupted: invalid opcode"), BOTH_STATIC}, {_T("^NoOLE"), _T("No OLE for: "), BOTH_STATIC}, {_T("^OutputFolder"), _T("Output folder: "), BOTH_STATIC}, {_T("^RemoveFolder"), _T("Remove folder: "), BOTH_STATIC}, {_T("^RenameOnReboot"), _T("Rename on reboot: "), BOTH_STATIC}, {_T("^Rename"), _T("Rename: "), BOTH_STATIC}, {_T("^Skipped"), _T("Skipped: "), BOTH_STATIC}, {_T("^CopyDetails"), _T("Copy Details To Clipboard"), BOTH_STATIC}, {_T("^LogInstall"), _T("Log install process"), BOTH_STATIC}, {_T("^Byte"), _T("B"), BOTH_STATIC}, {_T("^Kilo"), _T(" K"), BOTH_STATIC}, {_T("^Mega"), _T(" M"), BOTH_STATIC}, {_T("^Giga"), _T(" G"), BOTH_STATIC}, {_T("^Font"), _T("MS Shell Dlg"), NONE_STATIC}, {_T("^FontSize"), _T("8"), NONE_STATIC}, {_T("^RTL"), _T("0"), NONE_STATIC}, {_T("^Language"), _T("English"), NONE_STATIC} }; // ============== // LangStringList // ============== int LangStringList::add(const TCHAR *name, int *sn/*=0*/) { int pos = SortedStringListND::add(name); if (pos == -1) return -1; langstring* lstrPtr = (langstring*)(m_gr.get()) + pos; lstrPtr->sn = m_count; if (sn) *sn = m_count; m_count++; lstrPtr->index = -1; lstrPtr->uindex = -1; lstrPtr->process = 1; return pos; } int LangStringList::get(const TCHAR *name, int *sn/*=0*/, int *index/*=0*/, int *uindex/*=0*/, int *process/*=0*/) { if (index) *index = -1; if (uindex) *uindex = -1; if (sn) *sn = -1; int v=find(name); if (v==-1) return -1; langstring* lstrPtr = (langstring*)(m_gr.get()) + v; if (index) *index = lstrPtr->index; if (uindex) *uindex = lstrPtr->uindex; if (sn) *sn = lstrPtr->sn; if (process) *process = lstrPtr->process; return v; } void LangStringList::set(int pos, int index/*=-1*/, int uindex/*=-1*/, int process/*=-1*/) { if ((unsigned int)pos > (m_gr.getlen() / sizeof(struct langstring))) return; struct langstring *data=((struct langstring *) m_gr.get()) + pos; if (index >= 0) data->index = index; if (uindex >= 0) data->uindex = uindex; if (process >= 0) data->process = process; } void LangStringList::set(const TCHAR *name, int index, int uindex/*=-1*/, int process/*=-1*/) { set(find(name), index, uindex, process); } const TCHAR* LangStringList::pos2name(int pos) { struct langstring *data=(struct langstring *) m_gr.get(); if (pos < 0 || (unsigned int)pos > (m_gr.getlen() / sizeof(struct langstring))) return 0; return ((const TCHAR*) m_strings.get() + data[pos].name); } const TCHAR* LangStringList::offset2name(int name) { if (name < 0 || (unsigned int)name > m_strings.getlen()/sizeof(TCHAR)) return 0; return (const TCHAR*) m_strings.get() + name; } int LangStringList::getnum() { return m_gr.getlen() / sizeof(struct langstring); } int LangStringList::compare_index(const void *item1, const void *item2) { struct langstring *ls1 = (struct langstring *)item1; struct langstring *ls2 = (struct langstring *)item2; return ls1->index - ls2->index; } langstring* LangStringList::sort_index(int *num) { if (!num) return 0; m_sortbuf.resize(0); m_sortbuf.add(m_gr.get(), m_gr.getlen()); *num = m_sortbuf.getlen() / sizeof(struct langstring); qsort(m_sortbuf.get(), *num, sizeof(struct langstring), compare_index); return (struct langstring*) m_sortbuf.get(); } int LangStringList::compare_uindex(const void *item1, const void *item2) { struct langstring *ls1 = (struct langstring *)item1; struct langstring *ls2 = (struct langstring *)item2; return ls1->uindex - ls2->uindex; } langstring* LangStringList::sort_uindex(int *num) { if (!num) return 0; m_sortbuf.resize(0); m_sortbuf.add(m_gr.get(), m_gr.getlen()); *num = m_sortbuf.getlen() / sizeof(struct langstring); qsort(m_sortbuf.get(), *num, sizeof(struct langstring), compare_uindex); return (struct langstring*) m_sortbuf.get(); } // ============ // StringsArray // ============ StringsArray::StringsArray() { // We make zero an invalid index. Using 0 will get back an empty string. m_offsets.set_zeroing(1); m_strings.add(_T(""), sizeof(_T(""))); } void StringsArray::resize(int num) { m_offsets.resize(num * sizeof(int)); } int StringsArray::set(int idx, const TCHAR *str) { if (idx < 0) return 0; if (idx >= (int)(m_offsets.getlen() / sizeof(int))) resize(idx+1); int old = ((int*) m_offsets.get())[idx]; // Need to store the TCHAR index so we divide the return value of add by sizeof(TCHAR). ((int*)m_offsets.get())[idx] = m_strings.add(str, (DWORD)(_tcslen(str)+1)*sizeof(TCHAR))/sizeof(TCHAR); return old; } const TCHAR* StringsArray::get(int idx) { if (idx < 0 || (unsigned int)idx >= (m_offsets.getlen() / sizeof(int))) return 0; return (const TCHAR *) m_strings.get() + ((int*) m_offsets.get())[idx]; } // ========= // CEXEBuild // ========= void CEXEBuild::InitLangTables() { keep_ref = false; for (int i = 0; i < NLF_STRINGS; i++) { NLFRefs[i].iRef = 0; NLFRefs[i].iUnRef = 0; #ifdef NSIS_CONFIG_LOG if (i == NLF_NAME) { NLFRefs[i].iRef++; NLFRefs[i].iUnRef++; } #endif if (NLFStrings[i].eStaticID & INSTALL_STATIC) { set_uninstall_mode(0); DefineLangString(NLFStrings[i].szLangStringName); } #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT if (NLFStrings[i].eStaticID & UNINSTALL_STATIC) { set_uninstall_mode(1); DefineLangString(NLFStrings[i].szLangStringName); } #endif } set_uninstall_mode(0); keep_ref = true; } ////////////////////////////////////////////////////////////////////////////// // class CEXEBuild // // Note: The functions below refer to the methods related to Languages. ////////////////////////////////////////////////////////////////////////////// LanguageTable* CEXEBuild::GetLangTable(LANGID &lang, bool create/*=true*/) { int nlt = lang_tables.getlen() / sizeof(LanguageTable); LanguageTable *nla = (LanguageTable*)lang_tables.get(); lang = lang ? lang : last_used_lang; LanguageTable *table = NULL; for (int i = 0; i < nlt; i++) { if (lang == nla[i].lang_id) { table = &nla[i]; break; } } if (!table && create) { LanguageTable newtable; newtable.lang_id = lang; newtable.dlg_offset = 0; memset(&newtable.nlf, 0, sizeof(NLF)); newtable.lang_strings = new StringsArray; lang_tables.add(&newtable, sizeof(LanguageTable)); table = (LanguageTable*)lang_tables.get() + nlt; } if (table) // update last used language if a table was loaded last_used_lang = lang; return table; } const TCHAR *CEXEBuild::GetLangNameAndCP(LANGID lang, unsigned int *codepage/*=NULL*/) { LanguageTable *table = GetLangTable(lang, false); if (table && table->nlf.m_bLoaded) { if (codepage) *codepage = table->nlf.m_uCodePage; return table->nlf.m_szName; } else { // If the language table does not exist, then we default to Unicode or ANSI // depending on the target installer type if (codepage) *codepage = build_unicode ? 1200 : 1252; // Unicode or CP1252 if (lang == 1033) return _T("English"); else return _T("???"); } } const TCHAR *CEXEBuild::GetLangNameAndCPForVersionResource(LANGID &lang, unsigned int *codepage/*=NULL*/, bool deflangfallback/*=true*/) { const TCHAR *langname = GetLangNameAndCP(lang, codepage); if (0 == lang) { if (deflangfallback) lang = last_used_lang; else langname = _T("Neutral"); } return langname; } int CEXEBuild::DefineLangString(const TCHAR *name, int process/*=-1*/) { int index, uindex, pos, ret, sn; /* If not exist, index and uindex will get -1. */ pos = build_langstrings.get(name, &sn, &index, &uindex); if (pos < 0) { pos = build_langstrings.add(name); } #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT if (!this->uninstall_mode) { #endif if (index < 0) { // Did not exist. Increment. index = this->build_langstring_num++; } ret = -index - 1; #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT } else { if (uindex < 0) { // Did not exist. Increment. uindex = this->ubuild_langstring_num++; } ret = -uindex - 1; } #endif // Now set the new index and uindex values with the // passed in process value. build_langstrings.set(pos, index, uindex, process); // set reference count for NLF strings if (this->keep_ref && name[0] == _T('^')) { for (int i = 0; i < NLF_STRINGS; i++) { if (!_tcscmp(name, NLFStrings[i].szLangStringName)) { #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT if (this->uninstall_mode) this->NLFRefs[i].iUnRef++; else #endif this->NLFRefs[i].iRef++; break; } } } return ret; } int CEXEBuild::DefineInnerLangString(int id, int process/*=-1*/) { bool old_keep_ref = keep_ref; // set reference count for NLF strings if (keep_ref) { #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT if (uninstall_mode) NLFRefs[id].iUnRef++; else #endif NLFRefs[id].iRef++; keep_ref = false; } int ret = DefineLangString(NLFStrings[id].szLangStringName, process); keep_ref = old_keep_ref; return ret; } // A LangString is a string variable that varies in value depending on what // language is being used. This function sets the string value for the // variable 'name' for a given language ID. // // @return If the language id, the variable name or string is invalid, it will // return a PS_ERROR. If this function call is overwriting a set user string, // this will return a PS_WARNING. int CEXEBuild::SetLangString(const TCHAR *name, LANGID lang, const TCHAR *str, BOOL LicenseData) { if (!str || !name) return PS_ERROR; LanguageTable *table = GetLangTable(lang); if (!table) return PS_ERROR; int sn; if (!LicenseData && _tcsclen(str) > NSIS_MAX_STRLEN-1) warning_fl(DW_LANGSTRING_OVERLONGLENGTH, _T("LangString \"%") NPRIs _T("\" longer than NSIS_MAX_STRLEN!"), name); int pos = build_langstrings.get(name, &sn); if (pos < 0) pos = build_langstrings.add(name, &sn); if (table->lang_strings->set(sn, str)) return PS_WARNING; return PS_OK; } int CEXEBuild::SetLangString(const TCHAR *name, LANGID lang, const TCHAR *str) { return SetLangString(name, lang, str, false); } // Sets the user string to the specific NLF_STRINGS id. // // @return If the id is invalid or the string is not valid, it will return a // PS_ERROR. If this function call is overwriting a set user string, this // will return a PS_WARNING. int CEXEBuild::SetInnerString(int id, const TCHAR *str) { if ((unsigned int)id >= NLF_STRINGS || !str) return PS_ERROR; int ret = PS_OK; const TCHAR *ps = UserInnerStrings.get(id); if (ps && *ps) ret = PS_WARNING; UserInnerStrings.set(id, str); return ret; } int CEXEBuild::GenerateLangTable(LanguageTable *lt, int num_lang_tables) { // Add all installer language strings int i, j, l, tabsset; struct langstring* lang_strings = NULL; TinyGrowBuf *string_ptrs = new TinyGrowBuf[num_lang_tables]; tabsset = 1; while (tabsset) { tabsset = 0; for (i = num_lang_tables; i--; ) { // Fill in default values for all used language strings that we can FillLanguageTable(<[i]); // Make sure the string lists are large enough string_ptrs[i].set_zeroing(1); if (!uninstall_mode) string_ptrs[i].resize(build_langstring_num * sizeof(int)); else string_ptrs[i].resize(ubuild_langstring_num * sizeof(int)); } // For all current language strings if (!uninstall_mode) lang_strings = build_langstrings.sort_index(&l); else lang_strings = build_langstrings.sort_uindex(&l); for (j = 0; j < l; j++) { int lang_string_index; if (!uninstall_mode) lang_string_index = lang_strings[j].index; else lang_string_index = lang_strings[j].uindex; // Is this language string used (in the installer)? if (lang_string_index >= 0) { // For each language for (i = num_lang_tables; i--; ) { // Get the current string pointer int *ptr = (int *)string_ptrs[i].get() + lang_string_index; // Not already set? if (!*ptr) { // Get the language string and its name const TCHAR *str = lt[i].lang_strings->get(lang_strings[j].sn); const TCHAR *lsn = build_langstrings.offset2name(lang_strings[j].name); // lsn = variable name, str = value if (!str || !*str) { // No string is defined; give a warning (for user strings only) if (lsn[0] != _T('^')) { if (lt[i].nlf.m_bLoaded) warning(DW_LANGSTRING_NOTSETINLANG, _T("LangString \"%") NPRIs _T("\" is not set in language table of language %") NPRIs, lsn, lt[i].nlf.m_szName); else warning(DW_LANGSTRING_NOTSETINLANG, _T("LangString \"%") NPRIs _T("\" is not set in language table of language %d"), lsn, lt[i].lang_id); } } else { // Add the language string to the string data block TCHAR fn[1024]; _stprintf(fn, _T("LangString %") NPRIs, lsn); curfilename = fn; linecnt = lt[i].lang_id; *ptr = add_string(str, lang_strings[j].process, (WORD) lt[i].nlf.m_uCodePage); curfilename = 0; // Indicate that we should check again for any newly referenced language strings tabsset++; } } } } } } // Optimize langstrings and check for recursion for (i = num_lang_tables; i--; ) { TinyGrowBuf rec; int *lst = (int *)string_ptrs[i].get(); int langstring_num = uninstall_mode ? ubuild_langstring_num : build_langstring_num; for (j = 0; j < langstring_num; j++) { // Does this string reference another language string directly? while (lst[j] < 0) { // Search through list of language string references for (l = 0; (unsigned int)l < rec.getlen() / sizeof(int); l++) { if (((int*)rec.get())[l] == lst[j]) { // We have the index of a recursive language string; now find the name const TCHAR *name = _T("(unnamed)"); for (l = 0; l < langstring_num; l++) { int recidx = uninstall_mode ? lang_strings[l].uindex : lang_strings[l].index; if (recidx == j) { name = build_langstrings.offset2name(lang_strings[l].name); } } ERROR_MSG(_T("Error: LangString %") NPRIs _T(" in language %u is recursive!\n"), name, lt->lang_id); delete [] string_ptrs; return PS_ERROR; } } // Add this reference to the list rec.add(&lst[j], sizeof(int)); // and dereference it lst[j] = lst[-lst[j] - 1]; } rec.resize(0); } } // Add language tables into their datablock for (i = num_lang_tables; i--; ) { cur_langtables->add(<[i].lang_id, sizeof(LANGID)); cur_langtables->add(<[i].dlg_offset, sizeof(int)); int rtl = lt[i].nlf.m_bRTL ? 1 : 0; cur_langtables->add(&rtl, sizeof(int)); cur_langtables->add(string_ptrs[i].get(), string_ptrs[i].getlen()); string_ptrs[i].resize(0); } cur_header->blocks[NB_LANGTABLES].num = num_lang_tables; cur_header->langtable_size = cur_langtables->getlen() / num_lang_tables; delete [] string_ptrs; return PS_OK; } int CEXEBuild::GenerateLangTables() { int i; LanguageTable *lt = (LanguageTable*)lang_tables.get(); SCRIPT_MSG(_T("Generating language tables... ")); if ( #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT ubuild_langstring_num > MAX_CODED || #endif build_langstring_num > MAX_CODED ) { ERROR_MSG(_T("\nError: too many LangStrings. Maximum allowed is %u.\n"), MAX_CODED); return PS_ERROR; } // If we have no tables (user didn't set any string and didn't load any NLF) create the default one if (!lang_tables.getlen()) { LANGID lang = NSIS_DEFAULT_LANG; LanguageTable *table = GetLangTable(lang); if (!table) return PS_ERROR; lt = (LanguageTable*)lang_tables.get(); } // Apply default font if (*build_font) { try { init_res_editor(); #define ADD_FONT(id) { \ BYTE* dlg = res_editor->GetResource(RT_DIALOG, id, NSIS_DEFAULT_LANG); \ if (dlg) { \ CDialogTemplate td(dlg,build_unicode); \ res_editor->FreeResource(dlg); \ td.SetFont(build_font, (WORD) build_font_size); \ DWORD dwSize; \ dlg = td.Save(dwSize); \ res_editor->UpdateResource(RT_DIALOG, id, NSIS_DEFAULT_LANG, dlg, dwSize); \ td.FreeSavedTemplate(dlg); \ } \ } #ifdef NSIS_CONFIG_LICENSEPAGE ADD_FONT(IDD_LICENSE); ADD_FONT(IDD_LICENSE_FSRB); ADD_FONT(IDD_LICENSE_FSCB); #endif ADD_FONT(IDD_DIR); #ifdef NSIS_CONFIG_COMPONENTPAGE ADD_FONT(IDD_SELCOM); #endif ADD_FONT(IDD_INST); ADD_FONT(IDD_INSTFILES); #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT ADD_FONT(IDD_UNINST); #endif #ifdef NSIS_CONFIG_CRC_SUPPORT ADD_FONT(IDD_VERIFY); #endif #undef ADD_FONT } catch (exception& err) { ERROR_MSG(_T("\nError while applying font: %") NPRIs _T("\n"), CtoTStrParam(err.what())); return PS_ERROR; } } // Fill tables with defaults (if needed) and with instruction strings // Create language specific resources (currently only dialogs with different fonts) int num_lang_tables = lang_tables.getlen() / sizeof(LanguageTable); // if there is one string table then there is no need for two sets of dialogs int cur_offset = num_lang_tables == 1 ? 0 : 100; for (i = 0; i < num_lang_tables; i++) { // A Unicode-only language is never displayed correctly by ANSI exehead if (!build_unicode && 1200 == lt[i].nlf.m_uCodePage) { ERROR_MSG(_T("\nError: Unicode-only language %") NPRIs _T(" cannot be used in ANSI installer!\n"), lt[i].nlf.m_szName); return PS_ERROR; } if ((lt[i].nlf.m_szFont && !*build_font) || lt[i].nlf.m_bRTL) { lt[i].dlg_offset = cur_offset; TCHAR *font = lt[i].nlf.m_szFont; if (*build_font) font = 0; try { init_res_editor(); #define ADD_FONT(id) { \ BYTE* dlg = res_editor->GetResource(RT_DIALOG, id, NSIS_DEFAULT_LANG); \ if (dlg) { \ CDialogTemplate td(dlg,build_unicode,lt[i].nlf.m_uCodePage); \ res_editor->FreeResource(dlg); \ if (font) td.SetFont(font, (WORD) lt[i].nlf.m_iFontSize); \ if (lt[i].nlf.m_bRTL) { \ td.ConvertToRTL(); \ DialogItemTemplate* dir = td.GetItem(IDC_DIR); \ if (id == IDD_DIR && dir) { \ if ((dir->dwStyle & ES_CENTER) == 0) dir->dwStyle ^= ES_RIGHT; \ dir->dwExtStyle &= ~(WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR); \ } \ } \ DWORD dwSize; \ dlg = td.Save(dwSize); \ res_editor->UpdateResource(RT_DIALOG, id+cur_offset, NSIS_DEFAULT_LANG, dlg, dwSize); \ td.FreeSavedTemplate(dlg); \ } \ } #ifdef NSIS_CONFIG_LICENSEPAGE ADD_FONT(IDD_LICENSE); ADD_FONT(IDD_LICENSE_FSRB); ADD_FONT(IDD_LICENSE_FSCB); #endif ADD_FONT(IDD_DIR); #ifdef NSIS_CONFIG_COMPONENTPAGE ADD_FONT(IDD_SELCOM); #endif ADD_FONT(IDD_INST); ADD_FONT(IDD_INSTFILES); #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT ADD_FONT(IDD_UNINST); #endif #ifdef NSIS_CONFIG_CRC_SUPPORT ADD_FONT(IDD_VERIFY); #endif #undef ADD_FONT } catch (exception& err) { ERROR_MSG(_T("\nError while applying NLF font/RTL: %") NPRIs _T("\n"), CtoTStrParam(err.what())); return PS_ERROR; } cur_offset += 100; } } const int orig_uninstall_mode = uninstall_mode; set_uninstall_mode(0); if (GenerateLangTable(lt, num_lang_tables) != PS_OK) return PS_ERROR; #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT set_uninstall_mode(1); if (GenerateLangTable(lt, num_lang_tables) != PS_OK) return PS_ERROR; #endif set_uninstall_mode(orig_uninstall_mode); SCRIPT_MSG(_T("Done!\n")); return PS_OK; } static void CreatePlatformStrfmt(const TCHAR *templ, TCHAR *out) { // NOTE: Only supports plain %s with no options for ( ;; out++ ) { *out = *templ++; if (!*out) break; if (*out == L'%' && *templ == L's') { out++, templ++; size_t cch = my_strncpy(out, NPRIs, -1); out += --cch; // --cch because for loop does out++ } } } void CEXEBuild::FillLanguageTable(LanguageTable *table) { for (int i = 0; i < NLF_STRINGS; i++) { #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT if (!NLFRefs[i].iUnRef && !NLFRefs[i].iRef) continue; #else if (!NLFRefs[i].iRef) continue; #endif else if (i == NLF_SPACE_REQ || i == NLF_SPACE_AVAIL) { if (no_space_texts) { continue; } } int sn, index; int pos = build_langstrings.get(NLFStrings[i].szLangStringName, &sn, &index); if (pos >= 0) { const TCHAR *str = table->lang_strings->get(sn); if (!str || !*str) { const TCHAR *us = UserInnerStrings.get(i); if (i == NLF_NAME_DA && (!us || !*us)) { // if the user didn't set NLF_NAME_DA we set it to $(^Name) table->lang_strings->set(sn, _T("$(^Name)")); } if (us && *us) { table->lang_strings->set(sn, (TCHAR *) us); } else { const TCHAR *dstr = table->nlf.m_szStrings[i] ? table->nlf.m_szStrings[i] : NLFStrings[i].szDefault; if (!dstr) continue; if (i == NLF_BRANDING) { TCHAR temp[NSIS_MAX_STRLEN + sizeof(NSIS_VERSION)], temp2[COUNTOF(temp)]; CreatePlatformStrfmt(dstr, temp2); // Change %s to %ls if required _stprintf(temp, temp2, NSIS_VERSION); table->lang_strings->set(sn, temp); continue; } else if (i == NLF_FONT) { TCHAR *font = *build_font ? build_font : table->nlf.m_szFont; if (font) table->lang_strings->set(sn, font); else table->lang_strings->set(sn, dstr); continue; } else if (i == NLF_FONTSIZE) { WORD font_size = *build_font ? (WORD) build_font_size : (WORD) table->nlf.m_iFontSize; if (font_size) { TCHAR temp[64]; _stprintf(temp, _T("%d"), font_size); table->lang_strings->set(sn, temp); } else table->lang_strings->set(sn, dstr); continue; } table->lang_strings->set(sn, dstr); } } } } } void TrimTrailingNewlines(TCHAR *s) { size_t cch = _tcslen(s); while (s[cch-1] == _T('\n') || s[cch-1] == _T('\r')) s[cch-1] = 0, --cch; } static UINT GetNextNLFLine(NStreamLineReader&lr, TCHAR*Buf, UINT cchBuf) { for (;;) { UINT err = lr.ReadLine(Buf, cchBuf); if (NStream::OK != err) { if (lr.IsEOF()) err = NStream::OK; return err; } if (NStream::IsNewline(*Buf, false)) continue; if (_T('#') != *Buf && _T(';') != *Buf) return err; } } static inline bool GetNextNLFLine(NStreamLineReader&lr, TCHAR*Buf, UINT cchBuf, UINT&errlr) { errlr = GetNextNLFLine(lr, Buf, cchBuf); return NStream::OK == errlr; } // NSIS Language File parser LanguageTable * CEXEBuild::LoadLangFile(TCHAR *filename) { NIStream strm; strm.StreamEncoding().SetCodepage(NStreamEncoding::ACP); if (!strm.OpenFileForReading(filename)) { ERROR_MSG(_T("Error: Can't open language file - \"%") NPRIs _T("\"!\n"),filename); return 0; } NStreamLineReader lr(strm); UINT errlr; // Check header TCHAR buf[NSIS_MAX_STRLEN]; if (!GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN, errlr)) { l_readerr: ERROR_MSG(lr.GetErrorMessage(errlr).c_str()); return 0; } if (_tcsncmp(buf, _T("NLF v"), 5)) { ERROR_MSG(_T("Error: Invalid language file.\n")); return 0; } int nlf_version = _ttoi(buf+5); if (nlf_version != NLF_VERSION) { if (nlf_version != 2 && nlf_version != 3 && nlf_version != 4 && nlf_version != 5) { ERROR_MSG(_T("Error: Language file version doesn't match NSIS version.\n")); return 0; } } // Get language ID if (!GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN, errlr)) goto l_readerr; LANGID lang_id = _ttoi(buf); // Get appropriate table LanguageTable *table = GetLangTable(lang_id); if (!table) return 0; NLF *nlf = &table->nlf; if (nlf->m_bLoaded) { ERROR_MSG(_T("Error: can't load same language file twice.\n")); return 0; } // Generate language name TCHAR *p, *p2, *p3, t = 0; p = _tcsrchr(filename, _T('.')); if (p) { t = *p; *p = 0; } p2 = _tcsrchr(filename, _T('\\')), p3 = _tcsrchr(filename, _T('/')); if (p3 > p2) p2 = p3; if (p2) { p2++; nlf->m_szName = (TCHAR*)malloc((_tcslen(p2)+1)*sizeof(TCHAR)); _tcscpy(nlf->m_szName, p2); } else { nlf->m_szName = (TCHAR*)malloc((_tcslen(filename)+1)*sizeof(TCHAR)); _tcscpy(nlf->m_szName, filename); } if (p) *p = t; if (nlf_version != NLF_VERSION) { warning_fl(DW_NLF_OLDVERSION, _T("%") NPRIs _T(" language file version doesn't match. Using default English texts for missing strings."), nlf->m_szName); } // set ^Language nlf->m_szStrings[NLF_LANGUAGE] = _tcsdup(nlf->m_szName); size_t temp; // Get font if (!GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN, errlr)) goto l_readerr; if (!nlf->m_szFont) { TrimTrailingNewlines(buf); if (buf[0] != _T('-') || buf[1] != 0) { nlf->m_szFont = (TCHAR*)malloc((_tcslen(buf)+1)*sizeof(TCHAR)); _tcscpy(nlf->m_szFont, buf); } } if (!GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN, errlr)) goto l_readerr; if (!nlf->m_iFontSize) { if (buf[0] != _T('-') || buf[1] != 0) { nlf->m_iFontSize = _ttoi(buf); } } // Get code page bool isnlfdataucp = false; // Unicode-only language? nlf->m_uCodePage = NSISRT_GetASCIICodepage(); if (!GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN, errlr)) goto l_readerr; TrimTrailingNewlines(buf); if (buf[0] != _T('-') || buf[1] != 0) { nlf->m_uCodePage = _ttoi(buf); isnlfdataucp = NStreamEncoding::IsUnicodeCodepage(nlf->m_uCodePage); if (isnlfdataucp && 1200 != nlf->m_uCodePage) { ERROR_MSG(_T("Error: Unicode-only language files must use codepage 1200!\n")); return 0; } if ((unsigned)nlf->m_uCodePage <= 1 && !lr.IsUnicode()) // Warn if someone uses a system specific codepage { warning_fl(DW_NLF_SYSCP, _T("%") NPRIs _T(" language file uses the system default codepage!"), nlf->m_szName); } if (CP_ACP != nlf->m_uCodePage && !isnlfdataucp && !IsValidCodePage(nlf->m_uCodePage)) { warning_fl(DW_NLF_UNSUPPORTED_CP, _T("%") NPRIs _T(" language file uses a codepage (%d) that is not supported on this system, using ACP!"), nlf->m_szName, nlf->m_uCodePage); nlf->m_uCodePage = CP_ACP; } } // SVN is not a big fan of UTF16 so we should always use UTF8SIG if (isnlfdataucp && !lr.StreamEncoding().IsUTF8()) { warning_fl(DW_NLF_NOT_PREFERRED_ENC, _T("%") NPRIs _T(" Unicode language file is not UTF8SIG."), nlf->m_szName); } #ifdef _UNICODE if (!lr.IsUnicode()) { if (nlf->m_szFont) { // Convert font name now that we know the codepage TCHAR* oldfont = nlf->m_szFont; nlf->m_szFont = _tcsdup(CtoTString2(TtoCString(oldfont), nlf->m_uCodePage)); // BUGBUG: Depends on lossless ACP -> TCHAR -> NLF CP -> TCHAR conversion! free(oldfont); } lr.StreamEncoding().SetCodepage(nlf->m_uCodePage); // Read the rest of this NLF file with the correct MBCS > TCHAR conversion } #endif // Get RTL setting if (!GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN, errlr)) goto l_readerr; nlf->m_szStrings[NLF_RTL] = (TCHAR*) malloc(2*sizeof(TCHAR)); nlf->m_bRTL = false; if (buf[0] == _T('R') && buf[1] == _T('T') && buf[2] == _T('L') && (!buf[3] || buf[3] == _T('\r') || buf[3] == _T('\n'))) { nlf->m_bRTL = true; } _tcscpy(nlf->m_szStrings[NLF_RTL], nlf->m_bRTL ? _T("1") : _T("0")); // Read strings for (int i = 0; i < NLF_STRINGS_NO_SPECIAL; i++) { // skip virtual strings if (!NLFStrings[i].szDefault) continue; // Fill in for missing strings // 0 will mean default will be used from NLFStrings switch (i) { case NLF_BTN_LICENSE_AGREE: case NLF_BTN_LICENSE_DISAGREE: if (nlf_version >= 3) break; case NLF_LOG_INSTALL_PROCESS: case NLF_BYTE: case NLF_KILO: case NLF_MEGA: case NLF_GIGA: case NLF_REGISTERING: case NLF_UNREGISTERING: if (nlf_version >= 4) break; case NLF_FILE_ERROR_NOIGNORE: if (nlf_version >= 5) break; case NLF_USUBCAPTION_OPTIONS: case NLF_USUBCAPTION_DIR: case NLF_CLICK_NEXT: case NLF_CLICK_INSTALL: case NLF_CLICK_UNINSTALL: case NLF_LICENSE_TEXT: case NLF_LICENSE_TEXT_FSCB: case NLF_LICENSE_TEXT_FSRB: case NLF_ULICENSE_TEXT: case NLF_ULICENSE_TEXT_FSCB: case NLF_ULICENSE_TEXT_FSRB: case NLF_COMP_TEXT: case NLF_UCOMP_TEXT: case NLF_UCOMP_SUBTEXT1: case NLF_UCOMP_SUBTEXT1_NO_INST_TYPES: case NLF_UCOMP_SUBTEXT2: case NLF_DIR_TEXT: case NLF_DIR_BROWSETEXT: case NLF_UDIR_TEXT: case NLF_UDIR_SUBTEXT: case NLF_UDIR_BROWSETEXT: case NLF_UNINST_TEXT: if (nlf_version >= 6) break; nlf->m_szStrings[i] = 0; continue; // This applies to the for loop and not this switch! } errlr = GetNextNLFLine(lr, buf, NSIS_MAX_STRLEN); if (_tcslen(buf) == NSIS_MAX_STRLEN-1) { ERROR_MSG(_T("Error: String too long (string #%d - \"%") NPRIs _T("\")\n"), i, NLFStrings[i].szLangStringName); return 0; } if (NStream::OK != errlr) goto l_readerr; temp=_tcslen(buf); // Not using TrimTrailingNewlines because we need temp to be correct for the quote trimming while (buf[temp-1] == _T('\n') || buf[temp-1] == _T('\r')) { buf[--temp] = 0; } TCHAR *in = buf; // trim quotes if (buf[0] == _T('"') && buf[temp-1] == _T('"')) { in++; buf[--temp] = 0; } nlf->m_szStrings[i] = (TCHAR*)malloc((temp+1)*sizeof(TCHAR)); TCHAR *out; for (out = nlf->m_szStrings[i]; *in; in++, out++) { if (*in == _T('\\')) { in++; switch (*in) { case _T('n'): *out = _T('\n'); break; case _T('r'): *out = _T('\r'); break; case _T('t'): *out = _T('\t'); break; default: *out++ = _T('\\'); *out = *in; } } else *out = *in; } *out = 0; } nlf->m_bLoaded = true; return table; } void CEXEBuild::DeleteLangTable(LanguageTable *table) { if (table->nlf.m_szName) free(table->nlf.m_szName); if (table->nlf.m_szFont) free(table->nlf.m_szFont); delete table->lang_strings; for (int i = 0; i < NLF_STRINGS; i++) { if (table->nlf.m_szStrings[i]) free(table->nlf.m_szStrings[i]); } }