1 /////////////////////////////////////////////////////////////////////////////
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation; either version 2 of the License, or (at
6 // your option) any later version.
8 // This program is distributed in the hope that it will be useful, but
9 // WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16 /////////////////////////////////////////////////////////////////////////////
20 * @brief CConfigLog implementation
24 #include "ConfigLog.h"
28 #include "Constants.h"
29 #include "VersionInfo.h"
36 #include "Environment.h"
38 #include "OptionsMgr.h"
43 CConfigLog::CConfigLog()
44 : m_pfile(new UniStdioFile())
48 CConfigLog::~CConfigLog()
55 * @brief Get details of the Compiler Version from _MSC_VER, etc.
56 * Infer the Visual Studio version
58 static String GetCompilerVersion()
60 String sVisualStudio = _T("");
63 # error "** Unknown OLD Version of Visual Studio **"
64 #elif _MSC_VER == 1800
65 sVisualStudio = _T("VS.2013 (12.0) - ");
66 #elif _MSC_VER == 1900
67 sVisualStudio = _T("VS.2015 (14.0) - ");
68 #elif _MSC_VER == 1910
69 sVisualStudio = _T("VS.2017 (15.0) - ");
70 #elif _MSC_VER == 1911
71 sVisualStudio = _T("VS.2017 (15.3) - ");
72 #elif _MSC_VER >= 1912 && _MSC_VER < 1920
73 sVisualStudio = strutils::format(_T("VS.2017 (15.%d) - "), 5 + (_MSC_VER - 1912));
74 #elif _MSC_VER >= 1920 && _MSC_VER < 2000
75 sVisualStudio = strutils::format(_T("VS.2019 (16.%d) - "), (_MSC_VER - 1920));
76 #elif _MSC_VER >= 2000
77 # error "** Unknown NEW Version of Visual Studio **"
81 return strutils::format(_T("%sC/C++ Compiler %02i.%02i.%05i.%i"),
82 sVisualStudio.c_str(),
83 (int)(_MSC_VER / 100), (int)(_MSC_VER % 100), (int)(_MSC_FULL_VER % 100000), _MSC_BUILD
89 * @brief Return logfile name and path
91 String CConfigLog::GetFileName() const
98 * @brief Get the Modified time of fully qualified file path and name
100 static String GetLastModified(const String &path)
102 String sPath2 = path;
103 if (sPath2[0] == '.')
105 CVersionInfo EXEversion;
106 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
107 sPath2 = sEXEPath + _T("\\") + sPath2;
111 String sModifiedTime = _T("");
114 Poco::Timestamp mtime(file.getLastModified());
116 const int64_t r = (mtime.epochTime());
117 sModifiedTime = locality::TimeString(&r);
119 return sModifiedTime;
123 * @brief Write plugin names
125 void CConfigLog::WritePluginsInLogFile(const wchar_t *transformationEvent)
127 CVersionInfo EXEversion;
128 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
130 // get an array with the available scripts
131 PluginArray * piPluginArray;
134 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(transformationEvent);
136 for (size_t iPlugin = 0 ; iPlugin < piPluginArray->size() ; iPlugin++)
138 const PluginInfoPtr& plugin = piPluginArray->at(iPlugin);
140 String sFileName = paths::GetLongPath(plugin->m_filepath);
141 if (sFileName.length() > sEXEPath.length())
142 if (sFileName.substr(0, sEXEPath.length()) == sEXEPath)
143 sFileName = _T(".") + sFileName.erase(0, sEXEPath.length());
145 String sModifiedTime = _T("");
146 sModifiedTime = GetLastModified(plugin->m_filepath);
147 if (!sModifiedTime.empty())
148 sModifiedTime = _T("[") + sModifiedTime + _T("]");
150 String sPluginText = strutils::format
151 (_T("\r\n %s%-36s path=%s %s"),
152 plugin->m_disabled ? _T("!") : _T(" "),
153 plugin->m_name.c_str(),
155 sModifiedTime.c_str()
157 m_pfile->WriteString(sPluginText);
162 * @brief String wrapper around API call GetLocaleInfo
164 static String GetLocaleString(LCID locid, LCTYPE lctype)
167 if (!GetLocaleInfo(locid, lctype, buffer, sizeof(buffer)/sizeof(buffer[0])))
173 * @brief Write string item
175 void CConfigLog::WriteItem(int indent, const String& key, const TCHAR *value /*= nullptr*/)
177 String text = strutils::format(value ? _T("%*.0s%s: %s\r\n") : _T("%*.0s%s:\r\n"), indent, key.c_str(), key.c_str(), value);
178 m_pfile->WriteString(text);
182 * @brief Write string item
184 void CConfigLog::WriteItem(int indent, const String& key, const String &str)
186 WriteItem(indent, key, str.c_str());
190 * @brief Write int item
192 void CConfigLog::WriteItem(int indent, const String& key, long value)
194 String text = strutils::format(_T("%*.0s%s: %ld\r\n"), indent, key.c_str(), key.c_str(), value);
195 m_pfile->WriteString(text);
199 * @brief Write out various possibly relevant windows locale information
201 void CConfigLog::WriteLocaleSettings(unsigned locid, const String& title)
204 WriteItem(2, _T("Def ANSI codepage"), GetLocaleString(locid, LOCALE_IDEFAULTANSICODEPAGE));
205 WriteItem(2, _T("Def OEM codepage"), GetLocaleString(locid, LOCALE_IDEFAULTCODEPAGE));
206 WriteItem(2, _T("Country"), GetLocaleString(locid, LOCALE_SENGCOUNTRY));
207 WriteItem(2, _T("Language"), GetLocaleString(locid, LOCALE_SENGLANGUAGE));
208 WriteItem(2, _T("Language code"), GetLocaleString(locid, LOCALE_ILANGUAGE));
209 WriteItem(2, _T("ISO Language code"), GetLocaleString(locid, LOCALE_SISO639LANGNAME));
213 * @brief Write version of a single executable file
215 void CConfigLog::WriteVersionOf1(int indent, const String& path)
218 if (path2.find(_T(".\\")) == 0)
220 // Remove "relative path" info for Win API calls.
221 const TCHAR *pf = path2.c_str();
222 path2 = String(pf+2);
224 String name = paths::FindFileName(path2);
225 CVersionInfo vi(path2.c_str(), true);
226 String sModifiedTime = _T("");
229 sModifiedTime = GetLastModified(path);
230 if (!sModifiedTime.empty())
231 sModifiedTime = _T(" [") + sModifiedTime + _T("]");
233 String text = strutils::format
236 ? _T(" %*s%-18s %s=%u.%02u %s=%04u\r\n")
237 : _T(" %*s%-18s %s=%u.%02u %s=%04u path=%s%s\r\n"),
239 // Tilde prefix for modules currently mapped into WinMerge
240 GetModuleHandle(path2.c_str())
244 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwMajorVersion)
247 vi.m_dvi.dwMajorVersion,
248 vi.m_dvi.dwMinorVersion,
249 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwBuildNumber)
252 vi.m_dvi.dwBuildNumber,
254 sModifiedTime.c_str()
256 m_pfile->WriteString(text);
260 * @brief Write winmerge configuration
262 void CConfigLog::WriteWinMergeConfig()
265 String tmppath = tmpfile.Create();
266 GetOptionsMgr()->ExportOptions(tmppath, true);
268 if (!ufile.OpenReadOnly(tmppath))
272 while (ufile.ReadString(line, &lossy))
274 String prefix = _T(" ");
275 if (line[0] == _T('[') )
277 FileWriteString(prefix + line + _T("\r\n"));
283 * @brief Write logfile
285 bool CConfigLog::DoFile(String &sError)
287 CVersionInfo version;
290 String sFileName = paths::ConcatPath(env::GetMyDocuments(), WinMergeDocumentsFolder);
291 paths::CreateIfNeeded(sFileName);
292 m_sFileName = paths::ConcatPath(sFileName, _T("WinMerge.txt"));
294 if (!m_pfile->OpenCreateUtf8(m_sFileName))
296 const UniFile::UniError &err = m_pfile->GetLastUniError();
297 sError = err.GetError();
300 m_pfile->SetBom(true);
304 FileWriteString(_T("WinMerge Configuration Log\r\n"));
305 FileWriteString(_T("--------------------------\r\n"));
306 FileWriteString(_T("\r\nLog Saved to: "));
307 FileWriteString(m_sFileName);
308 FileWriteString(_T("\r\n >> >> Please add this information (or attach this file) when reporting bugs << <<"));
312 FileWriteString(_T("\r\n\r\nWindows Info: "));
313 text = GetWindowsVer();
314 FileWriteString(text);
315 text = GetProcessorInfo();
318 FileWriteString(_T("\r\n Processor: "));
319 FileWriteString(text);
324 FileWriteString(_T("\r\n\r\nWinMerge Info:"));
325 String sEXEFullFileName = paths::GetLongPath(version.GetFullFileName(), false);
326 FileWriteString(_T("\r\n Code File: "));
327 FileWriteString(sEXEFullFileName);
329 FileWriteString(_T("\r\n Version: "));
330 FileWriteString(version.GetProductVersion());
331 String privBuild = version.GetPrivateBuild();
332 if (!privBuild.empty())
334 FileWriteString(_T(" + ") + privBuild);
337 FileWriteString(_T("\r\n Code File Modified: "));
338 FileWriteString(GetLastModified(sEXEFullFileName));
340 FileWriteString(_T("\r\n Build Config: "));
341 FileWriteString(GetBuildFlags());
343 FileWriteString(_T("\r\n Build Software: "));
344 FileWriteString(GetCompilerVersion());
346 LPCTSTR szCmdLine = ::GetCommandLine();
347 assert(szCmdLine != nullptr);
349 // Skip the quoted executable file name.
350 if (szCmdLine != nullptr)
352 szCmdLine = _tcschr(szCmdLine, '"');
353 if (szCmdLine != nullptr)
355 szCmdLine += 1; // skip the opening quote.
356 szCmdLine = _tcschr(szCmdLine, '"');
357 if (szCmdLine != nullptr)
359 szCmdLine += 1; // skip the closing quote.
364 // The command line include a space after the executable file name,
365 // which mean that empty command line will have length of one.
366 if (!szCmdLine || lstrlen(szCmdLine) < 2)
368 szCmdLine = _T(" none");
371 FileWriteString(_T("\r\n\r\nCommand Line: "));
372 FileWriteString(szCmdLine);
374 String sEXEPathOnly = paths::GetPathOnly(sEXEFullFileName);
376 FileWriteString(_T("\r\n\r\nModule Names: '~' prefix indicates module is loaded into the WinMerge process.\r\n"));
377 FileWriteString(_T(" Windows:\r\n"));
378 WriteVersionOf1(2, _T("kernel32.dll"));
379 WriteVersionOf1(2, _T("shell32.dll"));
380 WriteVersionOf1(2, _T("shlwapi.dll"));
381 WriteVersionOf1(2, _T("COMCTL32.dll"));
382 WriteVersionOf1(2, _T("msvcrt.dll"));
383 FileWriteString(_T( " WinMerge: Path names are relative to the Code File's directory.\r\n"));
384 WriteVersionOf1(2, _T(".\\ShellExtensionU.dll"));
385 WriteVersionOf1(2, _T(".\\ShellExtensionX64.dll"));
386 WriteVersionOf1(2, _T(".\\Frhed\\hekseditU.dll"));
387 WriteVersionOf1(2, _T(".\\WinIMerge\\WinIMergeLib.dll"));
388 WriteVersionOf1(2, _T(".\\Merge7z\\7z.dll"));
391 FileWriteString(_T("\r\nSystem Settings:\r\n"));
392 FileWriteString(_T(" Codepage Settings:\r\n"));
393 WriteItem(2, _T("ANSI codepage"), GetACP());
394 WriteItem(2, _T("OEM codepage"), GetOEMCP());
396 WriteItem(2, _T("multibyte codepage"), _getmbcp());
398 WriteLocaleSettings(GetThreadLocale(), _T("Locale (Thread)"));
399 WriteLocaleSettings(LOCALE_USER_DEFAULT, _T("Locale (User)"));
400 WriteLocaleSettings(LOCALE_SYSTEM_DEFAULT, _T("Locale (System)"));
403 FileWriteString(_T("\r\nPlugins: '!' Prefix indicates the plugin is Disabled.\r\n"));
404 FileWriteString( _T(" Unpackers: Path names are relative to the Code File's directory."));
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() const
434 if (key.QueryRegMachine(_T("Software\\Microsoft\\Windows NT\\CurrentVersion")))
435 return key.ReadString(_T("ProductName"), _T("Unknown OS"));
436 return _T("Unknown OS");
441 * @brief Parse Processor Information data to string.
442 * @return String describing Windows version.
444 String CConfigLog::GetProcessorInfo() const
447 String sProductName = _T("");
448 if (key.QueryRegMachine(_T("Hardware\\Description\\System\\CentralProcessor\\0")))
449 sProductName = key.ReadString(_T("Identifier"), _T(""));
450 if (sProductName != _T(""))
452 // This is the full identifier of the processor
453 // (e.g. "Intel64 Family 6 Model 158 Stepping 9")
454 // but we'll only keep the first word (e.g. "Intel64")
455 int x = (int)sProductName.find_first_of(_T(" "));
456 sProductName = sProductName.substr(0, x);
460 // Number of processors, Amount of memory
461 SYSTEM_INFO siSysInfo;
462 ::GetSystemInfo(&siSysInfo);
464 MEMORYSTATUSEX GlobalMemoryBuffer;
465 memset(&GlobalMemoryBuffer, 0, sizeof(GlobalMemoryBuffer));
466 GlobalMemoryBuffer.dwLength = sizeof (GlobalMemoryBuffer);
467 ::GlobalMemoryStatusEx(&GlobalMemoryBuffer);
468 ULONG lInstalledMemory = (ULONG)(GlobalMemoryBuffer.ullTotalPhys / (1024*1024));
471 swprintf_s(buf, MAX_PATH, _T("%u Logical Processors, %u MB Memory"),
472 siSysInfo.dwNumberOfProcessors, lInstalledMemory);
474 return sProductName + _T(", ") + String(buf);
478 * @brief Return string representation of build flags (for reporting in config log)
480 String CConfigLog::GetBuildFlags() const
485 flags += _T(" WIN64 ");
487 flags += _T(" WIN32 ");
491 flags += _T(" UNICODE ");
495 flags += _T(" _DEBUG ");
498 #if defined TEST_WINMERGE
499 flags += _T(" TEST_WINMERGE ");
505 bool CConfigLog::WriteLogFile(String &sError)
509 return DoFile(sError);
512 /// Write line to file (if writing configuration log)
514 CConfigLog::FileWriteString(const String& lpsz)
516 m_pfile->WriteString(lpsz);
520 * @brief Close any open file
523 CConfigLog::CloseFile()
525 if (m_pfile->IsOpen())