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 < 1930
60 sVisualStudio = strutils::format(_T("VS.2019 (16.%d) - "), (_MSC_VER - 1920));
61 #elif _MSC_VER >= 1930 && _MSC_VER < 2000
62 sVisualStudio = strutils::format(_T("VS.2022 (17.%d) - "), (_MSC_VER - 1930));
63 #elif _MSC_VER >= 2000
64 # error "** Unknown NEW Version of Visual Studio **"
68 return strutils::format(_T("%sC/C++ Compiler %02i.%02i.%05i.%i"),
70 (int)(_MSC_VER / 100), (int)(_MSC_VER % 100), (int)(_MSC_FULL_VER % 100000), _MSC_BUILD
75 * @brief Get the Modified time of fully qualified file path and name
77 static String GetLastModified(const String &path)
82 CVersionInfo EXEversion;
83 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
84 sPath2 = sEXEPath + _T("\\") + sPath2;
88 String sModifiedTime = _T("");
91 Poco::Timestamp mtime(file.getLastModified());
93 const int64_t r = (mtime.epochTime());
94 sModifiedTime = locality::TimeString(&r);
100 * @brief Write plugin names
102 void CConfigLog::WritePluginsInLogFile(const wchar_t *transformationEvent)
104 CVersionInfo EXEversion;
105 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
107 // get an array with the available scripts
108 PluginArray * piPluginArray;
111 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(transformationEvent);
113 for (size_t iPlugin = 0 ; iPlugin < piPluginArray->size() ; iPlugin++)
115 const PluginInfoPtr& plugin = piPluginArray->at(iPlugin);
117 if (plugin->m_filepath.find(':') != String::npos)
119 String sFileName = paths::GetLongPath(plugin->m_filepath);
120 if (sFileName.length() > sEXEPath.length())
121 if (sFileName.substr(0, sEXEPath.length()) == sEXEPath)
122 sFileName = _T(".") + sFileName.erase(0, sEXEPath.length());
124 String sModifiedTime = _T("");
125 sModifiedTime = GetLastModified(plugin->m_filepath);
126 if (!sModifiedTime.empty())
127 sModifiedTime = _T("[") + sModifiedTime + _T("]");
129 sPluginText = strutils::format
130 (_T("\r\n %s%-36s path=%s %s"),
131 plugin->m_disabled ? _T("!") : _T(" "),
139 sPluginText = strutils::format
141 plugin->m_disabled ? _T("!") : _T(" "),
145 m_pfile->WriteString(sPluginText);
150 * @brief String wrapper around API call GetLocaleInfo
152 static String GetLocaleString(LCID locid, LCTYPE lctype)
155 if (!GetLocaleInfo(locid, lctype, buffer, sizeof(buffer)/sizeof(buffer[0])))
161 * @brief Write string item
163 void CConfigLog::WriteItem(int indent, const String& key, const tchar_t *value /*= nullptr*/)
165 String text = strutils::format(value ? _T("%*.0s%s: %s\r\n") : _T("%*.0s%s:\r\n"), indent, key, key, value);
166 m_pfile->WriteString(text);
170 * @brief Write string item
172 void CConfigLog::WriteItem(int indent, const String& key, const String &str)
174 WriteItem(indent, key, str.c_str());
178 * @brief Write int item
180 void CConfigLog::WriteItem(int indent, const String& key, long value)
182 String text = strutils::format(_T("%*.0s%s: %ld\r\n"), indent, key, key, value);
183 m_pfile->WriteString(text);
187 * @brief Write out various possibly relevant windows locale information
189 void CConfigLog::WriteLocaleSettings(unsigned locid, const String& title)
192 WriteItem(2, _T("Def ANSI codepage"), GetLocaleString(locid, LOCALE_IDEFAULTANSICODEPAGE));
193 WriteItem(2, _T("Def OEM codepage"), GetLocaleString(locid, LOCALE_IDEFAULTCODEPAGE));
194 WriteItem(2, _T("Country"), GetLocaleString(locid, LOCALE_SENGCOUNTRY));
195 WriteItem(2, _T("Language"), GetLocaleString(locid, LOCALE_SENGLANGUAGE));
196 WriteItem(2, _T("Language code"), GetLocaleString(locid, LOCALE_ILANGUAGE));
197 WriteItem(2, _T("ISO Language code"), GetLocaleString(locid, LOCALE_SISO639LANGNAME));
201 * @brief Write version of a single executable file
203 void CConfigLog::WriteVersionOf1(int indent, const String& path)
206 if (path2.find(_T(".\\")) == 0)
208 // Remove "relative path" info for Win API calls.
209 const tchar_t *pf = path2.c_str();
210 path2 = String(pf+2);
212 String name = paths::FindFileName(path2);
213 CVersionInfo vi(path2.c_str(), true);
214 String sModifiedTime = _T("");
217 sModifiedTime = GetLastModified(path);
218 if (!sModifiedTime.empty())
219 sModifiedTime = _T(" [") + sModifiedTime + _T("]");
221 String text = strutils::format
224 ? _T(" %*s%-18s %s=%u.%02u %s=%04u\r\n")
225 : _T(" %*s%-18s %s=%u.%02u %s=%04u path=%s%s\r\n"),
227 // Tilde prefix for modules currently mapped into WinMerge
228 GetModuleHandle(path2.c_str())
232 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwMajorVersion)
235 vi.m_dvi.dwMajorVersion,
236 vi.m_dvi.dwMinorVersion,
237 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwBuildNumber)
240 vi.m_dvi.dwBuildNumber,
244 m_pfile->WriteString(text);
248 * @brief Write winmerge configuration
250 void CConfigLog::WriteWinMergeConfig()
253 String tmppath = tmpfile.Create();
254 GetOptionsMgr()->ExportOptions(tmppath, true);
256 if (!ufile.OpenReadOnly(tmppath))
260 while (ufile.ReadString(line, &lossy))
262 String prefix = _T(" ");
263 if (line[0] == _T('[') )
265 FileWriteString(prefix + line + _T("\r\n"));
271 * @brief Write logfile
273 bool CConfigLog::DoFile(String &sError)
275 CVersionInfo version;
278 String sFileName = paths::ConcatPath(env::GetMyDocuments(), WinMergeDocumentsFolder);
279 paths::CreateIfNeeded(sFileName);
280 m_sFileName = paths::ConcatPath(sFileName, _T("WinMerge.txt"));
282 if (!m_pfile->OpenCreateUtf8(m_sFileName))
284 m_sFileName = paths::ConcatPath(env::GetTemporaryPath(), _T("WinMerge.txt"));
285 if (!m_pfile->OpenCreateUtf8(m_sFileName))
287 const UniFile::UniError& err = m_pfile->GetLastUniError();
288 sError = err.GetError();
292 m_pfile->SetBom(true);
296 FileWriteString(_T("WinMerge Configuration Log\r\n"));
297 FileWriteString(_T("--------------------------\r\n"));
298 FileWriteString(_T("\r\nLog Saved to: "));
299 FileWriteString(m_sFileName);
300 FileWriteString(_T("\r\n >> >> Please add this information (or attach this file) when reporting bugs << <<"));
304 FileWriteString(_T("\r\n\r\nWindows Info: "));
305 text = GetWindowsVer();
306 FileWriteString(text);
307 text = GetProcessorInfo();
310 FileWriteString(_T("\r\n Processor: "));
311 FileWriteString(text);
316 FileWriteString(_T("\r\n\r\nWinMerge Info:"));
317 String sEXEFullFileName = paths::GetLongPath(version.GetFullFileName(), false);
318 FileWriteString(_T("\r\n Code File: "));
319 FileWriteString(sEXEFullFileName);
321 FileWriteString(_T("\r\n Version: "));
322 FileWriteString(version.GetProductVersion());
323 String privBuild = version.GetPrivateBuild();
324 if (!privBuild.empty())
326 FileWriteString(_T(" + ") + privBuild);
329 FileWriteString(_T("\r\n Code File Modified: "));
330 FileWriteString(GetLastModified(sEXEFullFileName));
332 FileWriteString(_T("\r\n Build Config: "));
333 FileWriteString(GetBuildFlags());
335 FileWriteString(_T("\r\n Build Software: "));
336 FileWriteString(GetCompilerVersion());
338 const tchar_t* szCmdLine = ::GetCommandLine();
339 assert(szCmdLine != nullptr);
341 // Skip the quoted executable file name.
342 if (szCmdLine != nullptr)
344 szCmdLine = tc::tcschr(szCmdLine, '"');
345 if (szCmdLine != nullptr)
347 szCmdLine += 1; // skip the opening quote.
348 szCmdLine = tc::tcschr(szCmdLine, '"');
349 if (szCmdLine != nullptr)
351 szCmdLine += 1; // skip the closing quote.
356 // The command line include a space after the executable file name,
357 // which mean that empty command line will have length of one.
358 if (!szCmdLine || lstrlen(szCmdLine) < 2)
360 szCmdLine = _T(" none");
363 FileWriteString(_T("\r\n\r\nCommand Line: "));
364 FileWriteString(szCmdLine);
366 tchar_t szCurrentDirectory[MAX_PATH]{};
367 GetCurrentDirectory(sizeof(szCurrentDirectory) / sizeof(tchar_t), szCurrentDirectory);
368 FileWriteString(_T("\r\n\r\nCurrent Directory: "));
369 FileWriteString(szCurrentDirectory);
371 FileWriteString(_T("\r\n\r\nModule Names: '~' prefix indicates module is loaded into the WinMerge process.\r\n"));
372 FileWriteString(_T(" Windows:\r\n"));
373 WriteVersionOf1(2, _T("kernel32.dll"));
374 WriteVersionOf1(2, _T("shell32.dll"));
375 WriteVersionOf1(2, _T("shlwapi.dll"));
376 WriteVersionOf1(2, _T("COMCTL32.dll"));
377 WriteVersionOf1(2, _T("msvcrt.dll"));
378 FileWriteString(_T( " WinMerge: Path names are relative to the Code File's directory.\r\n"));
379 WriteVersionOf1(2, _T(".\\ShellExtensionU.dll"));
380 WriteVersionOf1(2, _T(".\\ShellExtensionX64.dll"));
381 WriteVersionOf1(2, _T(".\\ShellExtensionARM.dll"));
382 WriteVersionOf1(2, _T(".\\ShellExtensionARM64.dll"));
383 WriteVersionOf1(2, _T(".\\WinMergeContextMenu.dll"));
384 WriteVersionOf1(2, _T(".\\Frhed\\hekseditU.dll"));
385 WriteVersionOf1(2, _T(".\\WinIMerge\\WinIMergeLib.dll"));
386 WriteVersionOf1(2, _T(".\\WinWebDiff\\WinWebDiffLib.dll"));
387 WriteVersionOf1(2, _T(".\\Merge7z\\7z.dll"));
390 FileWriteString(_T("\r\nSystem Settings:\r\n"));
391 FileWriteString(_T(" Codepage Settings:\r\n"));
392 WriteItem(2, _T("ANSI codepage"), GetACP());
393 WriteItem(2, _T("OEM codepage"), GetOEMCP());
395 WriteItem(2, _T("multibyte codepage"), _getmbcp());
397 WriteLocaleSettings(GetThreadLocale(), _T("Locale (Thread)"));
398 WriteLocaleSettings(LOCALE_USER_DEFAULT, _T("Locale (User)"));
399 WriteLocaleSettings(LOCALE_SYSTEM_DEFAULT, _T("Locale (System)"));
402 FileWriteString(_T("\r\nPlugins: '!' Prefix indicates the plugin is Disabled.\r\n"));
403 FileWriteString( _T(" Unpackers: Path names are relative to the Code File's directory."));
404 WritePluginsInLogFile(L"URL_PACK_UNPACK");
405 WritePluginsInLogFile(L"FILE_PACK_UNPACK");
406 WritePluginsInLogFile(L"BUFFER_PACK_UNPACK");
407 WritePluginsInLogFile(L"FILE_FOLDER_PACK_UNPACK");
408 FileWriteString(_T("\r\n Prediffers: "));
409 WritePluginsInLogFile(L"FILE_PREDIFF");
410 WritePluginsInLogFile(L"BUFFER_PREDIFF");
411 FileWriteString(_T("\r\n Editor scripts: "));
412 WritePluginsInLogFile(L"EDITOR_SCRIPT");
413 if (!plugin::IsWindowsScriptThere())
414 FileWriteString(_T("\r\n .sct scripts disabled (Windows Script Host not found)\r\n"));
416 FileWriteString(_T("\r\n\r\n"));
419 FileWriteString(_T("\r\nWinMerge configuration:\r\n"));
420 WriteWinMergeConfig();
428 * @brief Parse Windows version data to string.
429 * @return String describing Windows version.
431 String CConfigLog::GetWindowsVer()
434 if (key.QueryRegMachine(_T("Software\\Microsoft\\Windows NT\\CurrentVersion")))
436 String productName = key.ReadString(_T("ProductName"), _T("Unknown OS"));
437 if (HMODULE hModule = GetModuleHandle(_T("ntdll.dll")))
439 using RtlGetNtVersionNumbersFunc = void (WINAPI*)(DWORD*, DWORD*, DWORD*);
440 DWORD dwMajor = 0, dwMinor = 0, dwBuildNumber = 0;
441 if (RtlGetNtVersionNumbersFunc RtlGetNtVersionNumbers =
442 reinterpret_cast<RtlGetNtVersionNumbersFunc>(GetProcAddress(hModule, "RtlGetNtVersionNumbers")))
444 RtlGetNtVersionNumbers(&dwMajor, &dwMinor, &dwBuildNumber);
445 dwBuildNumber &= ~0xF0000000;
446 if (dwMajor == 10 && dwMinor == 0 && dwBuildNumber >= 22000)
447 strutils::replace(productName, _T("Windows 10"), _T("Windows 11"));
452 return _T("Unknown OS");
457 * @brief Parse Processor Information data to string.
458 * @return String describing Windows version.
460 String CConfigLog::GetProcessorInfo()
463 String sProductName = _T("");
464 if (key.QueryRegMachine(_T("Hardware\\Description\\System\\CentralProcessor\\0")))
465 sProductName = key.ReadString(_T("Identifier"), _T(""));
466 if (!sProductName.empty())
468 // This is the full identifier of the processor
469 // (e.g. "Intel64 Family 6 Model 158 Stepping 9")
470 // but we'll only keep the first word (e.g. "Intel64")
471 int x = (int)sProductName.find_first_of(_T(' '));
472 sProductName = sProductName.substr(0, x);
476 // Number of processors, Amount of memory
477 SYSTEM_INFO siSysInfo;
478 ::GetSystemInfo(&siSysInfo);
480 MEMORYSTATUSEX GlobalMemoryBuffer = {sizeof (GlobalMemoryBuffer)};
481 ::GlobalMemoryStatusEx(&GlobalMemoryBuffer);
482 ULONG lInstalledMemory = (ULONG)(GlobalMemoryBuffer.ullTotalPhys / (1024*1024));
484 tchar_t buf[MAX_PATH];
485 swprintf_s(buf, MAX_PATH, _T("%u Logical Processors, %u MB Memory"),
486 siSysInfo.dwNumberOfProcessors, lInstalledMemory);
488 return sProductName + _T(", ") + String(buf);
492 * @brief Return string representation of build flags (for reporting in config log)
494 String CConfigLog::GetBuildFlags()
499 flags += _T(" WIN64 ");
501 flags += _T(" WIN32 ");
505 flags += _T(" UNICODE ");
509 flags += _T(" _DEBUG ");
512 #if defined TEST_WINMERGE
513 flags += _T(" TEST_WINMERGE ");
519 bool CConfigLog::WriteLogFile(String &sError)
523 return DoFile(sError);
526 /// Write line to file (if writing configuration log)
528 CConfigLog::FileWriteString(const String& lpsz)
530 m_pfile->WriteString(lpsz);
534 * @brief Close any open file
537 CConfigLog::CloseFile()
539 if (m_pfile->IsOpen())