1 // SPDX-License-Identifier: GPL-2.0-or-later
5 * @brief CConfigLog implementation
13 #include "Constants.h"
14 #include "VersionInfo.h"
21 #include "Environment.h"
23 #include "OptionsMgr.h"
28 CConfigLog::CConfigLog()
29 : m_pfile(new UniStdioFile())
33 CConfigLog::~CConfigLog()
40 * @brief Get details of the Compiler Version from _MSC_VER, etc.
41 * Infer the Visual Studio version
43 static String GetCompilerVersion()
45 String sVisualStudio = _T("");
48 # error "** Unknown OLD Version of Visual Studio **"
49 #elif _MSC_VER == 1800
50 sVisualStudio = _T("VS.2013 (12.0) - ");
51 #elif _MSC_VER == 1900
52 sVisualStudio = _T("VS.2015 (14.0) - ");
53 #elif _MSC_VER == 1910
54 sVisualStudio = _T("VS.2017 (15.0) - ");
55 #elif _MSC_VER == 1911
56 sVisualStudio = _T("VS.2017 (15.3) - ");
57 #elif _MSC_VER >= 1912 && _MSC_VER < 1920
58 sVisualStudio = strutils::format(_T("VS.2017 (15.%d) - "), 5 + (_MSC_VER - 1912));
59 #elif _MSC_VER >= 1920 && _MSC_VER < 2000
60 sVisualStudio = strutils::format(_T("VS.2019 (16.%d) - "), (_MSC_VER - 1920));
61 #elif _MSC_VER >= 2000
62 # error "** Unknown NEW Version of Visual Studio **"
66 return strutils::format(_T("%sC/C++ Compiler %02i.%02i.%05i.%i"),
68 (int)(_MSC_VER / 100), (int)(_MSC_VER % 100), (int)(_MSC_FULL_VER % 100000), _MSC_BUILD
73 * @brief Get the Modified time of fully qualified file path and name
75 static String GetLastModified(const String &path)
80 CVersionInfo EXEversion;
81 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
82 sPath2 = sEXEPath + _T("\\") + sPath2;
86 String sModifiedTime = _T("");
89 Poco::Timestamp mtime(file.getLastModified());
91 const int64_t r = (mtime.epochTime());
92 sModifiedTime = locality::TimeString(&r);
98 * @brief Write plugin names
100 void CConfigLog::WritePluginsInLogFile(const wchar_t *transformationEvent)
102 CVersionInfo EXEversion;
103 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
105 // get an array with the available scripts
106 PluginArray * piPluginArray;
109 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(transformationEvent);
111 for (size_t iPlugin = 0 ; iPlugin < piPluginArray->size() ; iPlugin++)
113 const PluginInfoPtr& plugin = piPluginArray->at(iPlugin);
115 String sFileName = paths::GetLongPath(plugin->m_filepath);
116 if (sFileName.length() > sEXEPath.length())
117 if (sFileName.substr(0, sEXEPath.length()) == sEXEPath)
118 sFileName = _T(".") + sFileName.erase(0, sEXEPath.length());
120 String sModifiedTime = _T("");
121 sModifiedTime = GetLastModified(plugin->m_filepath);
122 if (!sModifiedTime.empty())
123 sModifiedTime = _T("[") + sModifiedTime + _T("]");
125 String sPluginText = strutils::format
126 (_T("\r\n %s%-36s path=%s %s"),
127 plugin->m_disabled ? _T("!") : _T(" "),
132 m_pfile->WriteString(sPluginText);
137 * @brief String wrapper around API call GetLocaleInfo
139 static String GetLocaleString(LCID locid, LCTYPE lctype)
142 if (!GetLocaleInfo(locid, lctype, buffer, sizeof(buffer)/sizeof(buffer[0])))
148 * @brief Write string item
150 void CConfigLog::WriteItem(int indent, const String& key, const TCHAR *value /*= nullptr*/)
152 String text = strutils::format(value ? _T("%*.0s%s: %s\r\n") : _T("%*.0s%s:\r\n"), indent, key, key, value);
153 m_pfile->WriteString(text);
157 * @brief Write string item
159 void CConfigLog::WriteItem(int indent, const String& key, const String &str)
161 WriteItem(indent, key, str.c_str());
165 * @brief Write int item
167 void CConfigLog::WriteItem(int indent, const String& key, long value)
169 String text = strutils::format(_T("%*.0s%s: %ld\r\n"), indent, key, key, value);
170 m_pfile->WriteString(text);
174 * @brief Write out various possibly relevant windows locale information
176 void CConfigLog::WriteLocaleSettings(unsigned locid, const String& title)
179 WriteItem(2, _T("Def ANSI codepage"), GetLocaleString(locid, LOCALE_IDEFAULTANSICODEPAGE));
180 WriteItem(2, _T("Def OEM codepage"), GetLocaleString(locid, LOCALE_IDEFAULTCODEPAGE));
181 WriteItem(2, _T("Country"), GetLocaleString(locid, LOCALE_SENGCOUNTRY));
182 WriteItem(2, _T("Language"), GetLocaleString(locid, LOCALE_SENGLANGUAGE));
183 WriteItem(2, _T("Language code"), GetLocaleString(locid, LOCALE_ILANGUAGE));
184 WriteItem(2, _T("ISO Language code"), GetLocaleString(locid, LOCALE_SISO639LANGNAME));
188 * @brief Write version of a single executable file
190 void CConfigLog::WriteVersionOf1(int indent, const String& path)
193 if (path2.find(_T(".\\")) == 0)
195 // Remove "relative path" info for Win API calls.
196 const TCHAR *pf = path2.c_str();
197 path2 = String(pf+2);
199 String name = paths::FindFileName(path2);
200 CVersionInfo vi(path2.c_str(), true);
201 String sModifiedTime = _T("");
204 sModifiedTime = GetLastModified(path);
205 if (!sModifiedTime.empty())
206 sModifiedTime = _T(" [") + sModifiedTime + _T("]");
208 String text = strutils::format
211 ? _T(" %*s%-18s %s=%u.%02u %s=%04u\r\n")
212 : _T(" %*s%-18s %s=%u.%02u %s=%04u path=%s%s\r\n"),
214 // Tilde prefix for modules currently mapped into WinMerge
215 GetModuleHandle(path2.c_str())
219 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwMajorVersion)
222 vi.m_dvi.dwMajorVersion,
223 vi.m_dvi.dwMinorVersion,
224 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwBuildNumber)
227 vi.m_dvi.dwBuildNumber,
231 m_pfile->WriteString(text);
235 * @brief Write winmerge configuration
237 void CConfigLog::WriteWinMergeConfig()
240 String tmppath = tmpfile.Create();
241 GetOptionsMgr()->ExportOptions(tmppath, true);
243 if (!ufile.OpenReadOnly(tmppath))
247 while (ufile.ReadString(line, &lossy))
249 String prefix = _T(" ");
250 if (line[0] == _T('[') )
252 FileWriteString(prefix + line + _T("\r\n"));
258 * @brief Write logfile
260 bool CConfigLog::DoFile(String &sError)
262 CVersionInfo version;
265 String sFileName = paths::ConcatPath(env::GetMyDocuments(), WinMergeDocumentsFolder);
266 paths::CreateIfNeeded(sFileName);
267 m_sFileName = paths::ConcatPath(sFileName, _T("WinMerge.txt"));
269 if (!m_pfile->OpenCreateUtf8(m_sFileName))
271 const UniFile::UniError &err = m_pfile->GetLastUniError();
272 sError = err.GetError();
275 m_pfile->SetBom(true);
279 FileWriteString(_T("WinMerge Configuration Log\r\n"));
280 FileWriteString(_T("--------------------------\r\n"));
281 FileWriteString(_T("\r\nLog Saved to: "));
282 FileWriteString(m_sFileName);
283 FileWriteString(_T("\r\n >> >> Please add this information (or attach this file) when reporting bugs << <<"));
287 FileWriteString(_T("\r\n\r\nWindows Info: "));
288 text = GetWindowsVer();
289 FileWriteString(text);
290 text = GetProcessorInfo();
293 FileWriteString(_T("\r\n Processor: "));
294 FileWriteString(text);
299 FileWriteString(_T("\r\n\r\nWinMerge Info:"));
300 String sEXEFullFileName = paths::GetLongPath(version.GetFullFileName(), false);
301 FileWriteString(_T("\r\n Code File: "));
302 FileWriteString(sEXEFullFileName);
304 FileWriteString(_T("\r\n Version: "));
305 FileWriteString(version.GetProductVersion());
306 String privBuild = version.GetPrivateBuild();
307 if (!privBuild.empty())
309 FileWriteString(_T(" + ") + privBuild);
312 FileWriteString(_T("\r\n Code File Modified: "));
313 FileWriteString(GetLastModified(sEXEFullFileName));
315 FileWriteString(_T("\r\n Build Config: "));
316 FileWriteString(GetBuildFlags());
318 FileWriteString(_T("\r\n Build Software: "));
319 FileWriteString(GetCompilerVersion());
321 LPCTSTR szCmdLine = ::GetCommandLine();
322 assert(szCmdLine != nullptr);
324 // Skip the quoted executable file name.
325 if (szCmdLine != nullptr)
327 szCmdLine = _tcschr(szCmdLine, '"');
328 if (szCmdLine != nullptr)
330 szCmdLine += 1; // skip the opening quote.
331 szCmdLine = _tcschr(szCmdLine, '"');
332 if (szCmdLine != nullptr)
334 szCmdLine += 1; // skip the closing quote.
339 // The command line include a space after the executable file name,
340 // which mean that empty command line will have length of one.
341 if (!szCmdLine || lstrlen(szCmdLine) < 2)
343 szCmdLine = _T(" none");
346 FileWriteString(_T("\r\n\r\nCommand Line: "));
347 FileWriteString(szCmdLine);
349 FileWriteString(_T("\r\n\r\nModule Names: '~' prefix indicates module is loaded into the WinMerge process.\r\n"));
350 FileWriteString(_T(" Windows:\r\n"));
351 WriteVersionOf1(2, _T("kernel32.dll"));
352 WriteVersionOf1(2, _T("shell32.dll"));
353 WriteVersionOf1(2, _T("shlwapi.dll"));
354 WriteVersionOf1(2, _T("COMCTL32.dll"));
355 WriteVersionOf1(2, _T("msvcrt.dll"));
356 FileWriteString(_T( " WinMerge: Path names are relative to the Code File's directory.\r\n"));
357 WriteVersionOf1(2, _T(".\\ShellExtensionU.dll"));
358 WriteVersionOf1(2, _T(".\\ShellExtensionX64.dll"));
359 WriteVersionOf1(2, _T(".\\ShellExtensionARM64.dll"));
360 WriteVersionOf1(2, _T(".\\Frhed\\hekseditU.dll"));
361 WriteVersionOf1(2, _T(".\\WinIMerge\\WinIMergeLib.dll"));
362 WriteVersionOf1(2, _T(".\\Merge7z\\7z.dll"));
365 FileWriteString(_T("\r\nSystem Settings:\r\n"));
366 FileWriteString(_T(" Codepage Settings:\r\n"));
367 WriteItem(2, _T("ANSI codepage"), GetACP());
368 WriteItem(2, _T("OEM codepage"), GetOEMCP());
370 WriteItem(2, _T("multibyte codepage"), _getmbcp());
372 WriteLocaleSettings(GetThreadLocale(), _T("Locale (Thread)"));
373 WriteLocaleSettings(LOCALE_USER_DEFAULT, _T("Locale (User)"));
374 WriteLocaleSettings(LOCALE_SYSTEM_DEFAULT, _T("Locale (System)"));
377 FileWriteString(_T("\r\nPlugins: '!' Prefix indicates the plugin is Disabled.\r\n"));
378 FileWriteString( _T(" Unpackers: Path names are relative to the Code File's directory."));
379 WritePluginsInLogFile(L"FILE_PACK_UNPACK");
380 WritePluginsInLogFile(L"BUFFER_PACK_UNPACK");
381 WritePluginsInLogFile(L"FILE_FOLDER_PACK_UNPACK");
382 FileWriteString(_T("\r\n Prediffers: "));
383 WritePluginsInLogFile(L"FILE_PREDIFF");
384 WritePluginsInLogFile(L"BUFFER_PREDIFF");
385 FileWriteString(_T("\r\n Editor scripts: "));
386 WritePluginsInLogFile(L"EDITOR_SCRIPT");
387 if (!plugin::IsWindowsScriptThere())
388 FileWriteString(_T("\r\n .sct scripts disabled (Windows Script Host not found)\r\n"));
390 FileWriteString(_T("\r\n\r\n"));
393 FileWriteString(_T("\r\nWinMerge configuration:\r\n"));
394 WriteWinMergeConfig();
402 * @brief Parse Windows version data to string.
403 * @return String describing Windows version.
405 String CConfigLog::GetWindowsVer()
408 if (key.QueryRegMachine(_T("Software\\Microsoft\\Windows NT\\CurrentVersion")))
409 return key.ReadString(_T("ProductName"), _T("Unknown OS"));
410 return _T("Unknown OS");
415 * @brief Parse Processor Information data to string.
416 * @return String describing Windows version.
418 String CConfigLog::GetProcessorInfo()
421 String sProductName = _T("");
422 if (key.QueryRegMachine(_T("Hardware\\Description\\System\\CentralProcessor\\0")))
423 sProductName = key.ReadString(_T("Identifier"), _T(""));
424 if (sProductName != _T(""))
426 // This is the full identifier of the processor
427 // (e.g. "Intel64 Family 6 Model 158 Stepping 9")
428 // but we'll only keep the first word (e.g. "Intel64")
429 int x = (int)sProductName.find_first_of(_T(" "));
430 sProductName = sProductName.substr(0, x);
434 // Number of processors, Amount of memory
435 SYSTEM_INFO siSysInfo;
436 ::GetSystemInfo(&siSysInfo);
438 MEMORYSTATUSEX GlobalMemoryBuffer = {sizeof (GlobalMemoryBuffer)};
439 ::GlobalMemoryStatusEx(&GlobalMemoryBuffer);
440 ULONG lInstalledMemory = (ULONG)(GlobalMemoryBuffer.ullTotalPhys / (1024*1024));
443 swprintf_s(buf, MAX_PATH, _T("%u Logical Processors, %u MB Memory"),
444 siSysInfo.dwNumberOfProcessors, lInstalledMemory);
446 return sProductName + _T(", ") + String(buf);
450 * @brief Return string representation of build flags (for reporting in config log)
452 String CConfigLog::GetBuildFlags()
457 flags += _T(" WIN64 ");
459 flags += _T(" WIN32 ");
463 flags += _T(" UNICODE ");
467 flags += _T(" _DEBUG ");
470 #if defined TEST_WINMERGE
471 flags += _T(" TEST_WINMERGE ");
477 bool CConfigLog::WriteLogFile(String &sError)
481 return DoFile(sError);
484 /// Write line to file (if writing configuration log)
486 CConfigLog::FileWriteString(const String& lpsz)
488 m_pfile->WriteString(lpsz);
492 * @brief Close any open file
495 CConfigLog::CloseFile()
497 if (m_pfile->IsOpen())