diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..412eeda7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/resources/style.css b/resources/style.css index d23693f9..e869993f 100755 --- a/resources/style.css +++ b/resources/style.css @@ -1,178 +1,178 @@ -/* BOSS - - A plugin load order optimiser for games that use the esp/esm plugin system. - - Copyright (C) 2013 WrinklyNinja - - This file is part of BOSS. - - BOSS 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 3 of - the License, or (at your option) any later version. - - BOSS 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 BOSS. If not, see - . -*/ -body { - font:10pt/1.5 Helvetica,sans-serif; - margin:0; - background:#f8f8f8; -} -noscript div { - background:#c00; - display:block; - padding:10px 5px; - color:white; -} -#nav { - background:#e3e3e3; - position:fixed; - top:0; - left:0; - z-index:2; - box-shadow: 0 0 3px 1px rgba(0,0,0,0.5); - width:100%; -} -.button { - display:inline-table; - cursor:pointer; - padding:0.5em; - text-decoration:none; - transition:background 0.2s; -} -.button.current { - background: #ccc; -} -.button[id] { - position:absolute; - right:0; -} -#main { - position:absolute; - top:32px; - width:100%; -} -#summary, #plugins { - padding:1em; -} -table { - margin:0 auto; - border-radius:10px; - border-collapse:separate; - border-spacing:0; -} -td { - padding:0.5em; - line-height:1.5; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; - text-align:right; - padding-left:3em; -} -table tr:first-child td { - border-top: 1px solid #ccc; -} -table td:first-child { - text-align:left; - padding-left:0.5em; - border-left: 1px solid #ccc; - border-right: none; -} -table tr:first-child td:first-child { - border-top-left-radius: 5px; -} -table tr:first-child td:last-child { - border-top-right-radius: 5px; -} -table tr:last-child td:first-child { - border-bottom-left-radius: 5px; -} -table tr:last-child td:last-child { - border-bottom-right-radius: 5px; -} -#noChanges { - font-weight:bold; - font-size:12pt; - text-align:center; - margin:1em; -} -#filters { - background:#e8e8e8; - z-index:1; - overflow:hidden; - position:fixed; - right:0; - top:32px; - display:block; - box-shadow:0 0 3px 1px rgba(0,0,0,0.5); -} -#filters.hidden { - display:none; -} -#filters label { - display:block; - padding:0.2em 0.5em; - white-space:nowrap; - cursor:pointer; -} -#filters div { - padding:0.4em; - font-style:italic; -} -input[type='checkbox'] { - position:relative; - top:0.15em; - margin-right:0.5em; -} - -ul {margin-top:0.5em; margin-bottom:1em;} -li {margin:0.75em 0;} - -#plugins > ul {list-style:none;} -.hidden, li.hidden, #summary.hidden, #plugins.hidden { - display:none; -} - - -/* Old BOSS Log CSS */ - -ul{list-style:none;padding-left:0;} - -ul li{margin-left:0;margin-bottom:1em;} - -li ul{margin-top:.5em;padding-left:2.5em;margin-bottom:2em;} - -li.error{background:#ff9090;display:table;padding:0.3em 0.5em;border-radius:0.5em;} - -li.warn{background:#FFDD55;display:table;padding:0.3em 0.5em;border-radius:0.5em;} - -li.success{background:#90ff90;display:table;padding:0.3em 0.5em;border-radius:0.5em;} - -.version{color:#6394F8;margin-right:1em;} - -.crc{color:#BC8923;margin-right:1em;} - -.mod{margin-right:1em;} - -tr.good td {background: #90ff90;} - -tr.warn td {background: #FFDD55;} - -tr.error td {background:#ff9090;} - -th {text-align:left; padding:1em; background:lightblue;} - -.message {color:#880088;} - -#summary li {border-radius:0.5em;padding:0.5em 1em;display:table;background:#DDD;} - -#summary li.error{background:#ff9090;} - -#summary li.warn{background:#FFDD55;} +/* BOSS + + A plugin load order optimiser for games that use the esp/esm plugin system. + + Copyright (C) 2013 WrinklyNinja + + This file is part of BOSS. + + BOSS 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 3 of + the License, or (at your option) any later version. + + BOSS 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 BOSS. If not, see + . +*/ +body { + font:10pt/1.5 Helvetica,sans-serif; + margin:0; + background:#f8f8f8; +} +noscript div { + background:#c00; + display:block; + padding:10px 5px; + color:white; +} +#nav { + background:#e3e3e3; + position:fixed; + top:0; + left:0; + z-index:2; + box-shadow: 0 0 3px 1px rgba(0,0,0,0.5); + width:100%; +} +.button { + display:inline-table; + cursor:pointer; + padding:0.5em; + text-decoration:none; + transition:background 0.2s; +} +.button.current { + background: #ccc; +} +.button[id] { + position:absolute; + right:0; +} +#main { + position:absolute; + top:32px; + width:100%; +} +#summary, #plugins { + padding:1em; +} +table { + margin:0 auto; + border-radius:10px; + border-collapse:separate; + border-spacing:0; +} +td { + padding:0.5em; + line-height:1.5; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + text-align:right; + padding-left:3em; +} +table tr:first-child td { + border-top: 1px solid #ccc; +} +table td:first-child { + text-align:left; + padding-left:0.5em; + border-left: 1px solid #ccc; + border-right: none; +} +table tr:first-child td:first-child { + border-top-left-radius: 5px; +} +table tr:first-child td:last-child { + border-top-right-radius: 5px; +} +table tr:last-child td:first-child { + border-bottom-left-radius: 5px; +} +table tr:last-child td:last-child { + border-bottom-right-radius: 5px; +} +#noChanges { + font-weight:bold; + font-size:12pt; + text-align:center; + margin:1em; +} +#filters { + background:#e8e8e8; + z-index:1; + overflow:hidden; + position:fixed; + right:0; + top:32px; + display:block; + box-shadow:0 0 3px 1px rgba(0,0,0,0.5); +} +#filters.hidden { + display:none; +} +#filters label { + display:block; + padding:0.2em 0.5em; + white-space:nowrap; + cursor:pointer; +} +#filters div { + padding:0.4em; + font-style:italic; +} +input[type='checkbox'] { + position:relative; + top:0.15em; + margin-right:0.5em; +} + +ul {margin-top:0.5em; margin-bottom:1em;} +li {margin:0.75em 0;} + +#plugins > ul {list-style:none;} +.hidden, li.hidden, #summary.hidden, #plugins.hidden { + display:none; +} + + +/* Old BOSS Log CSS */ + +ul{list-style:none;padding-left:0;} + +ul li{margin-left:0;margin-bottom:1em;} + +li ul{margin-top:.5em;padding-left:2.5em;margin-bottom:2em;} + +li.error{background:#ff9090;display:table;padding:0.3em 0.5em;border-radius:0.5em;} + +li.warn{background:#FFDD55;display:table;padding:0.3em 0.5em;border-radius:0.5em;} + +li.success{background:#90ff90;display:table;padding:0.3em 0.5em;border-radius:0.5em;} + +.version{color:#6394F8;margin-right:1em;} + +.crc{color:#BC8923;margin-right:1em;} + +.mod{margin-right:1em;} + +tr.good td {background: #90ff90;} + +tr.warn td {background: #FFDD55;} + +tr.error td {background:#ff9090;} + +th {text-align:left; padding:1em; background:lightblue;} + +.message {color:#880088;} + +#summary li {border-radius:0.5em;padding:0.5em 1em;display:table;background:#DDD;} + +#summary li.error{background:#ff9090;} + +#summary li.warn{background:#FFDD55;} diff --git a/src/backend/helpers.cpp b/src/backend/helpers.cpp index 400ba368..5a7cecbc 100644 --- a/src/backend/helpers.cpp +++ b/src/backend/helpers.cpp @@ -1,471 +1,471 @@ -/* BOSS - - A plugin load order optimiser for games that use the esp/esm plugin system. - - Copyright (C) 2012-2013 WrinklyNinja - - This file is part of BOSS. - - BOSS 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 3 of - the License, or (at your option) any later version. - - BOSS 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 BOSS. If not, see - . -*/ - -#include "helpers.h" -#include "error.h" -#include "streams.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#if _WIN32 || _WIN64 -# ifndef UNICODE -# define UNICODE -# endif -# ifndef _UNICODE -# define _UNICODE -# endif -# include "windows.h" -# include "shlobj.h" -#endif -#define BUFSIZE 4096 - -namespace boss { - using namespace std; - using boost::algorithm::replace_all; - using boost::algorithm::replace_first; - namespace karma = boost::spirit::karma; - namespace fs = boost::filesystem; - - - /// REGEX expression definition - /// Each expression is composed of three parts: - /// 1. The marker string "version", "ver", "rev", "v" or "r" - /// 2. The version string itself. - - const char* regex1 = - "^(?:\\bversion\\b[ ]*(?:[:.\\-]?)|\\brevision\\b(?:[:.\\-]?))[ ]*" - "((?:alpha|beta|test|debug)?\\s*[-0-9a-zA-Z._+]+\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*))$" - ; - - const char* regex2 = - "(?:\\bversion\\b(?:[ :]?)|\\brevision\\b(?:[:.\\-]?))[ ]*" - "([0-9][-0-9a-zA-Z._]+\\+?)" - ; - - const char* regex3 = - "(?:\\bver(?:[:.]?)|\\brev(?:[:.]?))\\s*" - "([0-9][-0-9a-zA-Z._]*\\+?)" - ; - - // Matches "Updated: " for the Bashed patch - const char* regex4 = - "(?:Updated:)\\s*" - "([-0-9aAmMpP/ :]+)$" - ; - - // Matches isolated versions as last resort - const char* regex5 = - "(?:(?:\\bv|\\br)(?:\\s?)(?:[-.:])?(?:\\s*))" - "((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*[0-9][-0-9a-zA-Z._]*\\+?)" - ; - - // Matches isolated versions as last resort - const char* regex6 = - "((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*\\b[0-9][-0-9a-zA-Z._]*\\+?)$" - ; - - const char* regex7 = - "(^\\bmark\\b\\s*\\b[IVX0-9][-0-9a-zA-Z._+]*\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*)?)$" - ; - - /// Array used to try each of the expressions defined above using - /// an iteration for each of them. - boost::regex version_checks[7] = { - boost::regex(regex1, boost::regex::icase), - boost::regex(regex2, boost::regex::icase), - boost::regex(regex3, boost::regex::icase), - boost::regex(regex4, boost::regex::icase), - boost::regex(regex5, boost::regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer. - boost::regex(regex6, boost::regex::icase), //This is responsible for metallicow's false positive. - boost::regex(regex7, boost::regex::icase) - }; - - ////////////////////////////////////////////////////////////////////////// - // Helper functions - ////////////////////////////////////////////////////////////////////////// - - //Calculate the CRC of the given file for comparison purposes. - uint32_t GetCrc32(const fs::path& filename) { - uint32_t chksum = 0; - static const size_t buffer_size = 8192; - char buffer[buffer_size]; - boss::ifstream ifile(filename, ios::binary); - // LOG_TRACE("calculating CRC for: '%s'", filename.string().c_str()); - boost::crc_32_type result; - if (ifile) { - do { - ifile.read(buffer, buffer_size); - result.process_bytes(buffer, ifile.gcount()); - } while (ifile); - chksum = result.checksum(); - } else { - throw error(error::path_read_fail, "Unable to open \"" + filename.string() + "\" for CRC calculation."); - } - // LOG_DEBUG("CRC32('%s'): 0x%x", filename.string().c_str(), chksum); - return chksum; - } - - //Converts an integer to a string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion... - std::string IntToString(const int n) { - string out; - back_insert_iterator sink(out); - karma::generate(sink,karma::upper[karma::int_],n); - return out; - } - - //Converts an integer to a hex string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion... - std::string IntToHexString(const int n) { - string out; - back_insert_iterator sink(out); - karma::generate(sink,karma::upper[karma::hex],n); - return out; - } - - //Converts a boolean to a string representation (true/false) - std::string BoolToString(const bool b) { - if (b) - return "true"; - else - return "false"; - } - - //Check if registry subkey exists. - bool RegKeyExists(const std::string& keyStr, const std::string& subkey, const std::string& value) { - return !RegKeyStringValue(keyStr, subkey, value).empty(); - } - - //Get registry subkey value string. - string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value) { -#if _WIN32 || _WIN64 - HKEY hKey, key; - DWORD BufferSize = 4096; - wchar_t val[4096]; - - if (keyStr == "HKEY_CLASSES_ROOT") - key = HKEY_CLASSES_ROOT; - else if (keyStr == "HKEY_CURRENT_CONFIG") - key = HKEY_CURRENT_CONFIG; - else if (keyStr == "HKEY_CURRENT_USER") - key = HKEY_CURRENT_USER; - else if (keyStr == "HKEY_LOCAL_MACHINE") - key = HKEY_LOCAL_MACHINE; - else if (keyStr == "HKEY_USERS") - key = HKEY_USERS; - - LONG ret = RegOpenKeyEx(key, fs::path(subkey).wstring().c_str(), 0, KEY_READ|KEY_WOW64_32KEY, &hKey); - - if (ret == ERROR_SUCCESS) { - ret = RegQueryValueEx(hKey, fs::path(value).wstring().c_str(), NULL, NULL, (LPBYTE)&val, &BufferSize); - RegCloseKey(hKey); - - if (ret == ERROR_SUCCESS) - return fs::path(val).string(); //Easiest way to convert from wide to narrow character strings. - else - return ""; - } else - return ""; -#else - return ""; -#endif - } - - boost::filesystem::path GetLocalAppDataPath() { -#if _WIN32 || _WIN64 - HWND owner; - TCHAR path[MAX_PATH]; - - HRESULT res = SHGetFolderPath(owner, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path); - - if (res == S_OK) - return fs::path(path); - else - return fs::path(""); -#else - return fs::path(""); -#endif - } - - //Turns an absolute filesystem path into a valid file:// URL. - std::string ToFileURL(const fs::path& file) { - //URLs are UTF-8 encoded then any characters (equiv. their corresponding bytes) not in the unreserved set (equiv. their corresponding bytes) are replaced by a percentage sign followed by the hex representation of their binary value. - string unreserved = "-.0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; //Unreserved in byte value order, plus the colon character since that's allowed for drive paths. - - string url = "file:///"; - for (boost::filesystem::path::const_iterator it=file.begin(), endit=file.end(); it != endit; ++it) { - string part = it->string(); - if (part == "/") //Skip exta backslash after drive path. - continue; - //String iterator is byte-by-byte, not character-by-character, which is good. - for (string::const_iterator jt=part.begin(), endjt=part.end(); jt != endjt; ++jt) { - if (!binary_search(unreserved.begin(), unreserved.end(), *jt)) - //Replace with percentage-hex value. - url += '%' + IntToHexString(*jt); - else - url += *jt; - } - url += '/'; - } - url.resize(url.length()-1); //Get rid of trailing forward slash. - - return url; - } - - std::string GetLangString(const unsigned int num) { - if (num == g_lang_english) - return "eng"; - else if (num == g_lang_spanish) - return "spa"; - else if (num == g_lang_russian) - return "rus"; - else - return ""; - } - - unsigned int GetLangNum(const std::string& str) { - if (boost::iequals(str, "eng")) - return g_lang_english; - else if (boost::iequals(str, "spa")) - return g_lang_spanish; - else if (boost::iequals(str, "rus")) - return g_lang_russian; - else - return g_lang_any; - } - - //Runs a command using the Win32 API. - bool RunCommand(const std::string& command, std::string& output) { - HANDLE consoleWrite = NULL; - HANDLE consoleRead = NULL; - - SECURITY_ATTRIBUTES saAttr; - - PROCESS_INFORMATION piProcInfo; - STARTUPINFO siStartInfo; - - CHAR chBuf[BUFSIZE]; - DWORD dwRead; - - DWORD exitCode; - - //Init attributes. - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - BOOST_LOG_TRIVIAL(trace) << "Creating a pipe for the process."; - - //Create I/O pipes. - if (!CreatePipe(&consoleRead, &consoleWrite, &saAttr, 0)) { - BOOST_LOG_TRIVIAL(error) << "Could not create pipe for process."; - throw error(error::windows_error, "Could not create pipe for process."); - } - - //Create a child process. - BOOST_LOG_TRIVIAL(trace) << "Creating a child process."; - ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); - - ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); - siStartInfo.cb = sizeof(STARTUPINFO); - siStartInfo.hStdError = consoleWrite; - siStartInfo.hStdOutput = consoleWrite; - siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - siStartInfo.wShowWindow = SW_HIDE; - - const int utf16Len = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, NULL, 0); - wchar_t * cmdLine = new wchar_t[utf16Len]; - MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, cmdLine, utf16Len); - - bool result = CreateProcess(NULL, - cmdLine, // command line - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - CREATE_NO_WINDOW, // creation flags - NULL, // use parent's environment - NULL, // use parent's current directory - &siStartInfo, // STARTUPINFO pointer - &piProcInfo); // receives PROCESS_INFORMATION - - delete [] cmdLine; - - if (!result) { - BOOST_LOG_TRIVIAL(error) << "Could not create process."; - throw error(error::windows_error, "Could not create process."); - } - - BOOST_LOG_TRIVIAL(trace) << "Waiting for process to complete."; - - WaitForSingleObject(piProcInfo.hProcess, INFINITE); - - BOOST_LOG_TRIVIAL(trace) << "Getting the process exit code."; - - if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode)) { - BOOST_LOG_TRIVIAL(error) << "Could not get process exit code."; - throw error(error::windows_error, "Could not get process exit code."); - } - - BOOST_LOG_TRIVIAL(trace) << "Getting the process output."; - - if (!ReadFile(consoleRead, chBuf, BUFSIZE, &dwRead, NULL)) { - BOOST_LOG_TRIVIAL(error) << "Could not read process output."; - throw error(error::windows_error, "Could not read process output."); - } - - output = string(chBuf, dwRead); - - return exitCode == 0; - } - - - ////////////////////////////// - // Version Class Functions - ////////////////////////////// - - Version::Version() {} - - Version::Version(const std::string& ver) - : verString(ver) {} - - Version::Version(const fs::path& file) { -#if _WIN32 || _WIN64 - DWORD dummy = 0; - DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy); - - if (size > 0) { - LPBYTE point = new BYTE[size]; - UINT uLen; - VS_FIXEDFILEINFO *info; - string ver; - - GetFileVersionInfo(file.wstring().c_str(),0,size,point); - - VerQueryValue(point,L"\\",(LPVOID *)&info,&uLen); - - DWORD dwLeftMost = HIWORD(info->dwFileVersionMS); - DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS); - DWORD dwSecondRight = HIWORD(info->dwFileVersionLS); - DWORD dwRightMost = LOWORD(info->dwFileVersionLS); - - delete [] point; - - verString = IntToString(dwLeftMost) + '.' + IntToString(dwSecondLeft) + '.' + IntToString(dwSecondRight) + '.' + IntToString(dwRightMost); - } -#else - // ensure filename has no quote characters in it to avoid command injection attacks - if (string::npos != file.string().find('"')) { - // command mostly borrowed from the gnome-exe-thumbnailer.sh script - // wrestool is part of the icoutils package - string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'"; - - FILE *fp = popen(cmd.c_str(), "r"); - - // read out the version string - static const uint32_t BUFSIZE = 32; - char buf[BUFSIZE]; - if (NULL != fgets(buf, BUFSIZE, fp)) { - verString = string(buf); - } - pclose(fp); - } -#endif - } - - Version::Version(const Plugin& plugin) { - verString = plugin.Version(); - } - - string Version::AsString() const { - return verString; - } - - bool Version::operator < (Version ver) { - //Version string could have a wide variety of formats. Use regex to choose specific comparison types. - - boost::regex reg1("(\\d+\\.?)+"); //a.b.c.d.e.f.... where the letters are all integers, and 'a' is the shortest possible match. - - //boost::regex reg2("(\\d+\\.?)+([a-zA-Z\\-]+(\\d+\\.?)*)+"); //Matches a mix of letters and numbers - from "0.99.xx", "1.35Alpha2", "0.9.9MB8b1", "10.52EV-D", "1.62EV" to "10.0EV-D1.62EV". - - if (boost::regex_match(verString, reg1) && boost::regex_match(ver.AsString(), reg1)) { - //First type: numbers separated by periods. If two versions have a different number of numbers, then the shorter should be padded - //with zeros. An arbitrary number of numbers should be supported. - istringstream parser1(verString); - istringstream parser2(ver.AsString()); - while (parser1.good() || parser2.good()) { - //Check if each stringstream is OK for i/o before doing anything with it. If not, replace its extracted value with a 0. - uint32_t n1, n2; - if (parser1.good()) { - parser1 >> n1; - parser1.get(); - } else - n1 = 0; - if (parser2.good()) { - parser2 >> n2; - parser2.get(); - } else - n2 = 0; - if (n1 < n2) - return true; - else if (n1 > n2) - return false; - } - return false; - } else { - //Wacky format. Use the Alphanum Algorithm. (what a name!) - return (doj::alphanum_comp(verString, ver.AsString()) < 0); - } - } - - bool Version::operator > (Version ver) { - return (*this != ver && !(*this < ver)); - } - - bool Version::operator >= (Version ver) { - return (*this == ver || *this > ver); - } - - bool Version::operator <= (Version ver) { - return (*this == ver || *this < ver); - } - - bool Version::operator == (Version ver) { - return (verString == ver.AsString()); - } - - bool Version::operator != (Version ver) { - return !(*this == ver); - } -} +/* BOSS + + A plugin load order optimiser for games that use the esp/esm plugin system. + + Copyright (C) 2012-2013 WrinklyNinja + + This file is part of BOSS. + + BOSS 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 3 of + the License, or (at your option) any later version. + + BOSS 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 BOSS. If not, see + . +*/ + +#include "helpers.h" +#include "error.h" +#include "streams.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#if _WIN32 || _WIN64 +# ifndef UNICODE +# define UNICODE +# endif +# ifndef _UNICODE +# define _UNICODE +# endif +# include "windows.h" +# include "shlobj.h" +#endif +#define BUFSIZE 4096 + +namespace boss { + using namespace std; + using boost::algorithm::replace_all; + using boost::algorithm::replace_first; + namespace karma = boost::spirit::karma; + namespace fs = boost::filesystem; + + + /// REGEX expression definition + /// Each expression is composed of three parts: + /// 1. The marker string "version", "ver", "rev", "v" or "r" + /// 2. The version string itself. + + const char* regex1 = + "^(?:\\bversion\\b[ ]*(?:[:.\\-]?)|\\brevision\\b(?:[:.\\-]?))[ ]*" + "((?:alpha|beta|test|debug)?\\s*[-0-9a-zA-Z._+]+\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*))$" + ; + + const char* regex2 = + "(?:\\bversion\\b(?:[ :]?)|\\brevision\\b(?:[:.\\-]?))[ ]*" + "([0-9][-0-9a-zA-Z._]+\\+?)" + ; + + const char* regex3 = + "(?:\\bver(?:[:.]?)|\\brev(?:[:.]?))\\s*" + "([0-9][-0-9a-zA-Z._]*\\+?)" + ; + + // Matches "Updated: " for the Bashed patch + const char* regex4 = + "(?:Updated:)\\s*" + "([-0-9aAmMpP/ :]+)$" + ; + + // Matches isolated versions as last resort + const char* regex5 = + "(?:(?:\\bv|\\br)(?:\\s?)(?:[-.:])?(?:\\s*))" + "((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*[0-9][-0-9a-zA-Z._]*\\+?)" + ; + + // Matches isolated versions as last resort + const char* regex6 = + "((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*\\b[0-9][-0-9a-zA-Z._]*\\+?)$" + ; + + const char* regex7 = + "(^\\bmark\\b\\s*\\b[IVX0-9][-0-9a-zA-Z._+]*\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*)?)$" + ; + + /// Array used to try each of the expressions defined above using + /// an iteration for each of them. + boost::regex version_checks[7] = { + boost::regex(regex1, boost::regex::icase), + boost::regex(regex2, boost::regex::icase), + boost::regex(regex3, boost::regex::icase), + boost::regex(regex4, boost::regex::icase), + boost::regex(regex5, boost::regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer. + boost::regex(regex6, boost::regex::icase), //This is responsible for metallicow's false positive. + boost::regex(regex7, boost::regex::icase) + }; + + ////////////////////////////////////////////////////////////////////////// + // Helper functions + ////////////////////////////////////////////////////////////////////////// + + //Calculate the CRC of the given file for comparison purposes. + uint32_t GetCrc32(const fs::path& filename) { + uint32_t chksum = 0; + static const size_t buffer_size = 8192; + char buffer[buffer_size]; + boss::ifstream ifile(filename, ios::binary); + // LOG_TRACE("calculating CRC for: '%s'", filename.string().c_str()); + boost::crc_32_type result; + if (ifile) { + do { + ifile.read(buffer, buffer_size); + result.process_bytes(buffer, ifile.gcount()); + } while (ifile); + chksum = result.checksum(); + } else { + throw error(error::path_read_fail, "Unable to open \"" + filename.string() + "\" for CRC calculation."); + } + // LOG_DEBUG("CRC32('%s'): 0x%x", filename.string().c_str(), chksum); + return chksum; + } + + //Converts an integer to a string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion... + std::string IntToString(const int n) { + string out; + back_insert_iterator sink(out); + karma::generate(sink,karma::upper[karma::int_],n); + return out; + } + + //Converts an integer to a hex string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion... + std::string IntToHexString(const int n) { + string out; + back_insert_iterator sink(out); + karma::generate(sink,karma::upper[karma::hex],n); + return out; + } + + //Converts a boolean to a string representation (true/false) + std::string BoolToString(const bool b) { + if (b) + return "true"; + else + return "false"; + } + + //Check if registry subkey exists. + bool RegKeyExists(const std::string& keyStr, const std::string& subkey, const std::string& value) { + return !RegKeyStringValue(keyStr, subkey, value).empty(); + } + + //Get registry subkey value string. + string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value) { +#if _WIN32 || _WIN64 + HKEY hKey, key; + DWORD BufferSize = 4096; + wchar_t val[4096]; + + if (keyStr == "HKEY_CLASSES_ROOT") + key = HKEY_CLASSES_ROOT; + else if (keyStr == "HKEY_CURRENT_CONFIG") + key = HKEY_CURRENT_CONFIG; + else if (keyStr == "HKEY_CURRENT_USER") + key = HKEY_CURRENT_USER; + else if (keyStr == "HKEY_LOCAL_MACHINE") + key = HKEY_LOCAL_MACHINE; + else if (keyStr == "HKEY_USERS") + key = HKEY_USERS; + + LONG ret = RegOpenKeyEx(key, fs::path(subkey).wstring().c_str(), 0, KEY_READ|KEY_WOW64_32KEY, &hKey); + + if (ret == ERROR_SUCCESS) { + ret = RegQueryValueEx(hKey, fs::path(value).wstring().c_str(), NULL, NULL, (LPBYTE)&val, &BufferSize); + RegCloseKey(hKey); + + if (ret == ERROR_SUCCESS) + return fs::path(val).string(); //Easiest way to convert from wide to narrow character strings. + else + return ""; + } else + return ""; +#else + return ""; +#endif + } + + boost::filesystem::path GetLocalAppDataPath() { +#if _WIN32 || _WIN64 + HWND owner; + TCHAR path[MAX_PATH]; + + HRESULT res = SHGetFolderPath(owner, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path); + + if (res == S_OK) + return fs::path(path); + else + return fs::path(""); +#else + return fs::path(""); +#endif + } + + //Turns an absolute filesystem path into a valid file:// URL. + std::string ToFileURL(const fs::path& file) { + //URLs are UTF-8 encoded then any characters (equiv. their corresponding bytes) not in the unreserved set (equiv. their corresponding bytes) are replaced by a percentage sign followed by the hex representation of their binary value. + string unreserved = "-.0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; //Unreserved in byte value order, plus the colon character since that's allowed for drive paths. + + string url = "file:///"; + for (boost::filesystem::path::const_iterator it=file.begin(), endit=file.end(); it != endit; ++it) { + string part = it->string(); + if (part == "/") //Skip exta backslash after drive path. + continue; + //String iterator is byte-by-byte, not character-by-character, which is good. + for (string::const_iterator jt=part.begin(), endjt=part.end(); jt != endjt; ++jt) { + if (!binary_search(unreserved.begin(), unreserved.end(), *jt)) + //Replace with percentage-hex value. + url += '%' + IntToHexString(*jt); + else + url += *jt; + } + url += '/'; + } + url.resize(url.length()-1); //Get rid of trailing forward slash. + + return url; + } + + std::string GetLangString(const unsigned int num) { + if (num == g_lang_english) + return "eng"; + else if (num == g_lang_spanish) + return "spa"; + else if (num == g_lang_russian) + return "rus"; + else + return ""; + } + + unsigned int GetLangNum(const std::string& str) { + if (boost::iequals(str, "eng")) + return g_lang_english; + else if (boost::iequals(str, "spa")) + return g_lang_spanish; + else if (boost::iequals(str, "rus")) + return g_lang_russian; + else + return g_lang_any; + } + + //Runs a command using the Win32 API. + bool RunCommand(const std::string& command, std::string& output) { + HANDLE consoleWrite = NULL; + HANDLE consoleRead = NULL; + + SECURITY_ATTRIBUTES saAttr; + + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + + CHAR chBuf[BUFSIZE]; + DWORD dwRead; + + DWORD exitCode; + + //Init attributes. + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + BOOST_LOG_TRIVIAL(trace) << "Creating a pipe for the process."; + + //Create I/O pipes. + if (!CreatePipe(&consoleRead, &consoleWrite, &saAttr, 0)) { + BOOST_LOG_TRIVIAL(error) << "Could not create pipe for process."; + throw error(error::windows_error, "Could not create pipe for process."); + } + + //Create a child process. + BOOST_LOG_TRIVIAL(trace) << "Creating a child process."; + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = consoleWrite; + siStartInfo.hStdOutput = consoleWrite; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + siStartInfo.wShowWindow = SW_HIDE; + + const int utf16Len = MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, NULL, 0); + wchar_t * cmdLine = new wchar_t[utf16Len]; + MultiByteToWideChar(CP_UTF8, 0, command.c_str(), -1, cmdLine, utf16Len); + + bool result = CreateProcess(NULL, + cmdLine, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + CREATE_NO_WINDOW, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + + delete [] cmdLine; + + if (!result) { + BOOST_LOG_TRIVIAL(error) << "Could not create process."; + throw error(error::windows_error, "Could not create process."); + } + + BOOST_LOG_TRIVIAL(trace) << "Waiting for process to complete."; + + WaitForSingleObject(piProcInfo.hProcess, INFINITE); + + BOOST_LOG_TRIVIAL(trace) << "Getting the process exit code."; + + if (!GetExitCodeProcess(piProcInfo.hProcess, &exitCode)) { + BOOST_LOG_TRIVIAL(error) << "Could not get process exit code."; + throw error(error::windows_error, "Could not get process exit code."); + } + + BOOST_LOG_TRIVIAL(trace) << "Getting the process output."; + + if (!ReadFile(consoleRead, chBuf, BUFSIZE, &dwRead, NULL)) { + BOOST_LOG_TRIVIAL(error) << "Could not read process output."; + throw error(error::windows_error, "Could not read process output."); + } + + output = string(chBuf, dwRead); + + return exitCode == 0; + } + + + ////////////////////////////// + // Version Class Functions + ////////////////////////////// + + Version::Version() {} + + Version::Version(const std::string& ver) + : verString(ver) {} + + Version::Version(const fs::path& file) { +#if _WIN32 || _WIN64 + DWORD dummy = 0; + DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy); + + if (size > 0) { + LPBYTE point = new BYTE[size]; + UINT uLen; + VS_FIXEDFILEINFO *info; + string ver; + + GetFileVersionInfo(file.wstring().c_str(),0,size,point); + + VerQueryValue(point,L"\\",(LPVOID *)&info,&uLen); + + DWORD dwLeftMost = HIWORD(info->dwFileVersionMS); + DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS); + DWORD dwSecondRight = HIWORD(info->dwFileVersionLS); + DWORD dwRightMost = LOWORD(info->dwFileVersionLS); + + delete [] point; + + verString = IntToString(dwLeftMost) + '.' + IntToString(dwSecondLeft) + '.' + IntToString(dwSecondRight) + '.' + IntToString(dwRightMost); + } +#else + // ensure filename has no quote characters in it to avoid command injection attacks + if (string::npos != file.string().find('"')) { + // command mostly borrowed from the gnome-exe-thumbnailer.sh script + // wrestool is part of the icoutils package + string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'"; + + FILE *fp = popen(cmd.c_str(), "r"); + + // read out the version string + static const uint32_t BUFSIZE = 32; + char buf[BUFSIZE]; + if (NULL != fgets(buf, BUFSIZE, fp)) { + verString = string(buf); + } + pclose(fp); + } +#endif + } + + Version::Version(const Plugin& plugin) { + verString = plugin.Version(); + } + + string Version::AsString() const { + return verString; + } + + bool Version::operator < (Version ver) { + //Version string could have a wide variety of formats. Use regex to choose specific comparison types. + + boost::regex reg1("(\\d+\\.?)+"); //a.b.c.d.e.f.... where the letters are all integers, and 'a' is the shortest possible match. + + //boost::regex reg2("(\\d+\\.?)+([a-zA-Z\\-]+(\\d+\\.?)*)+"); //Matches a mix of letters and numbers - from "0.99.xx", "1.35Alpha2", "0.9.9MB8b1", "10.52EV-D", "1.62EV" to "10.0EV-D1.62EV". + + if (boost::regex_match(verString, reg1) && boost::regex_match(ver.AsString(), reg1)) { + //First type: numbers separated by periods. If two versions have a different number of numbers, then the shorter should be padded + //with zeros. An arbitrary number of numbers should be supported. + istringstream parser1(verString); + istringstream parser2(ver.AsString()); + while (parser1.good() || parser2.good()) { + //Check if each stringstream is OK for i/o before doing anything with it. If not, replace its extracted value with a 0. + uint32_t n1, n2; + if (parser1.good()) { + parser1 >> n1; + parser1.get(); + } else + n1 = 0; + if (parser2.good()) { + parser2 >> n2; + parser2.get(); + } else + n2 = 0; + if (n1 < n2) + return true; + else if (n1 > n2) + return false; + } + return false; + } else { + //Wacky format. Use the Alphanum Algorithm. (what a name!) + return (doj::alphanum_comp(verString, ver.AsString()) < 0); + } + } + + bool Version::operator > (Version ver) { + return (*this != ver && !(*this < ver)); + } + + bool Version::operator >= (Version ver) { + return (*this == ver || *this > ver); + } + + bool Version::operator <= (Version ver) { + return (*this == ver || *this < ver); + } + + bool Version::operator == (Version ver) { + return (verString == ver.AsString()); + } + + bool Version::operator != (Version ver) { + return !(*this == ver); + } +} diff --git a/src/backend/helpers.h b/src/backend/helpers.h index 0b9e86e8..6d383bfc 100644 --- a/src/backend/helpers.h +++ b/src/backend/helpers.h @@ -1,96 +1,96 @@ -/* BOSS - - A plugin load order optimiser for games that use the esp/esm plugin system. - - Copyright (C) 2012-2013 WrinklyNinja - - This file is part of BOSS. - - BOSS 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 3 of - the License, or (at your option) any later version. - - BOSS 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 BOSS. If not, see - . -*/ - -#ifndef __BOSS_HELPERS__ -#define __BOSS_HELPERS__ - -#include "metadata.h" - -#include -#include -#include -#include - -namespace boss { - - /// Array used to try each of the expressions defined using - /// an iteration for each of them. - extern boost::regex version_checks[7]; - - ////////////////////////////////////////////////////////////////////////// - // Helper functions - ////////////////////////////////////////////////////////////////////////// - - //Calculate the CRC of the given file for comparison purposes. - uint32_t GetCrc32(const boost::filesystem::path& filename); - - //Converts an integer to a string using BOOST's Spirit.Karma. Faster than a stringstream conversion. - std::string IntToString(const int n); - - //Converts an integer to a hex string using BOOST's Spirit.Karma. Faster than a stringstream conversion. - std::string IntToHexString(const int n); - - //Converts a boolean to a string representation (true/false) - std::string BoolToString(const bool b); - - //Check if registry subkey exists. - bool RegKeyExists(const std::string& keyStr, const std::string& subkey, const std::string& value); - - //Get registry subkey value string. - std::string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value); - - //Get the local application data path. - boost::filesystem::path GetLocalAppDataPath(); - - //Turns an absolute filesystem path into a valid file:// URL. - std::string ToFileURL(const boost::filesystem::path& file); - - //Converts between BOSS's global const value and the ISO 639-3 value for a language. - std::string GetLangString(const unsigned int num); - unsigned int GetLangNum(const std::string& str); - - //Runs a command using the Win32 API. - bool RunCommand(const std::string& command, std::string& output); - - //Version class for more robust version comparisons. - class Version { - private: - std::string verString; - public: - Version(); - Version(const std::string& ver); - Version(const boost::filesystem::path& file); - Version(const Plugin& plugin); - - std::string AsString() const; - - bool operator > (Version); - bool operator < (Version); - bool operator >= (Version); - bool operator <= (Version); - bool operator == (Version); - bool operator != (Version); - }; -} - -#endif +/* BOSS + + A plugin load order optimiser for games that use the esp/esm plugin system. + + Copyright (C) 2012-2013 WrinklyNinja + + This file is part of BOSS. + + BOSS 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 3 of + the License, or (at your option) any later version. + + BOSS 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 BOSS. If not, see + . +*/ + +#ifndef __BOSS_HELPERS__ +#define __BOSS_HELPERS__ + +#include "metadata.h" + +#include +#include +#include +#include + +namespace boss { + + /// Array used to try each of the expressions defined using + /// an iteration for each of them. + extern boost::regex version_checks[7]; + + ////////////////////////////////////////////////////////////////////////// + // Helper functions + ////////////////////////////////////////////////////////////////////////// + + //Calculate the CRC of the given file for comparison purposes. + uint32_t GetCrc32(const boost::filesystem::path& filename); + + //Converts an integer to a string using BOOST's Spirit.Karma. Faster than a stringstream conversion. + std::string IntToString(const int n); + + //Converts an integer to a hex string using BOOST's Spirit.Karma. Faster than a stringstream conversion. + std::string IntToHexString(const int n); + + //Converts a boolean to a string representation (true/false) + std::string BoolToString(const bool b); + + //Check if registry subkey exists. + bool RegKeyExists(const std::string& keyStr, const std::string& subkey, const std::string& value); + + //Get registry subkey value string. + std::string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value); + + //Get the local application data path. + boost::filesystem::path GetLocalAppDataPath(); + + //Turns an absolute filesystem path into a valid file:// URL. + std::string ToFileURL(const boost::filesystem::path& file); + + //Converts between BOSS's global const value and the ISO 639-3 value for a language. + std::string GetLangString(const unsigned int num); + unsigned int GetLangNum(const std::string& str); + + //Runs a command using the Win32 API. + bool RunCommand(const std::string& command, std::string& output); + + //Version class for more robust version comparisons. + class Version { + private: + std::string verString; + public: + Version(); + Version(const std::string& ver); + Version(const boost::filesystem::path& file); + Version(const Plugin& plugin); + + std::string AsString() const; + + bool operator > (Version); + bool operator < (Version); + bool operator >= (Version); + bool operator <= (Version); + bool operator == (Version); + bool operator != (Version); + }; +} + +#endif diff --git a/src/backend/legacy-parser.h b/src/backend/legacy-parser.h index b8bb30e7..4835035d 100644 --- a/src/backend/legacy-parser.h +++ b/src/backend/legacy-parser.h @@ -1,719 +1,719 @@ -/* BOSS - - A plugin load order optimiser for games that use the esp/esm plugin system. - - Copyright (C) 2012-2013 WrinklyNinja - - This file is part of BOSS. - - BOSS 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 3 of - the License, or (at your option) any later version. - - BOSS 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 BOSS. If not, see - . -*/ - -#ifndef __BOSS_LEGACY_PARSER__ -#define __BOSS_LEGACY_PARSER__ - -// This file holds the code necessary for inputting a v2 masterlist and getting a list of plugins and a list of global messages from it. - -#ifndef BOOST_SPIRIT_UNICODE -#define BOOST_SPIRIT_UNICODE -#endif - -#include "metadata.h" -#include "error.h" -#include "game.h" -#include "streams.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boss { - namespace qi = boost::spirit::qi; - namespace unicode = boost::spirit::unicode; - namespace phoenix = boost::phoenix; - - //Message types for those that will be converted, but need identifying in the interim. Must not conflict with the values for the global types. - unsigned int message_inc = 1000; - unsigned int message_req = 1001; - unsigned int message_dirty = 1002; - - - /////////////////////////////// - // Keyword structures - /////////////////////////////// - - struct masterlistMsgKey_ : qi::symbols { - masterlistMsgKey_() { - add //New Message keywords. - ("say",g_message_say) - ("tag",g_message_tag) - ("req",message_req) - ("inc", message_inc) - ("dirty",message_dirty) - ("warn",g_message_warn) - ("error",g_message_error) - ; - } - }; - - - /////////////////////////////// - //Skipper Grammar - /////////////////////////////// - - //Skipper for userlist, modlist and ini parsers. - template - class Skipper : public qi::grammar { - public: - Skipper() : Skipper::base_type(start, "skipper grammar") { - - start = - spc - | UTF8 - | CComment - | CPlusPlusComment - | iniComment - | eof; - - spc = unicode::space - qi::eol; - - UTF8 = unicode::char_("\xef") >> unicode::char_("\xbb") >> unicode::char_("\xbf"); //UTF8 BOM - - CComment = "/*" >> *(unicode::char_ - "*/") >> "*/"; - - iniComment = qi::lit("#") >> *(unicode::char_ - qi::eol); - - CPlusPlusComment = !(qi::lit("http:") | qi::lit("https:") | qi::lit("file:")) >> "//" >> *(unicode::char_ - qi::eol); - - eof = *(spc | CComment | CPlusPlusComment | qi::eol) >> qi::eoi; - } - - void SkipIniComments(const bool b) { - if (b) - iniComment = (qi::lit("#") | qi::lit(";")) >> *(unicode::char_ - qi::eol); - else - iniComment = CComment; - } - private: - qi::rule start, spc, eof, CComment, CPlusPlusComment, lineComment, iniComment, UTF8; - }; - - /////////////////////////////// - //Modlist/Masterlist Grammar - /////////////////////////////// - - //Modlist/Masterlist grammar. - template - class LegacyMasterlistGrammar : public qi::grammar(), Skipper > { - public: - LegacyMasterlistGrammar() - : LegacyMasterlistGrammar::base_type(modList, "modlist grammar") { - masterlistMsgKey_ masterlistMsgKey; - const std::list noMessages; //An empty set of messages. - globalMessageBuffer = NULL; - - modList = - *qi::eol - > - ( - listVar - | globalMessage [phoenix::bind(&LegacyMasterlistGrammar::StoreGlobalMessage, this, qi::_1)] - | groupLine - | listPlugin [phoenix::push_back(qi::_val, qi::_1)] - | qi::eps - ) % +qi::eol; - - listVar = - (conditionals [phoenix::ref(lastConditional) = qi::_1] - >> unicode::no_case[qi::lit("set")] - >> ( - ':' - > charString - )) [phoenix::bind(&LegacyMasterlistGrammar::RecordVarCondition, this, qi::_1, qi::_2)] - ; - - globalMessage = - (conditionals [phoenix::ref(lastConditional) = qi::_1] - >> unicode::no_case[qi::lit("global")] - >> ( - messageKeyword - >> ':' - >> charString - ) - )[phoenix::bind(&LegacyMasterlistGrammar::MakeMessage, this, qi::_val, qi::_1, qi::_2, qi::_3)] - ; - - groupLine = - conditionals [phoenix::ref(lastConditional) = qi::_1] - >> unicode::no_case[qi::lit("begingroup:") - | qi::lit("endgroup")] - > charString; - - listPlugin = - conditionals [phoenix::ref(lastConditional) = qi::_1] - > -unicode::no_case[qi::lit("mod:") | qi::lit("regex:")] - > (charString - > pluginMessages) [phoenix::bind(&LegacyMasterlistGrammar::MakePlugin, this, qi::_val, qi::_1, qi::_2)] - ; - - pluginMessages %= - ( - +qi::eol - >> pluginMessage % +qi::eol - ) | qi::eps [qi::_1 = noMessages]; - - pluginMessage = - (conditionals [phoenix::ref(lastConditional) = qi::_1] - >> messageKeyword - >> ':' - >> charString) [phoenix::bind(&LegacyMasterlistGrammar::MakeMessage, this, qi::_val, qi::_1, qi::_2, qi::_3)] //The double >> matters. A single > doesn't work. - ; - - charString %= qi::lexeme[+(unicode::char_ - qi::eol)]; //String, with no skipper. - - messageKeyword %= unicode::no_case[masterlistMsgKey]; - - conditionals = - ( - conditional [qi::_val = qi::_1] - > *((andOr > conditional) [qi::_val += qi::_1 + qi::_2]) - ) - | unicode::no_case[unicode::string("else")][phoenix::bind(&LegacyMasterlistGrammar::InvertCondition, this, qi::_val, phoenix::ref(lastConditional))] - | qi::eps [qi::_val = ""]; - - andOr = - qi::lit("&&") [qi::_val = " and "] - | qi::lit("||") [qi::_val = " or "]; - - conditional = - (ifIfnot - > functCondition) [phoenix::bind(&LegacyMasterlistGrammar::ConvertVarCondition, this, qi::_val, qi::_1, qi::_2)]; - - ifIfnot %= - unicode::no_case[ - unicode::string("ifnot")[qi::_val = "ifnot "] - | unicode::string("if") [qi::_val = "if "] - ]; - - functCondition %= - ( - unicode::no_case[unicode::string("var")][qi::_1 = "var"] > unicode::char_('(') > variable > unicode::char_(')') //Variable condition. - ) | ( - unicode::no_case[unicode::string("file")][qi::_1 = "file"] > unicode::char_('(') > file > unicode::char_(')') //File condition. - ) | ( - unicode::no_case[unicode::string("checksum")][qi::_1 = "checksum"] > unicode::char_('(') > file > unicode::char_(',') > checksum > unicode::char_(')') //Checksum condition. - ) | ( - unicode::no_case[unicode::string("version")][qi::_1 = "version"] > unicode::char_('(') > file > unicode::char_(',') > version > unicode::char_(',') > comparator > unicode::char_(')') //Version condition. - ) | ( - unicode::no_case[unicode::string("regex")][qi::_1 = "regex"] > unicode::char_('(') > regex > unicode::char_(')') //Regex condition. - ) | ( - unicode::no_case[unicode::string("active")][qi::_1 = "active"] > unicode::char_('(') > file > unicode::char_(')') //Active condition. - ) | ( - unicode::no_case[unicode::string("lang")][qi::_1 = "lang"] > unicode::char_('(') > language > unicode::char_(')') //Language condition. - ) - ; - - variable %= +(unicode::char_ - (')' | qi::eol)); - - file %= - qi::lit("OBSE") [qi::_val = "..\\obse_1_2_416.dll"] - | qi::lit("FOSE") [qi::_val = "..\\fose_loader.dll"] - | qi::lit("NVSE") [qi::_val = "..\\nvse_loader.dll"] - | qi::lit("SKSE") [qi::_val = "..\\skse_loader.dll"] - | qi::lit("MWSE") [qi::_val = "..\\MWSE.dll"] - | qi::lit("TES3") [qi::_val = "..\\Morrowind.exe"] - | qi::lit("TES4") [qi::_val = "..\\Oblivion.exe"] - | qi::lit("TES5") [qi::_val = "..\\TESV.exe"] - | qi::lit("FO3") [qi::_val = "..\\Fallout3.exe"] - | qi::lit("FONV") [qi::_val = "..\\FalloutNV.exe"] - | - ( - qi::lexeme[unicode::char_('"') > +(unicode::char_ - ('"' | qi::eol)) > unicode::char_('"')] - ) - ; - - checksum %= +unicode::xdigit; - - version %= file; - - comparator %= - unicode::string("=") // [qi::_val = "~="] - | unicode::string(">") - | unicode::string("<") - ; - - regex %= file; - - language %= file; - - - modList.name("modList"); - listVar.name("listVar"); - listPlugin.name("listPlugin"); - pluginMessages.name("pluginMessages"); - pluginMessage.name("pluginMessage"); - charString.name("charString"); - messageKeyword.name("messageKeyword"); - conditionals.name("conditional"); - andOr.name("andOr"); - conditional.name("conditional"); - ifIfnot.name("ifIfnot"); - functCondition.name("condition"); - variable.name("variable"); - file.name("file"); - checksum.name("checksum"); - version.name("version"); - comparator.name("comparator"); - regex.name("regex file"); - file.name("language"); - - qi::on_error(modList, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(listVar, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(listPlugin, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(pluginMessages, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(pluginMessage, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(charString, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(messageKeyword, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(conditionals, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(andOr, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(conditional, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(ifIfnot, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(functCondition, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(variable, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(file, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(checksum, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(version, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(comparator, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(regex, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - qi::on_error(language, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); - } - - void SetGlobalMessageBuffer(std::list * buffer) { - globalMessageBuffer = buffer; - } - private: - qi::rule(), Skipper > modList; - qi::rule > listVar, groupLine; - qi::rule > listPlugin; - qi::rule(), Skipper > pluginMessages; - qi::rule > globalMessage, pluginMessage; - qi::rule messageKeyword; - qi::rule > charString, andOr, conditional, conditionals, ifIfnot, functCondition, variable, file, checksum, version, comparator, regex, language; - - std::map varConditionMap; - std::string lastConditional; - std::list * globalMessageBuffer; - - void StoreGlobalMessage(Message& message) { - if (globalMessageBuffer != NULL) - globalMessageBuffer->push_back(message); - } - - void RecordVarCondition(std::string& condition, std::string& var) { - varConditionMap.insert(std::pair(boost::to_lower_copy(var), condition)); - } - - bool IsPosInQuotedText(const std::string& text, size_t pos) { - std::string::const_iterator last = text.begin(); - std::advance(last, pos); - - return (std::count(text.begin(), last, '"') % 2); - } - - void InvertCondition(std::string& output, const std::string& input) { - //Want to swap all the "if" and "ifnot" instances that occur outside of quoted strings (in case a filename contains them). - - output = input; - - size_t pos; - while (pos = output.find("if", pos) != std::string::npos) { - - if (IsPosInQuotedText(output, pos)) { - pos += 2; - continue; - } - - if (output.substr(pos, 5) == "ifnot") - output.erase(pos + 2, 3); - else - output.insert(pos + 2, "not"); - - pos += 2; - } - - //Also want to swap all instances of "or" and "and" outside of quoted strings. - pos = 0; - while (pos < output.length()) { - size_t pos1 = output.find("and", pos); - size_t pos2 = output.find("or", pos); - - if (pos1 == std::string::npos && pos2 == std::string::npos) - break; - - if (pos2 == std::string::npos || pos1 < pos2) - pos = pos1; - else if (pos1 == std::string::npos || pos2 < pos1) - pos = pos2; - - if (IsPosInQuotedText(output, pos)) { - pos += 3; - continue; - } - - if (pos == pos1) { - output.erase(pos, 3); - output.insert(pos, "or"); - } else { - output.erase(pos, 2); - output.insert(pos, "and"); - } - - pos += 3; - } - - } - - void ConvertVarCondition(std::string& output, std::string& ifIfnotStr, std::string& function) { - - if (function.find("var(") == std::string::npos) { - output = ifIfnotStr + function; - return; - } - - size_t pos1 = function.find("var(") + 4; - size_t pos2 = function.find(')', pos1); - - std::string var = function.substr(pos1, pos2 - pos1); - - std::map::const_iterator it = varConditionMap.find(boost::to_lower_copy(var)); - - if (it == varConditionMap.end()) - throw boss::error(boss::error::condition_eval_fail, "The variable \"" + var + "\" was not previously defined."); - - if (ifIfnotStr == "if ") - output = it->second; - else { - if (!it->second.empty()) - InvertCondition(output, it->second); - } - } - - void ConvertCondition(std::string& condition) { - /*Convert the v2 order of evaluation to the standard order. - The only consistent way to do this is to bracket everything, - which is supported by the v3 syntax. */ - - int bracketNo = 0; - size_t pos; - - while ((pos = condition.find(" and ", pos)) != std::string::npos) { - - if (IsPosInQuotedText(condition, pos)) { - pos += 7; - continue; - } - - condition.insert(pos, " )"); - bracketNo++; - pos += 7; - - } - - pos = 0; - while ((pos = condition.find(" or ", pos)) != std::string::npos) { - - if (IsPosInQuotedText(condition, pos)) { - pos += 6; - continue; - } - - condition.insert(pos, " )"); - bracketNo++; - pos += 6; - } - - for (int i=0; i < bracketNo; ++i) { - condition.insert(0, "( "); - } - - //Convert "if" and "ifnot" to "" and "not". - - while ((pos = condition.find("if ")) != std::string::npos) { - - if (IsPosInQuotedText(condition, pos)) - continue; - - condition.erase(pos, 3); - - } - - while ((pos = condition.find("ifnot ")) != std::string::npos) { - - if (IsPosInQuotedText(condition, pos)) - continue; - - condition.erase(pos, 2); - } - - //Also convert the "=" comparator in version conditions to "==". For some reason doing this in-parser causes most of the version condition to disappear... - - pos = condition.find("version("); - if (pos != std::string::npos) { - pos = condition.find(",=)", pos); - - if (pos != std::string::npos) - condition.insert(pos + 1, "="); - } - } - - void MakePlugin(Plugin& plugin, std::string& name, std::list& messages) { - plugin.Name(boost::algorithm::trim_copy(name)); - - //Need to remove any Bash Tag suggestion messages and apply them properly. - std::set tags; - std::list::iterator it = messages.begin(); - while (it != messages.end()) { - - if (it->Type() == g_message_tag) { - std::string message = it->ChooseContent(g_lang_any).Str(); - - std::string addedList, removedList; - size_t pos1, pos2 = std::string::npos; - - pos1 = message.find("{{BASH:"); - if (pos1 != std::string::npos) - pos2 = message.find("}}", pos1); - if (pos2 != std::string::npos) - addedList = message.substr(pos1 + 7, pos2 - pos1 - 7); - - pos1 = message.find("["); - pos2 = std::string::npos; - if (pos1 != std::string::npos) - pos2 = message.find("]", pos1); - if (pos2 != std::string::npos) - removedList = message.substr(pos1+1,pos2-pos1-1); - - if (!addedList.empty()) { - //Now we move through the list of added Tags. - //Search the set of already added Tags for each one and only add if it's not found. - std::string name; - pos1 = 0; - pos2 = addedList.find(",", pos1); - while (pos2 != std::string::npos) { - name = boost::algorithm::trim_copy(addedList.substr(pos1,pos2-pos1)); - if (tags.find(Tag(name)) == tags.end()) - tags.insert(Tag(name, true, it->Condition())); - pos1 = pos2+1; - pos2 = addedList.find(",", pos1); - } - name = boost::algorithm::trim_copy(addedList.substr(pos1)); - if (tags.find(Tag(name)) == tags.end()) - tags.insert(Tag(name, true, it->Condition())); - } - - if (!removedList.empty()) { - std::string name; - pos1 = 0; - pos2 = removedList.find(",", pos1); - while (pos2 != std::string::npos) { - name = boost::algorithm::trim_copy(removedList.substr(pos1,pos2-pos1)); - if (tags.find(Tag(name, false)) == tags.end()) - tags.insert(Tag(name, false, it->Condition())); - pos1 = pos2+1; - pos2 = removedList.find(",", pos1); - } - name = boost::algorithm::trim_copy(removedList.substr(pos1)); - if (tags.find(Tag(name, false)) == tags.end()) - tags.insert(Tag(name, false, it->Condition())); - } - - it = messages.erase(it); - } else - ++it; - } - - plugin.Messages(messages); - plugin.Tags(tags); - } - - void MakeMessage(Message& message, std::string& condition, unsigned int type, std::string& content) { - - //First check if the message has a language condition. If it does, remove it and use it to set the language of the message content. - - //Just in case there are multiple language conditions, erase them all and use the last. - std::string lang; - size_t pos1; - while ((pos1 = condition.find("lang(")) != std::string::npos) { - - if (IsPosInQuotedText(condition, pos1)) { - pos1 += 5; - continue; - } - - size_t pos2 = condition.find(')', pos1); - - lang = condition.substr(pos1 + 6, pos2 - 1 - (pos1 + 6)); - condition.erase(pos1, pos2 + 1 - pos1); - - //Also need to trim any leftover "and", "or", "if" and "ifnot". - - //First erase "if" and "ifnot". - if (pos1 >= 6) { - if (condition.substr(pos1 - 6, 5) == "ifnot") { - condition.erase(pos1 - 6, 6); - pos1 -= 6; - } else if (condition.substr(pos1 - 3, 2) == "if") { - condition.erase(pos1 - 3, 3); - pos1 -= 3; - } - } else if (pos1 == 3) { //First "if" condition. - condition.erase(0, 3); - pos1 = 0; - } - - //Now look immediately before the erased conditional for "and" and "or" and erase any found. If the conditional is the first, then do the same for any "and" and "or" immediately after it. - - if (pos1 == 0) { - if (condition.substr(pos1, 5) == " and ") - condition.erase(pos1, 5); - else if (condition.substr(pos1, 4) == " or ") - condition.erase(pos1, 4); - } else { - - assert(pos1 > 5); //Shouldn't be anything <= 5 here, but just to be safe. - if (condition.substr(pos1 - 5, 5) == " and ") - condition.erase(pos1 - 5, 5); - else if (condition.substr(pos1 - 4, 4) == " or ") - condition.erase(pos1 - 4, 4); - } - } - - //BOSSv2 supports Chinese and German in addition to English, Russian and Spanish, but they aren't used in any of the masterlists so don't bother mapping them. - unsigned int langInt; - if (lang == "english" || lang == "chinese" || lang == "german") - langInt = g_lang_english; - else if (lang == "russian") - langInt = g_lang_russian; - else if (lang == "spanish") - langInt = g_lang_spanish; - else - langInt = g_lang_any; - - - /* Now convert the temporary message types into say or warning messages as appropriate. Translations were obtained from the BOSSv2 translation files. Thanks to the translators for them. */ - - if (type == message_dirty) { - - std::string search; - std::string opener; - if (langInt == g_lang_english || langInt == g_lang_any) { - search = "do not clean"; - opener = "Contains dirty edits: "; - } else if (langInt == g_lang_spanish) { - search = "no se limpia"; - opener = "Contiene ediciones sucia: "; - } else if (langInt == g_lang_russian) { - search = "Не очищать"; - opener = "\"Грязные\" правки: "; - } - - if (boost::icontains(content, search)) - type = g_message_say; - else - type = g_message_warn; - - content = opener + content; - } else if (type == message_req) { - type = g_message_say; - - std::string opener; - if (langInt == g_lang_english || langInt == g_lang_any) - opener = "Requires: "; - else if (langInt == g_lang_spanish) - opener = "Requiere: "; - else if (langInt == g_lang_russian) - opener = "Требуется: "; - - content = opener + content; - } else if (type == message_inc) { - type = g_message_say; - - std::string opener; - if (langInt == g_lang_english || langInt == g_lang_any) - opener = "Incompatible with: "; - else if (langInt == g_lang_spanish) - opener = "Incompatible con: "; - else if (langInt == g_lang_russian) - opener = "Несовместим с: "; - - content = opener + content; - } - - std::vector mc_vec; - mc_vec.push_back(MessageContent(content, langInt)); - - ConvertCondition(condition); - - message = Message(type, mc_vec, condition); - } - - //Parser error reporter. - void SyntaxError(Iter const& /*first*/, Iter const& last, Iter const& errorpos, boost::spirit::info const& what) { - std::string context(errorpos, min(errorpos +50, last)); - boost::trim(context); - - throw boss::error(boss::error::condition_eval_fail, "Error parsing at \"" + context + "\", expected \"" + what.tag + "\""); - } - }; - - void Loadv2Masterlist(boost::filesystem::path& file, std::list& plugins, std::list& globalMessages) { - Skipper skipper; - LegacyMasterlistGrammar grammar; - std::string::const_iterator begin, end; - std::string contents; - - grammar.SetGlobalMessageBuffer(&globalMessages); - - boss::ifstream ifile(file); - ifile.unsetf(std::ios::skipws); // No white space skipping! - - std::copy( - std::istream_iterator(ifile), - std::istream_iterator(), - std::back_inserter(contents) - ); - - begin = contents.begin(); - end = contents.end(); - - bool r = phrase_parse(begin, end, grammar, skipper, plugins); - - if (!r || begin != end) - throw boss::error(boss::error::path_read_fail, file.string()); - } -} -#endif +/* BOSS + + A plugin load order optimiser for games that use the esp/esm plugin system. + + Copyright (C) 2012-2013 WrinklyNinja + + This file is part of BOSS. + + BOSS 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 3 of + the License, or (at your option) any later version. + + BOSS 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 BOSS. If not, see + . +*/ + +#ifndef __BOSS_LEGACY_PARSER__ +#define __BOSS_LEGACY_PARSER__ + +// This file holds the code necessary for inputting a v2 masterlist and getting a list of plugins and a list of global messages from it. + +#ifndef BOOST_SPIRIT_UNICODE +#define BOOST_SPIRIT_UNICODE +#endif + +#include "metadata.h" +#include "error.h" +#include "game.h" +#include "streams.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boss { + namespace qi = boost::spirit::qi; + namespace unicode = boost::spirit::unicode; + namespace phoenix = boost::phoenix; + + //Message types for those that will be converted, but need identifying in the interim. Must not conflict with the values for the global types. + unsigned int message_inc = 1000; + unsigned int message_req = 1001; + unsigned int message_dirty = 1002; + + + /////////////////////////////// + // Keyword structures + /////////////////////////////// + + struct masterlistMsgKey_ : qi::symbols { + masterlistMsgKey_() { + add //New Message keywords. + ("say",g_message_say) + ("tag",g_message_tag) + ("req",message_req) + ("inc", message_inc) + ("dirty",message_dirty) + ("warn",g_message_warn) + ("error",g_message_error) + ; + } + }; + + + /////////////////////////////// + //Skipper Grammar + /////////////////////////////// + + //Skipper for userlist, modlist and ini parsers. + template + class Skipper : public qi::grammar { + public: + Skipper() : Skipper::base_type(start, "skipper grammar") { + + start = + spc + | UTF8 + | CComment + | CPlusPlusComment + | iniComment + | eof; + + spc = unicode::space - qi::eol; + + UTF8 = unicode::char_("\xef") >> unicode::char_("\xbb") >> unicode::char_("\xbf"); //UTF8 BOM + + CComment = "/*" >> *(unicode::char_ - "*/") >> "*/"; + + iniComment = qi::lit("#") >> *(unicode::char_ - qi::eol); + + CPlusPlusComment = !(qi::lit("http:") | qi::lit("https:") | qi::lit("file:")) >> "//" >> *(unicode::char_ - qi::eol); + + eof = *(spc | CComment | CPlusPlusComment | qi::eol) >> qi::eoi; + } + + void SkipIniComments(const bool b) { + if (b) + iniComment = (qi::lit("#") | qi::lit(";")) >> *(unicode::char_ - qi::eol); + else + iniComment = CComment; + } + private: + qi::rule start, spc, eof, CComment, CPlusPlusComment, lineComment, iniComment, UTF8; + }; + + /////////////////////////////// + //Modlist/Masterlist Grammar + /////////////////////////////// + + //Modlist/Masterlist grammar. + template + class LegacyMasterlistGrammar : public qi::grammar(), Skipper > { + public: + LegacyMasterlistGrammar() + : LegacyMasterlistGrammar::base_type(modList, "modlist grammar") { + masterlistMsgKey_ masterlistMsgKey; + const std::list noMessages; //An empty set of messages. + globalMessageBuffer = NULL; + + modList = + *qi::eol + > + ( + listVar + | globalMessage [phoenix::bind(&LegacyMasterlistGrammar::StoreGlobalMessage, this, qi::_1)] + | groupLine + | listPlugin [phoenix::push_back(qi::_val, qi::_1)] + | qi::eps + ) % +qi::eol; + + listVar = + (conditionals [phoenix::ref(lastConditional) = qi::_1] + >> unicode::no_case[qi::lit("set")] + >> ( + ':' + > charString + )) [phoenix::bind(&LegacyMasterlistGrammar::RecordVarCondition, this, qi::_1, qi::_2)] + ; + + globalMessage = + (conditionals [phoenix::ref(lastConditional) = qi::_1] + >> unicode::no_case[qi::lit("global")] + >> ( + messageKeyword + >> ':' + >> charString + ) + )[phoenix::bind(&LegacyMasterlistGrammar::MakeMessage, this, qi::_val, qi::_1, qi::_2, qi::_3)] + ; + + groupLine = + conditionals [phoenix::ref(lastConditional) = qi::_1] + >> unicode::no_case[qi::lit("begingroup:") + | qi::lit("endgroup")] + > charString; + + listPlugin = + conditionals [phoenix::ref(lastConditional) = qi::_1] + > -unicode::no_case[qi::lit("mod:") | qi::lit("regex:")] + > (charString + > pluginMessages) [phoenix::bind(&LegacyMasterlistGrammar::MakePlugin, this, qi::_val, qi::_1, qi::_2)] + ; + + pluginMessages %= + ( + +qi::eol + >> pluginMessage % +qi::eol + ) | qi::eps [qi::_1 = noMessages]; + + pluginMessage = + (conditionals [phoenix::ref(lastConditional) = qi::_1] + >> messageKeyword + >> ':' + >> charString) [phoenix::bind(&LegacyMasterlistGrammar::MakeMessage, this, qi::_val, qi::_1, qi::_2, qi::_3)] //The double >> matters. A single > doesn't work. + ; + + charString %= qi::lexeme[+(unicode::char_ - qi::eol)]; //String, with no skipper. + + messageKeyword %= unicode::no_case[masterlistMsgKey]; + + conditionals = + ( + conditional [qi::_val = qi::_1] + > *((andOr > conditional) [qi::_val += qi::_1 + qi::_2]) + ) + | unicode::no_case[unicode::string("else")][phoenix::bind(&LegacyMasterlistGrammar::InvertCondition, this, qi::_val, phoenix::ref(lastConditional))] + | qi::eps [qi::_val = ""]; + + andOr = + qi::lit("&&") [qi::_val = " and "] + | qi::lit("||") [qi::_val = " or "]; + + conditional = + (ifIfnot + > functCondition) [phoenix::bind(&LegacyMasterlistGrammar::ConvertVarCondition, this, qi::_val, qi::_1, qi::_2)]; + + ifIfnot %= + unicode::no_case[ + unicode::string("ifnot")[qi::_val = "ifnot "] + | unicode::string("if") [qi::_val = "if "] + ]; + + functCondition %= + ( + unicode::no_case[unicode::string("var")][qi::_1 = "var"] > unicode::char_('(') > variable > unicode::char_(')') //Variable condition. + ) | ( + unicode::no_case[unicode::string("file")][qi::_1 = "file"] > unicode::char_('(') > file > unicode::char_(')') //File condition. + ) | ( + unicode::no_case[unicode::string("checksum")][qi::_1 = "checksum"] > unicode::char_('(') > file > unicode::char_(',') > checksum > unicode::char_(')') //Checksum condition. + ) | ( + unicode::no_case[unicode::string("version")][qi::_1 = "version"] > unicode::char_('(') > file > unicode::char_(',') > version > unicode::char_(',') > comparator > unicode::char_(')') //Version condition. + ) | ( + unicode::no_case[unicode::string("regex")][qi::_1 = "regex"] > unicode::char_('(') > regex > unicode::char_(')') //Regex condition. + ) | ( + unicode::no_case[unicode::string("active")][qi::_1 = "active"] > unicode::char_('(') > file > unicode::char_(')') //Active condition. + ) | ( + unicode::no_case[unicode::string("lang")][qi::_1 = "lang"] > unicode::char_('(') > language > unicode::char_(')') //Language condition. + ) + ; + + variable %= +(unicode::char_ - (')' | qi::eol)); + + file %= + qi::lit("OBSE") [qi::_val = "..\\obse_1_2_416.dll"] + | qi::lit("FOSE") [qi::_val = "..\\fose_loader.dll"] + | qi::lit("NVSE") [qi::_val = "..\\nvse_loader.dll"] + | qi::lit("SKSE") [qi::_val = "..\\skse_loader.dll"] + | qi::lit("MWSE") [qi::_val = "..\\MWSE.dll"] + | qi::lit("TES3") [qi::_val = "..\\Morrowind.exe"] + | qi::lit("TES4") [qi::_val = "..\\Oblivion.exe"] + | qi::lit("TES5") [qi::_val = "..\\TESV.exe"] + | qi::lit("FO3") [qi::_val = "..\\Fallout3.exe"] + | qi::lit("FONV") [qi::_val = "..\\FalloutNV.exe"] + | + ( + qi::lexeme[unicode::char_('"') > +(unicode::char_ - ('"' | qi::eol)) > unicode::char_('"')] + ) + ; + + checksum %= +unicode::xdigit; + + version %= file; + + comparator %= + unicode::string("=") // [qi::_val = "~="] + | unicode::string(">") + | unicode::string("<") + ; + + regex %= file; + + language %= file; + + + modList.name("modList"); + listVar.name("listVar"); + listPlugin.name("listPlugin"); + pluginMessages.name("pluginMessages"); + pluginMessage.name("pluginMessage"); + charString.name("charString"); + messageKeyword.name("messageKeyword"); + conditionals.name("conditional"); + andOr.name("andOr"); + conditional.name("conditional"); + ifIfnot.name("ifIfnot"); + functCondition.name("condition"); + variable.name("variable"); + file.name("file"); + checksum.name("checksum"); + version.name("version"); + comparator.name("comparator"); + regex.name("regex file"); + file.name("language"); + + qi::on_error(modList, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(listVar, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(listPlugin, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(pluginMessages, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(pluginMessage, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(charString, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(messageKeyword, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(conditionals, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(andOr, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(conditional, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(ifIfnot, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(functCondition, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(variable, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(file, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(checksum, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(version, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(comparator, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(regex, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + qi::on_error(language, phoenix::bind(&LegacyMasterlistGrammar::SyntaxError, this, qi::_1, qi::_2, qi::_3, qi::_4)); + } + + void SetGlobalMessageBuffer(std::list * buffer) { + globalMessageBuffer = buffer; + } + private: + qi::rule(), Skipper > modList; + qi::rule > listVar, groupLine; + qi::rule > listPlugin; + qi::rule(), Skipper > pluginMessages; + qi::rule > globalMessage, pluginMessage; + qi::rule messageKeyword; + qi::rule > charString, andOr, conditional, conditionals, ifIfnot, functCondition, variable, file, checksum, version, comparator, regex, language; + + std::map varConditionMap; + std::string lastConditional; + std::list * globalMessageBuffer; + + void StoreGlobalMessage(Message& message) { + if (globalMessageBuffer != NULL) + globalMessageBuffer->push_back(message); + } + + void RecordVarCondition(std::string& condition, std::string& var) { + varConditionMap.insert(std::pair(boost::to_lower_copy(var), condition)); + } + + bool IsPosInQuotedText(const std::string& text, size_t pos) { + std::string::const_iterator last = text.begin(); + std::advance(last, pos); + + return (std::count(text.begin(), last, '"') % 2); + } + + void InvertCondition(std::string& output, const std::string& input) { + //Want to swap all the "if" and "ifnot" instances that occur outside of quoted strings (in case a filename contains them). + + output = input; + + size_t pos; + while (pos = output.find("if", pos) != std::string::npos) { + + if (IsPosInQuotedText(output, pos)) { + pos += 2; + continue; + } + + if (output.substr(pos, 5) == "ifnot") + output.erase(pos + 2, 3); + else + output.insert(pos + 2, "not"); + + pos += 2; + } + + //Also want to swap all instances of "or" and "and" outside of quoted strings. + pos = 0; + while (pos < output.length()) { + size_t pos1 = output.find("and", pos); + size_t pos2 = output.find("or", pos); + + if (pos1 == std::string::npos && pos2 == std::string::npos) + break; + + if (pos2 == std::string::npos || pos1 < pos2) + pos = pos1; + else if (pos1 == std::string::npos || pos2 < pos1) + pos = pos2; + + if (IsPosInQuotedText(output, pos)) { + pos += 3; + continue; + } + + if (pos == pos1) { + output.erase(pos, 3); + output.insert(pos, "or"); + } else { + output.erase(pos, 2); + output.insert(pos, "and"); + } + + pos += 3; + } + + } + + void ConvertVarCondition(std::string& output, std::string& ifIfnotStr, std::string& function) { + + if (function.find("var(") == std::string::npos) { + output = ifIfnotStr + function; + return; + } + + size_t pos1 = function.find("var(") + 4; + size_t pos2 = function.find(')', pos1); + + std::string var = function.substr(pos1, pos2 - pos1); + + std::map::const_iterator it = varConditionMap.find(boost::to_lower_copy(var)); + + if (it == varConditionMap.end()) + throw boss::error(boss::error::condition_eval_fail, "The variable \"" + var + "\" was not previously defined."); + + if (ifIfnotStr == "if ") + output = it->second; + else { + if (!it->second.empty()) + InvertCondition(output, it->second); + } + } + + void ConvertCondition(std::string& condition) { + /*Convert the v2 order of evaluation to the standard order. + The only consistent way to do this is to bracket everything, + which is supported by the v3 syntax. */ + + int bracketNo = 0; + size_t pos; + + while ((pos = condition.find(" and ", pos)) != std::string::npos) { + + if (IsPosInQuotedText(condition, pos)) { + pos += 7; + continue; + } + + condition.insert(pos, " )"); + bracketNo++; + pos += 7; + + } + + pos = 0; + while ((pos = condition.find(" or ", pos)) != std::string::npos) { + + if (IsPosInQuotedText(condition, pos)) { + pos += 6; + continue; + } + + condition.insert(pos, " )"); + bracketNo++; + pos += 6; + } + + for (int i=0; i < bracketNo; ++i) { + condition.insert(0, "( "); + } + + //Convert "if" and "ifnot" to "" and "not". + + while ((pos = condition.find("if ")) != std::string::npos) { + + if (IsPosInQuotedText(condition, pos)) + continue; + + condition.erase(pos, 3); + + } + + while ((pos = condition.find("ifnot ")) != std::string::npos) { + + if (IsPosInQuotedText(condition, pos)) + continue; + + condition.erase(pos, 2); + } + + //Also convert the "=" comparator in version conditions to "==". For some reason doing this in-parser causes most of the version condition to disappear... + + pos = condition.find("version("); + if (pos != std::string::npos) { + pos = condition.find(",=)", pos); + + if (pos != std::string::npos) + condition.insert(pos + 1, "="); + } + } + + void MakePlugin(Plugin& plugin, std::string& name, std::list& messages) { + plugin.Name(boost::algorithm::trim_copy(name)); + + //Need to remove any Bash Tag suggestion messages and apply them properly. + std::set tags; + std::list::iterator it = messages.begin(); + while (it != messages.end()) { + + if (it->Type() == g_message_tag) { + std::string message = it->ChooseContent(g_lang_any).Str(); + + std::string addedList, removedList; + size_t pos1, pos2 = std::string::npos; + + pos1 = message.find("{{BASH:"); + if (pos1 != std::string::npos) + pos2 = message.find("}}", pos1); + if (pos2 != std::string::npos) + addedList = message.substr(pos1 + 7, pos2 - pos1 - 7); + + pos1 = message.find("["); + pos2 = std::string::npos; + if (pos1 != std::string::npos) + pos2 = message.find("]", pos1); + if (pos2 != std::string::npos) + removedList = message.substr(pos1+1,pos2-pos1-1); + + if (!addedList.empty()) { + //Now we move through the list of added Tags. + //Search the set of already added Tags for each one and only add if it's not found. + std::string name; + pos1 = 0; + pos2 = addedList.find(",", pos1); + while (pos2 != std::string::npos) { + name = boost::algorithm::trim_copy(addedList.substr(pos1,pos2-pos1)); + if (tags.find(Tag(name)) == tags.end()) + tags.insert(Tag(name, true, it->Condition())); + pos1 = pos2+1; + pos2 = addedList.find(",", pos1); + } + name = boost::algorithm::trim_copy(addedList.substr(pos1)); + if (tags.find(Tag(name)) == tags.end()) + tags.insert(Tag(name, true, it->Condition())); + } + + if (!removedList.empty()) { + std::string name; + pos1 = 0; + pos2 = removedList.find(",", pos1); + while (pos2 != std::string::npos) { + name = boost::algorithm::trim_copy(removedList.substr(pos1,pos2-pos1)); + if (tags.find(Tag(name, false)) == tags.end()) + tags.insert(Tag(name, false, it->Condition())); + pos1 = pos2+1; + pos2 = removedList.find(",", pos1); + } + name = boost::algorithm::trim_copy(removedList.substr(pos1)); + if (tags.find(Tag(name, false)) == tags.end()) + tags.insert(Tag(name, false, it->Condition())); + } + + it = messages.erase(it); + } else + ++it; + } + + plugin.Messages(messages); + plugin.Tags(tags); + } + + void MakeMessage(Message& message, std::string& condition, unsigned int type, std::string& content) { + + //First check if the message has a language condition. If it does, remove it and use it to set the language of the message content. + + //Just in case there are multiple language conditions, erase them all and use the last. + std::string lang; + size_t pos1; + while ((pos1 = condition.find("lang(")) != std::string::npos) { + + if (IsPosInQuotedText(condition, pos1)) { + pos1 += 5; + continue; + } + + size_t pos2 = condition.find(')', pos1); + + lang = condition.substr(pos1 + 6, pos2 - 1 - (pos1 + 6)); + condition.erase(pos1, pos2 + 1 - pos1); + + //Also need to trim any leftover "and", "or", "if" and "ifnot". + + //First erase "if" and "ifnot". + if (pos1 >= 6) { + if (condition.substr(pos1 - 6, 5) == "ifnot") { + condition.erase(pos1 - 6, 6); + pos1 -= 6; + } else if (condition.substr(pos1 - 3, 2) == "if") { + condition.erase(pos1 - 3, 3); + pos1 -= 3; + } + } else if (pos1 == 3) { //First "if" condition. + condition.erase(0, 3); + pos1 = 0; + } + + //Now look immediately before the erased conditional for "and" and "or" and erase any found. If the conditional is the first, then do the same for any "and" and "or" immediately after it. + + if (pos1 == 0) { + if (condition.substr(pos1, 5) == " and ") + condition.erase(pos1, 5); + else if (condition.substr(pos1, 4) == " or ") + condition.erase(pos1, 4); + } else { + + assert(pos1 > 5); //Shouldn't be anything <= 5 here, but just to be safe. + if (condition.substr(pos1 - 5, 5) == " and ") + condition.erase(pos1 - 5, 5); + else if (condition.substr(pos1 - 4, 4) == " or ") + condition.erase(pos1 - 4, 4); + } + } + + //BOSSv2 supports Chinese and German in addition to English, Russian and Spanish, but they aren't used in any of the masterlists so don't bother mapping them. + unsigned int langInt; + if (lang == "english" || lang == "chinese" || lang == "german") + langInt = g_lang_english; + else if (lang == "russian") + langInt = g_lang_russian; + else if (lang == "spanish") + langInt = g_lang_spanish; + else + langInt = g_lang_any; + + + /* Now convert the temporary message types into say or warning messages as appropriate. Translations were obtained from the BOSSv2 translation files. Thanks to the translators for them. */ + + if (type == message_dirty) { + + std::string search; + std::string opener; + if (langInt == g_lang_english || langInt == g_lang_any) { + search = "do not clean"; + opener = "Contains dirty edits: "; + } else if (langInt == g_lang_spanish) { + search = "no se limpia"; + opener = "Contiene ediciones sucia: "; + } else if (langInt == g_lang_russian) { + search = "Не очищать"; + opener = "\"Грязные\" правки: "; + } + + if (boost::icontains(content, search)) + type = g_message_say; + else + type = g_message_warn; + + content = opener + content; + } else if (type == message_req) { + type = g_message_say; + + std::string opener; + if (langInt == g_lang_english || langInt == g_lang_any) + opener = "Requires: "; + else if (langInt == g_lang_spanish) + opener = "Requiere: "; + else if (langInt == g_lang_russian) + opener = "Требуется: "; + + content = opener + content; + } else if (type == message_inc) { + type = g_message_say; + + std::string opener; + if (langInt == g_lang_english || langInt == g_lang_any) + opener = "Incompatible with: "; + else if (langInt == g_lang_spanish) + opener = "Incompatible con: "; + else if (langInt == g_lang_russian) + opener = "Несовместим с: "; + + content = opener + content; + } + + std::vector mc_vec; + mc_vec.push_back(MessageContent(content, langInt)); + + ConvertCondition(condition); + + message = Message(type, mc_vec, condition); + } + + //Parser error reporter. + void SyntaxError(Iter const& /*first*/, Iter const& last, Iter const& errorpos, boost::spirit::info const& what) { + std::string context(errorpos, min(errorpos +50, last)); + boost::trim(context); + + throw boss::error(boss::error::condition_eval_fail, "Error parsing at \"" + context + "\", expected \"" + what.tag + "\""); + } + }; + + void Loadv2Masterlist(boost::filesystem::path& file, std::list& plugins, std::list& globalMessages) { + Skipper skipper; + LegacyMasterlistGrammar grammar; + std::string::const_iterator begin, end; + std::string contents; + + grammar.SetGlobalMessageBuffer(&globalMessages); + + boss::ifstream ifile(file); + ifile.unsetf(std::ios::skipws); // No white space skipping! + + std::copy( + std::istream_iterator(ifile), + std::istream_iterator(), + std::back_inserter(contents) + ); + + begin = contents.begin(); + end = contents.end(); + + bool r = phrase_parse(begin, end, grammar, skipper, plugins); + + if (!r || begin != end) + throw boss::error(boss::error::path_read_fail, file.string()); + } +} +#endif diff --git a/src/installer.nsi b/src/installer.nsi index 3c87f99c..cbbefcd1 100644 --- a/src/installer.nsi +++ b/src/installer.nsi @@ -1,520 +1,520 @@ -;BOSS NSIS Installer Script - -;-------------------------------- -;Include NSIS files. - - !include "MUI2.nsh" - !include "LogicLib.nsh" - !include "nsDialogs.nsh" - -;-------------------------------- -; Helper Function - -Function ReplaceLineStr - Exch $R0 ; string to replace that whole line with - Exch - Exch $R1 ; string that line should start with - Exch - Exch 2 - Exch $R2 ; file - Push $R3 ; file handle - Push $R4 ; temp file - Push $R5 ; temp file handle - Push $R6 ; global - Push $R7 ; input string length - Push $R8 ; line string length - Push $R9 ; global - - StrLen $R7 $R1 - - GetTempFileName $R4 - - FileOpen $R5 $R4 w - FileOpen $R3 $R2 r - - ReadLoop: - ClearErrors - FileRead $R3 $R6 - IfErrors Done - - StrLen $R8 $R6 - StrCpy $R9 $R6 $R7 -$R8 - StrCmp $R9 $R1 0 +3 - - FileWrite $R5 "$R0$\r$\n" - Goto ReadLoop - - FileWrite $R5 $R6 - Goto ReadLoop - - Done: - - FileClose $R3 - FileClose $R5 - - SetDetailsPrint none - Delete $R2 - Rename $R4 $R2 - SetDetailsPrint both - - Pop $R9 - Pop $R8 - Pop $R7 - Pop $R6 - Pop $R5 - Pop $R4 - Pop $R3 - Pop $R2 - Pop $R1 - Pop $R0 -FunctionEnd - -;-------------------------------- -;General - - ;Name, file and version info for installer. - Name "BOSS v3.0.0" - OutFile "..\build\BOSS Installer.exe" - VIProductVersion 3.0.0.0 - - ;Request application privileges for Windows Vista/7 - RequestExecutionLevel admin - - ;Icon for installer\uninstaller - !define MUI_ICON "..\resources\icon.ico" - !define MUI_UNICON "..\resources\icon.ico" - - ; This causes an "are you sure?" message to be displayed if you try to quit the installer or uninstaller. - !define MUI_ABORTWARNING - !define MUI_UNABORTWARNING - - ;Checks that the installer's CRC is correct (means we can remove installer CRC checking from BOSS). - CRCCheck force - - ;The SOLID lzma compressor gives the best compression ratio. - SetCompressor /SOLID lzma - - ;Default install directory. - InstallDir "$PROGRAMFILES\BOSS" - InstallDirRegKey HKLM "Software\BOSS" "Installed Path" - -;-------------------------------- -;Pages - - !define MUI_CUSTOMFUNCTION_GUIINIT onGUIInit - - !insertmacro MUI_PAGE_WELCOME - !insertmacro MUI_PAGE_DIRECTORY - !insertmacro MUI_PAGE_INSTFILES - !define MUI_FINISHPAGE_NOAUTOCLOSE - !define MUI_FINISHPAGE_RUN "$INSTDIR\BOSS.exe" - !define MUI_FINISHPAGE_RUN_TEXT "$(Text_Run)" - !define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\Docs\BOSS Readme.html" - !define MUI_FINISHPAGE_SHOWREADME_TEXT "$(Text_ShowReadme)" - !insertmacro MUI_PAGE_FINISH - - !insertmacro MUI_UNPAGE_WELCOME - !insertmacro MUI_UNPAGE_COMPONENTS - !insertmacro MUI_UNPAGE_CONFIRM - !insertmacro MUI_UNPAGE_INSTFILES - -;-------------------------------- -;Languages - - !insertmacro MUI_LANGUAGE "English" - !insertmacro MUI_LANGUAGE "Russian" - ;!insertmacro MUI_LANGUAGE "German" - !insertmacro MUI_LANGUAGE "Spanish" - ;!insertmacro MUI_LANGUAGE "SimpChinese" - !insertmacro MUI_RESERVEFILE_LANGDLL - -;-------------------------------- -;English Strings - - VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "BOSS" - VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "BOSS Development Team" - VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "© 2009-2013 BOSS Development Team" - VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "Installer for BOSS 3.0.0" - VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "3.0.0" - - LangString TEXT_MESSAGEBOX ${LANG_ENGLISH} "BOSS is already installed, and must be uninstalled before continuing. $\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade." - LangString TEXT_RUN ${LANG_ENGLISH} "Run BOSS" - LangString TEXT_SHOWREADME ${LANG_ENGLISH} "View Readme" - LangString TEXT_MAIN ${LANG_ENGLISH} "All BOSS's files, minus userlists and settings file." - LangString TEXT_USERFILES ${LANG_ENGLISH} "BOSS's userlist files and settings file." - -;-------------------------------- -;Russian (Русский) Strings - - VIAddVersionKey /LANG=${LANG_RUSSIAN} "ProductName" "BOSS" - VIAddVersionKey /LANG=${LANG_RUSSIAN} "CompanyName" "BOSS Development Team" - VIAddVersionKey /LANG=${LANG_RUSSIAN} "LegalCopyright" "© 2009-2013 BOSS Development Team" - VIAddVersionKey /LANG=${LANG_RUSSIAN} "FileDescription" "Установщик для BOSS 3.0.0" - VIAddVersionKey /LANG=${LANG_RUSSIAN} "FileVersion" "3.0.0" - - LangString TEXT_MESSAGEBOX ${LANG_RUSSIAN} "BOSS уже установлен и должен быть удален перед продолжением. $\n$\nНажмите `OK` для удаления предыдущей версии или `Отмена` для отмены обновления." - LangString TEXT_RUN ${LANG_RUSSIAN} "Запустить BOSS" - LangString TEXT_SHOWREADME ${LANG_RUSSIAN} "Смотреть BOSS-Readme" - LangString TEXT_MAIN ${LANG_RUSSIAN} "Все файлы BOSS, кроме пользовательских списков и BOSS.ini" - LangString TEXT_USERFILES ${LANG_RUSSIAN} "Файлы пользовательских списков и BOSS.ini." - -;-------------------------------- -;German (Deutsch) Strings - - ;VIAddVersionKey /LANG=${LANG_GERMAN} "ProductName" "BOSS" - ;VIAddVersionKey /LANG=${LANG_GERMAN} "CompanyName" "BOSS Development Team" - ;VIAddVersionKey /LANG=${LANG_GERMAN} "LegalCopyright" "© 2009-2013 BOSS Development Team" - ;VIAddVersionKey /LANG=${LANG_GERMAN} "FileDescription" "Installer für BOSS 3.0.0" - ;VIAddVersionKey /LANG=${LANG_GERMAN} "FileVersion" "3.0.0" - - ;LangString TEXT_MESSAGEBOX ${LANG_GERMAN} "BOSS ist bereits installiert und muss deinstalliert werden, bevor fortgefahren wird. $\n$\nKlicke auf `Ok` um die vorherige Version zu entfernen oder auf `Abbrechen` um das Upgrade abzubrechen." - ;LangString TEXT_RUN ${LANG_GERMAN} "BOSS starten" - ;LangString TEXT_SHOWREADME ${LANG_GERMAN} "Readme lesen" - ;LangString TEXT_MAIN ${LANG_GERMAN} "Alle Dateien von BOSS ohne die Benutzerlisten und die BOSS.ini." - ;LangString TEXT_USERFILES ${LANG_GERMAN} "Benutzerliste von BOSS und die BOSS.ini-Datei." - -;-------------------------------- -;Spanish (castellano) Strings - - VIAddVersionKey /LANG=${LANG_SPANISH} "ProductName" "BOSS" - VIAddVersionKey /LANG=${LANG_SPANISH} "CompanyName" "BOSS Development Team" - VIAddVersionKey /LANG=${LANG_SPANISH} "LegalCopyright" "© 2009-2013 BOSS Development Team" - VIAddVersionKey /LANG=${LANG_SPANISH} "FileDescription" "El instalador para BOSS 3.0.0" - VIAddVersionKey /LANG=${LANG_SPANISH} "FileVersion" "3.0.0" - - LangString TEXT_MESSAGEBOX ${LANG_SPANISH} "BOSS está instalado, y debe ser desinstalado antes de continuar. $\n$\nPresione `OK` para eliminar la versión anterior o `Cancel` para cancelar la actualización." - LangString TEXT_RUN ${LANG_SPANISH} "Ejecutar BOSS" - LangString TEXT_SHOWREADME ${LANG_SPANISH} "Ver Léame" - LangString TEXT_MAIN ${LANG_SPANISH} "Todos los archivos de BOSS, menos BOSS.ini y listas de usuarios." - LangString TEXT_USERFILES ${LANG_SPANISH} "BOSS.ini y listas de usuarios." - -;-------------------------------- -;Simplified Chinese (简体中文) Strings - - ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "ProductName" "BOSS" - ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "CompanyName" "BOSS Development Team" - ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "LegalCopyright" "© 2009-2013 BOSS Development Team" - ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileDescription" "BOSS 3.0.0安装包" - ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileVersion" "3.0.0" - - ;LangString TEXT_MESSAGEBOX ${LANG_SIMPCHINESE} "检测到旧版BOSS,您需要先卸载旧版才能安装新版。$\n$\n单击“确定”卸载旧版本或者“取消”取消更新。" - ;LangString TEXT_RUN ${LANG_SIMPCHINESE} "运行BOSS" - ;LangString TEXT_SHOWREADME ${LANG_SIMPCHINESE} "查看说明" - ;LangString TEXT_MAIN ${LANG_SIMPCHINESE} "所有BOSS文件(除userlist和BOSS.ini)" - ;LangString TEXT_USERFILES ${LANG_SIMPCHINESE} "BOSS的userlist和BOSS.ini文件。" - -;-------------------------------- -;Initialisations - - Var InstallPath ;Path to existing BOSS install. - - Function .onInit - - !insertmacro MUI_LANGDLL_DISPLAY - - FunctionEnd - - Function onGUIInit - ; First check to see if BOSS is already installed via installer, and launch the existing uninstaller if so. - IfFileExists "$COMMONFILES\BOSS\uninstall.exe" 0 +8 - MessageBox MB_OKCANCEL|MB_ICONQUESTION "$(Text_MessageBox)" IDOK oldCont IDCANCEL oldCancel - oldCancel: - Quit - oldCont: - ExecWait '$COMMONFILES\BOSS\uninstall.exe _?=$COMMONFILES\BOSS' ;Run the uninstaller in its folder and wait until it's done. - Delete "$COMMONFILES\BOSS\uninstall.exe" - RMDir "$COMMONFILES\BOSS" - - - - ;That was the old uninstaller location, now see if the current version is already installed. - ReadRegStr $InstallPath HKLM "Software\BOSS" "Installed Path" - ${If} $InstallPath != "" - IfFileExists "$InstallPath\Uninstall.exe" 0 +8 - MessageBox MB_OKCANCEL|MB_ICONQUESTION "$(Text_MessageBox)" IDOK cont IDCANCEL cancel - cancel: - Quit - cont: - ExecWait '$InstallPath\Uninstall.exe _?=$InstallPath' ;Run the uninstaller in its folder and wait until it's done. - Delete "$InstallPath\Uninstall.exe" - RMDir "$InstallPath" - ${EndIf} - FunctionEnd - - Function un.onInit - - !insertmacro MUI_LANGDLL_DISPLAY - - FunctionEnd - -;-------------------------------- -;Installer Sections - - Section "Installer Section" - - ;Install executable. - SetOutPath "$INSTDIR" - File "..\build\BOSS.exe" - - ;Now install API DLLs. - SetOutPath "$INSTDIR\API" - File "..\build\libboss32.dll" - File "..\build\libboss64.dll" - - ;Now install readme files. - SetOutPath "$INSTDIR\docs" - File "..\docs\BOSS API Readme.html" - File "..\docs\BOSS Metadata Syntax.html" - File "..\docs\BOSS Readme.html" - File "..\docs\Licenses.txt" - - ;Now install readme images. - SetOutPath "$INSTDIR\docs\images" - File "..\docs\images\editor.png" - File "..\docs\images\main.png" - File "..\docs\images\settings.png" - File "..\docs\images\viewer-1.png" - File "..\docs\images\viewer-2.png" - - ;Install resource files. - SetOutPath "$INSTDIR\resources" - File "..\resources\libespm.yaml" - File "..\resources\polyfill.js" - File "..\resources\script.js" - File "..\resources\style.css" - - SetOutPath "$INSTDIR\resources\svn" - File "..\resources\svn\intl3_svn.dll" - File "..\resources\svn\libapr-1.dll" - File "..\resources\svn\libapriconv-1.dll" - File "..\resources\svn\libaprutil-1.dll" - File "..\resources\svn\libdb48.dll" - File "..\resources\svn\libeay32.dll" - File "..\resources\svn\libsasl.dll" - File "..\resources\svn\libsvn_client-1.dll" - File "..\resources\svn\libsvn_delta-1.dll" - File "..\resources\svn\libsvn_diff-1.dll" - File "..\resources\svn\libsvn_fs-1.dll" - File "..\resources\svn\libsvn_ra-1.dll" - File "..\resources\svn\libsvn_repos-1.dll" - File "..\resources\svn\libsvn_subr-1.dll" - File "..\resources\svn\libsvn_wc-1.dll" - File "..\resources\svn\ssleay32.dll" - File "..\resources\svn\svn.exe" - - ;Now install language files. - SetOutPath "$INSTDIR\resources\l10n\ru\LC_MESSAGES" - ;File "..\resources\l10n\ru\LC_MESSAGES\messages.mo" - ;File "..\resources\l10n\ru\LC_MESSAGES\wxstd.mo" - SetOutPath "$INSTDIR\resources\l10n\es\LC_MESSAGES" - ;File "..\resources\l10n\es\LC_MESSAGES\wxstd.mo" - ;File "..\resources\l10n\es\LC_MESSAGES\messages.mo" - SetOutPath "$INSTDIR\resources\l10n\de\LC_MESSAGES" - ;File "..\resources\l10n\de\LC_MESSAGES\wxstd.mo" - SetOutPath "$INSTDIR\resources\l10n\zh\LC_MESSAGES" - ;File "..\resources\l10n\zh\LC_MESSAGES\messages.mo" - ;File "..\resources\l10n\zh\LC_MESSAGES\wxstd.mo" - - ;Install new BOSS settings.yaml. - SetOutPath "$LOCALAPPDATA\BOSS" - File "..\resources\settings.yaml" - - ;Write language setting to settings.yaml. - StrCmp $LANGUAGE ${LANG_RUSSIAN} 0 +5 - Push "$LOCALAPPDATA\BOSS\settings.yaml" - Push "Language:" - Push "Language: rus" - Call ReplaceLineStr - StrCmp $LANGUAGE ${LANG_GERMAN} 0 +5 - Push "$LOCALAPPDATA\BOSS\settings.yaml" - Push "Language:" - Push "Language: deu" - Call ReplaceLineStr - StrCmp $LANGUAGE ${LANG_SPANISH} 0 +5 - Push "$LOCALAPPDATA\BOSS\settings.yaml" - Push "Language:" - Push "Language: spa" - Call ReplaceLineStr - StrCmp $LANGUAGE ${LANG_SIMPCHINESE} 0 +5 - Push "$LOCALAPPDATA\BOSS\settings.yaml" - Push "Language:" - Push "Language: cmn" - Call ReplaceLineStr - - ;Add Start Menu shortcuts. Set out path back to $INSTDIR otherwise the shortcuts start in the wrong place. - ;Set Shell Var Context to all so that shortcuts are installed for all users, not just admin. - SetOutPath "$INSTDIR" - SetShellVarContext all - CreateDirectory "$SMPROGRAMS\BOSS" - CreateShortCut "$SMPROGRAMS\BOSS\BOSS.lnk" "$INSTDIR\BOSS.exe" - CreateShortCut "$SMPROGRAMS\BOSS\Uninstall.lnk" "$INSTDIR\Uninstall.exe" - CreateShortCut "$SMPROGRAMS\BOSS\Readme.lnk" "$INSTDIR\Docs\BOSS Readme.html" - - - ;Store installation folder in registry key. - WriteRegStr HKLM "Software\BOSS" "Installed Path" "$INSTDIR" - ;Write registry keys for Windows' uninstaller. - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "DisplayName" "BOSS" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "UninstallString" '"$INSTDIR\Uninstall.exe"' - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "URLInfoAbout" 'http://better-oblivion-sorting-software.googlecode.com/' - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "HelpLink" 'http://better-oblivion-sorting-software.googlecode.com/' - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "Publisher" 'BOSS Development Team' - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "DisplayVersion" '3.0.0' - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "NoModify" 1 - WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "NoRepair" 1 - - ;Create uninstaller - WriteUninstaller "$INSTDIR\Uninstall.exe" - - SectionEnd - -;-------------------------------- -;Uninstaller Section - - - Section "un.BOSS" Main - - ;Remove main executables. - Delete "$INSTDIR\BOSS.exe" - - ;Remove API DLLs. - Delete "$INSTDIR\API\libboss32.dll" - Delete "$INSTDIR\API\libboss64.dll" - RMDir "$INSTDIR\API" - - ;Remove readme files. - Delete "$INSTDIR\docs\BOSS API Readme.html" - Delete "$INSTDIR\docs\BOSS Metadata Syntax.html" - Delete "$INSTDIR\docs\BOSS Readme.html" - Delete "$INSTDIR\docs\Licenses.txt" - RMDir "$INSTDIR\docs" - - ;Remove readme images. - Delete "$INSTDIR\docs\images\editor.png" - Delete "$INSTDIR\docs\images\main.png" - Delete "$INSTDIR\docs\images\settings.png" - Delete "$INSTDIR\docs\images\viewer-1.png" - Delete "$INSTDIR\docs\images\viewer-2.png" - RMDir "$INSTDIR\docs\images" - - ;Remove resource files. - Delete "$INSTDIR\resources\libespm.yaml" - Delete "$INSTDIR\resources\polyfill.js" - Delete "$INSTDIR\resources\script.js" - Delete "$INSTDIR\resources\style.css" - RMDir "$INSTDIR\resources\svn" - Delete "$INSTDIR\resources\svn\intl3_svn.dll" - Delete "$INSTDIR\resources\svn\libapr-1.dll" - Delete "$INSTDIR\resources\svn\libapriconv-1.dll" - Delete "$INSTDIR\resources\svn\libaprutil-1.dll" - Delete "$INSTDIR\resources\svn\libdb48.dll" - Delete "$INSTDIR\resources\svn\libeay32.dll" - Delete "$INSTDIR\resources\svn\libsasl.dll" - Delete "$INSTDIR\resources\svn\libsvn_client-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_delta-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_diff-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_fs-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_ra-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_repos-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_subr-1.dll" - Delete "$INSTDIR\resources\svn\libsvn_wc-1.dll" - Delete "$INSTDIR\resources\svn\ssleay32.dll" - Delete "$INSTDIR\resources\svn\svn.exe" - - ;Remove language files. - Delete "$INSTDIR\resources\l10n\ru\LC_MESSAGES\messages.mo" - Delete "$INSTDIR\resources\l10n\ru\LC_MESSAGES\wxstd.mo" - Delete "$INSTDIR\resources\l10n\es\LC_MESSAGES\messages.mo" - Delete "$INSTDIR\resources\l10n\es\LC_MESSAGES\wxstd.mo" - Delete "$INSTDIR\resources\l10n\de\LC_MESSAGES\wxstd.mo" - Delete "$INSTDIR\resources\l10n\zh\LC_MESSAGES\messages.mo" - Delete "$INSTDIR\resources\l10n\zh\LC_MESSAGES\wxstd.mo" - RMDir "$INSTDIR\resources\l10n\ru\LC_MESSAGES" - RMDir "$INSTDIR\resources\l10n\ru" - RMDir "$INSTDIR\resources\l10n\es\LC_MESSAGES" - RMDir "$INSTDIR\resources\l10n\es" - RMDir "$INSTDIR\resources\l10n\de\LC_MESSAGES" - RMDir "$INSTDIR\resources\l10n\de" - RMDir "$INSTDIR\resources\l10n\zh\LC_MESSAGES" - RMDir "$INSTDIR\resources\l10n\zh" - RMDir "$INSTDIR\resources\l10n" - RMDir "$INSTDIR\resources" - - ;Now we have to remove the files BOSS generates when it runs. - Delete "$LOCALAPPDATA\BOSS\BOSSDebugLog.txt" - ;Trying to delete a file that doesn't exist doesn't cause an error, so delete all games' files. - ;This doesn't handle user-defined games. - Delete "$LOCALAPPDATA\BOSS\Oblivion\report.html" - Delete "$LOCALAPPDATA\BOSS\Oblivion\masterlist.yaml" - Delete "$LOCALAPPDATA\BOSS\Nehrim\report.html" - Delete "$LOCALAPPDATA\BOSS\Nehrim\masterlist.yaml" - Delete "$LOCALAPPDATA\BOSS\Skyrim\report.html" - Delete "$LOCALAPPDATA\BOSS\Skyrim\masterlist.yaml" - Delete "$LOCALAPPDATA\BOSS\Fallout3\report.html" - Delete "$LOCALAPPDATA\BOSS\Fallout3\masterlist.yaml" - Delete "$LOCALAPPDATA\BOSS\FalloutNV\report.html" - Delete "$LOCALAPPDATA\BOSS\FalloutNV\masterlist.yaml" - RMDir "$LOCALAPPDATA\BOSS\Oblivion" - RMDir "$LOCALAPPDATA\BOSS\Nehrim" - RMDir "$LOCALAPPDATA\BOSS\Skyrim" - RMDir "$LOCALAPPDATA\BOSS\Fallout3" - RMDir "$LOCALAPPDATA\BOSS\FalloutNV" - RMDir "$LOCALAPPDATA\BOSS" - - ;Remove uninstaller. - Delete "$INSTDIR\Uninstall.exe" - - ;Remove install directory. - RMDir "$INSTDIR" - - ;Delete registry key. - DeleteRegKey HKLM "Software\BOSS" - - ;Delete stupid Windows created registry keys: - DeleteRegKey HKCU "Software\BOSS" - DeleteRegKey HKLM "Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" - DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\App Management\ARPCache\BOSS" - DeleteRegValue HKCR "Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR" - DeleteRegValue HKCR "Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\BOSS.exe" - DeleteRegValue HKCR "Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\Uninstall.exe" - DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR" - DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\BOSS.exe" - DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\Uninstall.exe" - DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MuiCache" "$INSTDIR" - DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MuiCache" "$INSTDIR\BOSS.exe" - DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MuiCache" "$INSTDIR\Uninstall.exe" - - ;Delete Start Menu folder. - SetShellVarContext all - RMDir /r "$SMPROGRAMS\BOSS" - - SectionEnd - - Section /o "un.User Files" UserFiles - ;The following user files are only removed if set to. - Delete "$LOCALAPPDATA\BOSS\settings.yaml" - Delete "$LOCALAPPDATA\BOSS\Oblivion\userlist.yaml" - Delete "$LOCALAPPDATA\BOSS\Nehrim\userlist.yaml" - Delete "$LOCALAPPDATA\BOSS\Skyrim\userlist.yaml" - Delete "$LOCALAPPDATA\BOSS\Fallout3\userlist.yaml" - Delete "$LOCALAPPDATA\BOSS\FalloutNV\userlist.yaml" - ;Also try removing the folders storing them, in case they are otherwise empty. - RMDir "$LOCALAPPDATA\BOSS\Oblivion" - RMDir "$LOCALAPPDATA\BOSS\Nehrim" - RMDir "$LOCALAPPDATA\BOSS\Skyrim" - RMDir "$LOCALAPPDATA\BOSS\Fallout3" - RMDir "$LOCALAPPDATA\BOSS\FalloutNV" - ;Try removing install directory. - RMDir "$LOCALAPPDATA\BOSS" - SectionEnd - -;-------------------------------- -;Languages - Description Strings - - !insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${Main} "$(Text_Main)" - !insertmacro MUI_DESCRIPTION_TEXT ${UserFiles} "$(Text_UserFiles)" - !insertmacro MUI_UNFUNCTION_DESCRIPTION_END - -;-------------------------------- +;BOSS NSIS Installer Script + +;-------------------------------- +;Include NSIS files. + + !include "MUI2.nsh" + !include "LogicLib.nsh" + !include "nsDialogs.nsh" + +;-------------------------------- +; Helper Function + +Function ReplaceLineStr + Exch $R0 ; string to replace that whole line with + Exch + Exch $R1 ; string that line should start with + Exch + Exch 2 + Exch $R2 ; file + Push $R3 ; file handle + Push $R4 ; temp file + Push $R5 ; temp file handle + Push $R6 ; global + Push $R7 ; input string length + Push $R8 ; line string length + Push $R9 ; global + + StrLen $R7 $R1 + + GetTempFileName $R4 + + FileOpen $R5 $R4 w + FileOpen $R3 $R2 r + + ReadLoop: + ClearErrors + FileRead $R3 $R6 + IfErrors Done + + StrLen $R8 $R6 + StrCpy $R9 $R6 $R7 -$R8 + StrCmp $R9 $R1 0 +3 + + FileWrite $R5 "$R0$\r$\n" + Goto ReadLoop + + FileWrite $R5 $R6 + Goto ReadLoop + + Done: + + FileClose $R3 + FileClose $R5 + + SetDetailsPrint none + Delete $R2 + Rename $R4 $R2 + SetDetailsPrint both + + Pop $R9 + Pop $R8 + Pop $R7 + Pop $R6 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 +FunctionEnd + +;-------------------------------- +;General + + ;Name, file and version info for installer. + Name "BOSS v3.0.0" + OutFile "..\build\BOSS Installer.exe" + VIProductVersion 3.0.0.0 + + ;Request application privileges for Windows Vista/7 + RequestExecutionLevel admin + + ;Icon for installer\uninstaller + !define MUI_ICON "..\resources\icon.ico" + !define MUI_UNICON "..\resources\icon.ico" + + ; This causes an "are you sure?" message to be displayed if you try to quit the installer or uninstaller. + !define MUI_ABORTWARNING + !define MUI_UNABORTWARNING + + ;Checks that the installer's CRC is correct (means we can remove installer CRC checking from BOSS). + CRCCheck force + + ;The SOLID lzma compressor gives the best compression ratio. + SetCompressor /SOLID lzma + + ;Default install directory. + InstallDir "$PROGRAMFILES\BOSS" + InstallDirRegKey HKLM "Software\BOSS" "Installed Path" + +;-------------------------------- +;Pages + + !define MUI_CUSTOMFUNCTION_GUIINIT onGUIInit + + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + !define MUI_FINISHPAGE_NOAUTOCLOSE + !define MUI_FINISHPAGE_RUN "$INSTDIR\BOSS.exe" + !define MUI_FINISHPAGE_RUN_TEXT "$(Text_Run)" + !define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\Docs\BOSS Readme.html" + !define MUI_FINISHPAGE_SHOWREADME_TEXT "$(Text_ShowReadme)" + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_WELCOME + !insertmacro MUI_UNPAGE_COMPONENTS + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + !insertmacro MUI_LANGUAGE "Russian" + ;!insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Spanish" + ;!insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_RESERVEFILE_LANGDLL + +;-------------------------------- +;English Strings + + VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "BOSS" + VIAddVersionKey /LANG=${LANG_ENGLISH} "CompanyName" "BOSS Development Team" + VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "© 2009-2013 BOSS Development Team" + VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "Installer for BOSS 3.0.0" + VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "3.0.0" + + LangString TEXT_MESSAGEBOX ${LANG_ENGLISH} "BOSS is already installed, and must be uninstalled before continuing. $\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade." + LangString TEXT_RUN ${LANG_ENGLISH} "Run BOSS" + LangString TEXT_SHOWREADME ${LANG_ENGLISH} "View Readme" + LangString TEXT_MAIN ${LANG_ENGLISH} "All BOSS's files, minus userlists and settings file." + LangString TEXT_USERFILES ${LANG_ENGLISH} "BOSS's userlist files and settings file." + +;-------------------------------- +;Russian (Русский) Strings + + VIAddVersionKey /LANG=${LANG_RUSSIAN} "ProductName" "BOSS" + VIAddVersionKey /LANG=${LANG_RUSSIAN} "CompanyName" "BOSS Development Team" + VIAddVersionKey /LANG=${LANG_RUSSIAN} "LegalCopyright" "© 2009-2013 BOSS Development Team" + VIAddVersionKey /LANG=${LANG_RUSSIAN} "FileDescription" "Установщик для BOSS 3.0.0" + VIAddVersionKey /LANG=${LANG_RUSSIAN} "FileVersion" "3.0.0" + + LangString TEXT_MESSAGEBOX ${LANG_RUSSIAN} "BOSS уже установлен и должен быть удален перед продолжением. $\n$\nНажмите `OK` для удаления предыдущей версии или `Отмена` для отмены обновления." + LangString TEXT_RUN ${LANG_RUSSIAN} "Запустить BOSS" + LangString TEXT_SHOWREADME ${LANG_RUSSIAN} "Смотреть BOSS-Readme" + LangString TEXT_MAIN ${LANG_RUSSIAN} "Все файлы BOSS, кроме пользовательских списков и BOSS.ini" + LangString TEXT_USERFILES ${LANG_RUSSIAN} "Файлы пользовательских списков и BOSS.ini." + +;-------------------------------- +;German (Deutsch) Strings + + ;VIAddVersionKey /LANG=${LANG_GERMAN} "ProductName" "BOSS" + ;VIAddVersionKey /LANG=${LANG_GERMAN} "CompanyName" "BOSS Development Team" + ;VIAddVersionKey /LANG=${LANG_GERMAN} "LegalCopyright" "© 2009-2013 BOSS Development Team" + ;VIAddVersionKey /LANG=${LANG_GERMAN} "FileDescription" "Installer für BOSS 3.0.0" + ;VIAddVersionKey /LANG=${LANG_GERMAN} "FileVersion" "3.0.0" + + ;LangString TEXT_MESSAGEBOX ${LANG_GERMAN} "BOSS ist bereits installiert und muss deinstalliert werden, bevor fortgefahren wird. $\n$\nKlicke auf `Ok` um die vorherige Version zu entfernen oder auf `Abbrechen` um das Upgrade abzubrechen." + ;LangString TEXT_RUN ${LANG_GERMAN} "BOSS starten" + ;LangString TEXT_SHOWREADME ${LANG_GERMAN} "Readme lesen" + ;LangString TEXT_MAIN ${LANG_GERMAN} "Alle Dateien von BOSS ohne die Benutzerlisten und die BOSS.ini." + ;LangString TEXT_USERFILES ${LANG_GERMAN} "Benutzerliste von BOSS und die BOSS.ini-Datei." + +;-------------------------------- +;Spanish (castellano) Strings + + VIAddVersionKey /LANG=${LANG_SPANISH} "ProductName" "BOSS" + VIAddVersionKey /LANG=${LANG_SPANISH} "CompanyName" "BOSS Development Team" + VIAddVersionKey /LANG=${LANG_SPANISH} "LegalCopyright" "© 2009-2013 BOSS Development Team" + VIAddVersionKey /LANG=${LANG_SPANISH} "FileDescription" "El instalador para BOSS 3.0.0" + VIAddVersionKey /LANG=${LANG_SPANISH} "FileVersion" "3.0.0" + + LangString TEXT_MESSAGEBOX ${LANG_SPANISH} "BOSS está instalado, y debe ser desinstalado antes de continuar. $\n$\nPresione `OK` para eliminar la versión anterior o `Cancel` para cancelar la actualización." + LangString TEXT_RUN ${LANG_SPANISH} "Ejecutar BOSS" + LangString TEXT_SHOWREADME ${LANG_SPANISH} "Ver Léame" + LangString TEXT_MAIN ${LANG_SPANISH} "Todos los archivos de BOSS, menos BOSS.ini y listas de usuarios." + LangString TEXT_USERFILES ${LANG_SPANISH} "BOSS.ini y listas de usuarios." + +;-------------------------------- +;Simplified Chinese (简体中文) Strings + + ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "ProductName" "BOSS" + ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "CompanyName" "BOSS Development Team" + ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "LegalCopyright" "© 2009-2013 BOSS Development Team" + ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileDescription" "BOSS 3.0.0安装包" + ;VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileVersion" "3.0.0" + + ;LangString TEXT_MESSAGEBOX ${LANG_SIMPCHINESE} "检测到旧版BOSS,您需要先卸载旧版才能安装新版。$\n$\n单击“确定”卸载旧版本或者“取消”取消更新。" + ;LangString TEXT_RUN ${LANG_SIMPCHINESE} "运行BOSS" + ;LangString TEXT_SHOWREADME ${LANG_SIMPCHINESE} "查看说明" + ;LangString TEXT_MAIN ${LANG_SIMPCHINESE} "所有BOSS文件(除userlist和BOSS.ini)" + ;LangString TEXT_USERFILES ${LANG_SIMPCHINESE} "BOSS的userlist和BOSS.ini文件。" + +;-------------------------------- +;Initialisations + + Var InstallPath ;Path to existing BOSS install. + + Function .onInit + + !insertmacro MUI_LANGDLL_DISPLAY + + FunctionEnd + + Function onGUIInit + ; First check to see if BOSS is already installed via installer, and launch the existing uninstaller if so. + IfFileExists "$COMMONFILES\BOSS\uninstall.exe" 0 +8 + MessageBox MB_OKCANCEL|MB_ICONQUESTION "$(Text_MessageBox)" IDOK oldCont IDCANCEL oldCancel + oldCancel: + Quit + oldCont: + ExecWait '$COMMONFILES\BOSS\uninstall.exe _?=$COMMONFILES\BOSS' ;Run the uninstaller in its folder and wait until it's done. + Delete "$COMMONFILES\BOSS\uninstall.exe" + RMDir "$COMMONFILES\BOSS" + + + + ;That was the old uninstaller location, now see if the current version is already installed. + ReadRegStr $InstallPath HKLM "Software\BOSS" "Installed Path" + ${If} $InstallPath != "" + IfFileExists "$InstallPath\Uninstall.exe" 0 +8 + MessageBox MB_OKCANCEL|MB_ICONQUESTION "$(Text_MessageBox)" IDOK cont IDCANCEL cancel + cancel: + Quit + cont: + ExecWait '$InstallPath\Uninstall.exe _?=$InstallPath' ;Run the uninstaller in its folder and wait until it's done. + Delete "$InstallPath\Uninstall.exe" + RMDir "$InstallPath" + ${EndIf} + FunctionEnd + + Function un.onInit + + !insertmacro MUI_LANGDLL_DISPLAY + + FunctionEnd + +;-------------------------------- +;Installer Sections + + Section "Installer Section" + + ;Install executable. + SetOutPath "$INSTDIR" + File "..\build\BOSS.exe" + + ;Now install API DLLs. + SetOutPath "$INSTDIR\API" + File "..\build\libboss32.dll" + File "..\build\libboss64.dll" + + ;Now install readme files. + SetOutPath "$INSTDIR\docs" + File "..\docs\BOSS API Readme.html" + File "..\docs\BOSS Metadata Syntax.html" + File "..\docs\BOSS Readme.html" + File "..\docs\Licenses.txt" + + ;Now install readme images. + SetOutPath "$INSTDIR\docs\images" + File "..\docs\images\editor.png" + File "..\docs\images\main.png" + File "..\docs\images\settings.png" + File "..\docs\images\viewer-1.png" + File "..\docs\images\viewer-2.png" + + ;Install resource files. + SetOutPath "$INSTDIR\resources" + File "..\resources\libespm.yaml" + File "..\resources\polyfill.js" + File "..\resources\script.js" + File "..\resources\style.css" + + SetOutPath "$INSTDIR\resources\svn" + File "..\resources\svn\intl3_svn.dll" + File "..\resources\svn\libapr-1.dll" + File "..\resources\svn\libapriconv-1.dll" + File "..\resources\svn\libaprutil-1.dll" + File "..\resources\svn\libdb48.dll" + File "..\resources\svn\libeay32.dll" + File "..\resources\svn\libsasl.dll" + File "..\resources\svn\libsvn_client-1.dll" + File "..\resources\svn\libsvn_delta-1.dll" + File "..\resources\svn\libsvn_diff-1.dll" + File "..\resources\svn\libsvn_fs-1.dll" + File "..\resources\svn\libsvn_ra-1.dll" + File "..\resources\svn\libsvn_repos-1.dll" + File "..\resources\svn\libsvn_subr-1.dll" + File "..\resources\svn\libsvn_wc-1.dll" + File "..\resources\svn\ssleay32.dll" + File "..\resources\svn\svn.exe" + + ;Now install language files. + SetOutPath "$INSTDIR\resources\l10n\ru\LC_MESSAGES" + ;File "..\resources\l10n\ru\LC_MESSAGES\messages.mo" + ;File "..\resources\l10n\ru\LC_MESSAGES\wxstd.mo" + SetOutPath "$INSTDIR\resources\l10n\es\LC_MESSAGES" + ;File "..\resources\l10n\es\LC_MESSAGES\wxstd.mo" + ;File "..\resources\l10n\es\LC_MESSAGES\messages.mo" + SetOutPath "$INSTDIR\resources\l10n\de\LC_MESSAGES" + ;File "..\resources\l10n\de\LC_MESSAGES\wxstd.mo" + SetOutPath "$INSTDIR\resources\l10n\zh\LC_MESSAGES" + ;File "..\resources\l10n\zh\LC_MESSAGES\messages.mo" + ;File "..\resources\l10n\zh\LC_MESSAGES\wxstd.mo" + + ;Install new BOSS settings.yaml. + SetOutPath "$LOCALAPPDATA\BOSS" + File "..\resources\settings.yaml" + + ;Write language setting to settings.yaml. + StrCmp $LANGUAGE ${LANG_RUSSIAN} 0 +5 + Push "$LOCALAPPDATA\BOSS\settings.yaml" + Push "Language:" + Push "Language: rus" + Call ReplaceLineStr + StrCmp $LANGUAGE ${LANG_GERMAN} 0 +5 + Push "$LOCALAPPDATA\BOSS\settings.yaml" + Push "Language:" + Push "Language: deu" + Call ReplaceLineStr + StrCmp $LANGUAGE ${LANG_SPANISH} 0 +5 + Push "$LOCALAPPDATA\BOSS\settings.yaml" + Push "Language:" + Push "Language: spa" + Call ReplaceLineStr + StrCmp $LANGUAGE ${LANG_SIMPCHINESE} 0 +5 + Push "$LOCALAPPDATA\BOSS\settings.yaml" + Push "Language:" + Push "Language: cmn" + Call ReplaceLineStr + + ;Add Start Menu shortcuts. Set out path back to $INSTDIR otherwise the shortcuts start in the wrong place. + ;Set Shell Var Context to all so that shortcuts are installed for all users, not just admin. + SetOutPath "$INSTDIR" + SetShellVarContext all + CreateDirectory "$SMPROGRAMS\BOSS" + CreateShortCut "$SMPROGRAMS\BOSS\BOSS.lnk" "$INSTDIR\BOSS.exe" + CreateShortCut "$SMPROGRAMS\BOSS\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + CreateShortCut "$SMPROGRAMS\BOSS\Readme.lnk" "$INSTDIR\Docs\BOSS Readme.html" + + + ;Store installation folder in registry key. + WriteRegStr HKLM "Software\BOSS" "Installed Path" "$INSTDIR" + ;Write registry keys for Windows' uninstaller. + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "DisplayName" "BOSS" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "UninstallString" '"$INSTDIR\Uninstall.exe"' + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "URLInfoAbout" 'http://better-oblivion-sorting-software.googlecode.com/' + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "HelpLink" 'http://better-oblivion-sorting-software.googlecode.com/' + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "Publisher" 'BOSS Development Team' + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "DisplayVersion" '3.0.0' + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "NoModify" 1 + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" "NoRepair" 1 + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + + SectionEnd + +;-------------------------------- +;Uninstaller Section + + + Section "un.BOSS" Main + + ;Remove main executables. + Delete "$INSTDIR\BOSS.exe" + + ;Remove API DLLs. + Delete "$INSTDIR\API\libboss32.dll" + Delete "$INSTDIR\API\libboss64.dll" + RMDir "$INSTDIR\API" + + ;Remove readme files. + Delete "$INSTDIR\docs\BOSS API Readme.html" + Delete "$INSTDIR\docs\BOSS Metadata Syntax.html" + Delete "$INSTDIR\docs\BOSS Readme.html" + Delete "$INSTDIR\docs\Licenses.txt" + RMDir "$INSTDIR\docs" + + ;Remove readme images. + Delete "$INSTDIR\docs\images\editor.png" + Delete "$INSTDIR\docs\images\main.png" + Delete "$INSTDIR\docs\images\settings.png" + Delete "$INSTDIR\docs\images\viewer-1.png" + Delete "$INSTDIR\docs\images\viewer-2.png" + RMDir "$INSTDIR\docs\images" + + ;Remove resource files. + Delete "$INSTDIR\resources\libespm.yaml" + Delete "$INSTDIR\resources\polyfill.js" + Delete "$INSTDIR\resources\script.js" + Delete "$INSTDIR\resources\style.css" + RMDir "$INSTDIR\resources\svn" + Delete "$INSTDIR\resources\svn\intl3_svn.dll" + Delete "$INSTDIR\resources\svn\libapr-1.dll" + Delete "$INSTDIR\resources\svn\libapriconv-1.dll" + Delete "$INSTDIR\resources\svn\libaprutil-1.dll" + Delete "$INSTDIR\resources\svn\libdb48.dll" + Delete "$INSTDIR\resources\svn\libeay32.dll" + Delete "$INSTDIR\resources\svn\libsasl.dll" + Delete "$INSTDIR\resources\svn\libsvn_client-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_delta-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_diff-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_fs-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_ra-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_repos-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_subr-1.dll" + Delete "$INSTDIR\resources\svn\libsvn_wc-1.dll" + Delete "$INSTDIR\resources\svn\ssleay32.dll" + Delete "$INSTDIR\resources\svn\svn.exe" + + ;Remove language files. + Delete "$INSTDIR\resources\l10n\ru\LC_MESSAGES\messages.mo" + Delete "$INSTDIR\resources\l10n\ru\LC_MESSAGES\wxstd.mo" + Delete "$INSTDIR\resources\l10n\es\LC_MESSAGES\messages.mo" + Delete "$INSTDIR\resources\l10n\es\LC_MESSAGES\wxstd.mo" + Delete "$INSTDIR\resources\l10n\de\LC_MESSAGES\wxstd.mo" + Delete "$INSTDIR\resources\l10n\zh\LC_MESSAGES\messages.mo" + Delete "$INSTDIR\resources\l10n\zh\LC_MESSAGES\wxstd.mo" + RMDir "$INSTDIR\resources\l10n\ru\LC_MESSAGES" + RMDir "$INSTDIR\resources\l10n\ru" + RMDir "$INSTDIR\resources\l10n\es\LC_MESSAGES" + RMDir "$INSTDIR\resources\l10n\es" + RMDir "$INSTDIR\resources\l10n\de\LC_MESSAGES" + RMDir "$INSTDIR\resources\l10n\de" + RMDir "$INSTDIR\resources\l10n\zh\LC_MESSAGES" + RMDir "$INSTDIR\resources\l10n\zh" + RMDir "$INSTDIR\resources\l10n" + RMDir "$INSTDIR\resources" + + ;Now we have to remove the files BOSS generates when it runs. + Delete "$LOCALAPPDATA\BOSS\BOSSDebugLog.txt" + ;Trying to delete a file that doesn't exist doesn't cause an error, so delete all games' files. + ;This doesn't handle user-defined games. + Delete "$LOCALAPPDATA\BOSS\Oblivion\report.html" + Delete "$LOCALAPPDATA\BOSS\Oblivion\masterlist.yaml" + Delete "$LOCALAPPDATA\BOSS\Nehrim\report.html" + Delete "$LOCALAPPDATA\BOSS\Nehrim\masterlist.yaml" + Delete "$LOCALAPPDATA\BOSS\Skyrim\report.html" + Delete "$LOCALAPPDATA\BOSS\Skyrim\masterlist.yaml" + Delete "$LOCALAPPDATA\BOSS\Fallout3\report.html" + Delete "$LOCALAPPDATA\BOSS\Fallout3\masterlist.yaml" + Delete "$LOCALAPPDATA\BOSS\FalloutNV\report.html" + Delete "$LOCALAPPDATA\BOSS\FalloutNV\masterlist.yaml" + RMDir "$LOCALAPPDATA\BOSS\Oblivion" + RMDir "$LOCALAPPDATA\BOSS\Nehrim" + RMDir "$LOCALAPPDATA\BOSS\Skyrim" + RMDir "$LOCALAPPDATA\BOSS\Fallout3" + RMDir "$LOCALAPPDATA\BOSS\FalloutNV" + RMDir "$LOCALAPPDATA\BOSS" + + ;Remove uninstaller. + Delete "$INSTDIR\Uninstall.exe" + + ;Remove install directory. + RMDir "$INSTDIR" + + ;Delete registry key. + DeleteRegKey HKLM "Software\BOSS" + + ;Delete stupid Windows created registry keys: + DeleteRegKey HKCU "Software\BOSS" + DeleteRegKey HKLM "Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BOSS" + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\App Management\ARPCache\BOSS" + DeleteRegValue HKCR "Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR" + DeleteRegValue HKCR "Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\BOSS.exe" + DeleteRegValue HKCR "Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\Uninstall.exe" + DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR" + DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\BOSS.exe" + DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\Uninstall.exe" + DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MuiCache" "$INSTDIR" + DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MuiCache" "$INSTDIR\BOSS.exe" + DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MuiCache" "$INSTDIR\Uninstall.exe" + + ;Delete Start Menu folder. + SetShellVarContext all + RMDir /r "$SMPROGRAMS\BOSS" + + SectionEnd + + Section /o "un.User Files" UserFiles + ;The following user files are only removed if set to. + Delete "$LOCALAPPDATA\BOSS\settings.yaml" + Delete "$LOCALAPPDATA\BOSS\Oblivion\userlist.yaml" + Delete "$LOCALAPPDATA\BOSS\Nehrim\userlist.yaml" + Delete "$LOCALAPPDATA\BOSS\Skyrim\userlist.yaml" + Delete "$LOCALAPPDATA\BOSS\Fallout3\userlist.yaml" + Delete "$LOCALAPPDATA\BOSS\FalloutNV\userlist.yaml" + ;Also try removing the folders storing them, in case they are otherwise empty. + RMDir "$LOCALAPPDATA\BOSS\Oblivion" + RMDir "$LOCALAPPDATA\BOSS\Nehrim" + RMDir "$LOCALAPPDATA\BOSS\Skyrim" + RMDir "$LOCALAPPDATA\BOSS\Fallout3" + RMDir "$LOCALAPPDATA\BOSS\FalloutNV" + ;Try removing install directory. + RMDir "$LOCALAPPDATA\BOSS" + SectionEnd + +;-------------------------------- +;Languages - Description Strings + + !insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${Main} "$(Text_Main)" + !insertmacro MUI_DESCRIPTION_TEXT ${UserFiles} "$(Text_UserFiles)" + !insertmacro MUI_UNFUNCTION_DESCRIPTION_END + +;-------------------------------- diff --git a/src/resource.rc b/src/resource.rc index af908330..c7b84973 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -1,22 +1,22 @@ -#include "wx/msw/wx.rc" -1 VERSIONINFO - FILEVERSION 3,0,0,0 - PRODUCTVERSION 3,0,0,0 - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "FileVersion", "3.0.0" - VALUE "LegalCopyright", "Copyright (C) 2013 WrinklyNinja" - VALUE "ProductVersion", "3.0.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END -MAINICON ICON "../resources/icon.ico" +#include "wx/msw/wx.rc" +1 VERSIONINFO + FILEVERSION 3,0,0,0 + PRODUCTVERSION 3,0,0,0 + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileVersion", "3.0.0" + VALUE "LegalCopyright", "Copyright (C) 2013 WrinklyNinja" + VALUE "ProductVersion", "3.0.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END +MAINICON ICON "../resources/icon.ico"