OSDN Git Service

Avoid an assertion failure when loading settings from winmerge.ini
[winmerge-jp/winmerge-jp.git] / Src / ConfigLog.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** 
3  * @file  ConfigLog.cpp
4  *
5  * @brief CConfigLog implementation
6  */
7
8 #include "pch.h"
9 #include "ConfigLog.h"
10 #include <cassert>
11 #include <windows.h>
12 #include <memory>
13 #include "Constants.h"
14 #include "VersionInfo.h"
15 #include "UniFile.h"
16 #include "Plugins.h"
17 #include "TFile.h"
18 #include "paths.h"
19 #include "locality.h"
20 #include "unicoder.h"
21 #include "Environment.h"
22 #include "MergeApp.h"
23 #include "OptionsMgr.h"
24 #include "TempFile.h"
25 #include "UniFile.h"
26 #include "RegKey.h"
27
28 CConfigLog::CConfigLog()
29 : m_pfile(new UniStdioFile())
30 {
31 }
32
33 CConfigLog::~CConfigLog()
34 {
35         CloseFile();
36 }
37
38
39 /** 
40  * @brief Get details of the Compiler Version from _MSC_VER, etc.
41  *              Infer the Visual Studio version
42  */
43 static String GetCompilerVersion()
44 {
45         String sVisualStudio = _T(""); 
46 #ifdef _MSC_VER
47 #if     _MSC_VER <  1800
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 **"
63 #endif
64 #endif
65
66         return strutils::format(_T("%sC/C++ Compiler %02i.%02i.%05i.%i"),
67                 sVisualStudio,
68                 (int)(_MSC_VER / 100), (int)(_MSC_VER % 100), (int)(_MSC_FULL_VER % 100000), _MSC_BUILD
69         );
70 }
71
72 /** 
73  * @brief Get the Modified time of fully qualified file path and name
74  */
75 static String GetLastModified(const String &path) 
76 {
77         String sPath2 = path;
78         if (sPath2[0] == '.')
79         {
80                 CVersionInfo EXEversion;
81                 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
82                 sPath2 = sEXEPath + _T("\\") + sPath2;
83         }
84         TFile file(sPath2);
85
86         String sModifiedTime = _T("");
87         if (file.exists())
88         {
89                 Poco::Timestamp mtime(file.getLastModified());
90
91                 const int64_t r = (mtime.epochTime());
92                 sModifiedTime = locality::TimeString(&r);
93         }
94         return sModifiedTime;
95 }
96
97 /** 
98  * @brief Write plugin names
99  */
100 void CConfigLog::WritePluginsInLogFile(const wchar_t *transformationEvent)
101 {
102         CVersionInfo EXEversion;
103         String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
104
105         // get an array with the available scripts
106         PluginArray * piPluginArray; 
107
108         piPluginArray = 
109                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(transformationEvent);
110
111         for (size_t iPlugin = 0 ; iPlugin < piPluginArray->size() ; iPlugin++)
112         {
113                 const PluginInfoPtr& plugin = piPluginArray->at(iPlugin);
114
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());
119                 
120                 String sModifiedTime = _T("");
121                 sModifiedTime = GetLastModified(plugin->m_filepath);
122                 if (!sModifiedTime.empty())
123                         sModifiedTime = _T("[") + sModifiedTime + _T("]");
124                 
125                 String sPluginText = strutils::format
126                         (_T("\r\n  %s%-36s path=%s  %s"),
127                         plugin->m_disabled ? _T("!") : _T(" "),
128                         plugin->m_name,
129                         sFileName,
130                         sModifiedTime
131                         );
132                 m_pfile->WriteString(sPluginText);
133         }
134 }
135
136 /**
137  * @brief String wrapper around API call GetLocaleInfo
138  */
139 static String GetLocaleString(LCID locid, LCTYPE lctype)
140 {
141         TCHAR buffer[512];
142         if (!GetLocaleInfo(locid, lctype, buffer, sizeof(buffer)/sizeof(buffer[0])))
143                 buffer[0] = 0;
144         return buffer;
145 }
146
147 /**
148  * @brief Write string item
149  */
150 void CConfigLog::WriteItem(int indent, const String& key, const TCHAR *value /*= nullptr*/)
151 {
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);
154 }
155
156 /**
157  * @brief Write string item
158  */
159 void CConfigLog::WriteItem(int indent, const String& key, const String &str)
160 {
161         WriteItem(indent, key, str.c_str());
162 }
163
164 /**
165  * @brief Write int item
166  */
167 void CConfigLog::WriteItem(int indent, const String& key, long value)
168 {
169         String text = strutils::format(_T("%*.0s%s: %ld\r\n"), indent, key, key, value);
170         m_pfile->WriteString(text);
171 }
172
173 /**
174  * @brief Write out various possibly relevant windows locale information
175  */
176 void CConfigLog::WriteLocaleSettings(unsigned locid, const String& title)
177 {
178         WriteItem(1, 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));
185 }
186
187 /**
188  * @brief Write version of a single executable file
189  */
190 void CConfigLog::WriteVersionOf1(int indent, const String& path)
191 {
192         String path2 = path;
193         if (path2.find(_T(".\\")) == 0)
194         {
195                 // Remove "relative path" info for Win API calls.
196                 const TCHAR *pf = path2.c_str();
197                 path2 = String(pf+2);
198         }
199         String name = paths::FindFileName(path2);
200         CVersionInfo vi(path2.c_str(), true);
201         String sModifiedTime = _T("");
202         if (name != path)
203         {
204                 sModifiedTime = GetLastModified(path);
205                 if (!sModifiedTime.empty())
206                         sModifiedTime = _T("  [") + sModifiedTime + _T("]");
207         }
208         String text = strutils::format
209         (
210                 name == path
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"),
213                 indent,
214                 // Tilde prefix for modules currently mapped into WinMerge
215                 GetModuleHandle(path2.c_str()) 
216                         ? _T("~") 
217                         : _T("")/*name*/,
218                 name,
219                 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwMajorVersion)
220                         ?       _T("dllversion")
221                         :       _T("version"),
222                 vi.m_dvi.dwMajorVersion,
223                 vi.m_dvi.dwMinorVersion,
224                 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwBuildNumber)
225                         ?       _T("dllbuild")
226                         :       _T("build"),
227                 vi.m_dvi.dwBuildNumber,
228                 path,
229                 sModifiedTime
230         );
231         m_pfile->WriteString(text);
232 }
233
234 /**
235  * @brief Write winmerge configuration
236  */
237 void CConfigLog::WriteWinMergeConfig()
238 {
239         TempFile tmpfile;
240         String tmppath = tmpfile.Create();
241         GetOptionsMgr()->ExportOptions(tmppath, true);
242         UniMemFile ufile;
243         if (!ufile.OpenReadOnly(tmppath))
244                 return;
245         String line;
246         bool lossy;
247         while (ufile.ReadString(line, &lossy)) 
248         {
249                 String prefix = _T("  ");
250                 if (line[0] == _T('[') )
251                         prefix = _T(" ");
252                 FileWriteString(prefix + line + _T("\r\n"));
253         }
254         ufile.Close();
255 }
256
257 /** 
258  * @brief Write logfile
259  */
260 bool CConfigLog::DoFile(String &sError)
261 {
262         CVersionInfo version;
263         String text;
264
265         String sFileName = paths::ConcatPath(env::GetMyDocuments(), WinMergeDocumentsFolder);
266         paths::CreateIfNeeded(sFileName);
267         m_sFileName = paths::ConcatPath(sFileName, _T("WinMerge.txt"));
268
269         if (!m_pfile->OpenCreateUtf8(m_sFileName))
270         {
271                 const UniFile::UniError &err = m_pfile->GetLastUniError();
272                 sError = err.GetError();
273                 return false;
274         }
275         m_pfile->SetBom(true);
276         m_pfile->WriteBom();
277
278 // Begin log
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 << <<"));
284
285 // Platform stuff
286         
287         FileWriteString(_T("\r\n\r\nWindows Info:         "));
288         text = GetWindowsVer();
289         FileWriteString(text);
290         text = GetProcessorInfo();
291         if (text != _T(""))
292         {               
293                 FileWriteString(_T("\r\n Processor:           "));
294                 FileWriteString(text);
295         }
296
297 // WinMerge stuff
298
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);
303
304         FileWriteString(_T("\r\n Version:             "));
305         FileWriteString(version.GetProductVersion());
306         String privBuild = version.GetPrivateBuild();
307         if (!privBuild.empty())
308         {
309                 FileWriteString(_T(" + ") + privBuild);
310         }
311
312         FileWriteString(_T("\r\n Code File Modified:  "));
313         FileWriteString(GetLastModified(sEXEFullFileName));
314
315         FileWriteString(_T("\r\n Build Config:       "));
316         FileWriteString(GetBuildFlags());
317
318         FileWriteString(_T("\r\n Build Software:      "));
319         FileWriteString(GetCompilerVersion());
320
321         LPCTSTR szCmdLine = ::GetCommandLine();
322         assert(szCmdLine != nullptr);
323
324         // Skip the quoted executable file name.
325         if (szCmdLine != nullptr)
326         {
327                 szCmdLine = _tcschr(szCmdLine, '"');
328                 if (szCmdLine != nullptr)
329                 {
330                         szCmdLine += 1; // skip the opening quote.
331                         szCmdLine = _tcschr(szCmdLine, '"');
332                         if (szCmdLine != nullptr)
333                         {
334                                 szCmdLine += 1; // skip the closing quote.
335                         }
336                 }
337         }
338
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)
342         {
343                 szCmdLine = _T(" none");
344         }
345
346         FileWriteString(_T("\r\n\r\nCommand Line:        "));
347         FileWriteString(szCmdLine);
348
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"));
363
364 // System settings
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());
369 #ifndef UNICODE
370         WriteItem(2, _T("multibyte codepage"), _getmbcp());
371 #endif
372         WriteLocaleSettings(GetThreadLocale(), _T("Locale (Thread)"));
373         WriteLocaleSettings(LOCALE_USER_DEFAULT, _T("Locale (User)"));
374         WriteLocaleSettings(LOCALE_SYSTEM_DEFAULT, _T("Locale (System)"));
375
376 // Plugins
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"));
389
390         FileWriteString(_T("\r\n\r\n"));
391
392 // WinMerge settings
393         FileWriteString(_T("\r\nWinMerge configuration:\r\n"));
394         WriteWinMergeConfig();
395
396         CloseFile();
397
398         return true;
399 }
400
401 /** 
402  * @brief Parse Windows version data to string.
403  * @return String describing Windows version.
404  */
405 String CConfigLog::GetWindowsVer()
406 {
407         CRegKeyEx key;
408         if (key.QueryRegMachine(_T("Software\\Microsoft\\Windows NT\\CurrentVersion")))
409                 return key.ReadString(_T("ProductName"), _T("Unknown OS"));
410         return _T("Unknown OS");
411 }
412
413
414 /** 
415  * @brief Parse Processor Information data to string.
416  * @return String describing Windows version.
417  */
418 String CConfigLog::GetProcessorInfo()
419 {
420         CRegKeyEx key;
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(""))
425         {
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);
431         }
432
433
434         // Number of processors, Amount of memory
435         SYSTEM_INFO siSysInfo;
436         ::GetSystemInfo(&siSysInfo); 
437
438         MEMORYSTATUSEX GlobalMemoryBuffer = {sizeof (GlobalMemoryBuffer)};
439         ::GlobalMemoryStatusEx(&GlobalMemoryBuffer);
440         ULONG lInstalledMemory = (ULONG)(GlobalMemoryBuffer.ullTotalPhys / (1024*1024));
441
442         TCHAR buf[MAX_PATH];
443         swprintf_s(buf, MAX_PATH, _T("%u Logical Processors, %u MB Memory"), 
444                         siSysInfo.dwNumberOfProcessors, lInstalledMemory); 
445
446         return sProductName + _T(", ") + String(buf);
447 }
448         
449 /** 
450  * @brief Return string representation of build flags (for reporting in config log)
451  */
452 String CConfigLog::GetBuildFlags()
453 {
454         String flags;
455
456 #if defined WIN64
457         flags += _T(" WIN64 ");
458 #elif defined WIN32
459         flags += _T(" WIN32 ");
460 #endif
461
462 #if defined UNICODE
463         flags += _T(" UNICODE ");
464 #endif
465
466 #if defined _DEBUG
467         flags += _T(" _DEBUG ");
468 #endif
469
470 #if defined TEST_WINMERGE
471         flags += _T(" TEST_WINMERGE ");
472 #endif
473
474         return flags;
475 }
476
477 bool CConfigLog::WriteLogFile(String &sError)
478 {
479         CloseFile();
480
481         return DoFile(sError);
482 }
483
484 /// Write line to file (if writing configuration log)
485 void
486 CConfigLog::FileWriteString(const String& lpsz)
487 {
488         m_pfile->WriteString(lpsz);
489 }
490
491 /**
492  * @brief Close any open file
493  */
494 void
495 CConfigLog::CloseFile()
496 {
497         if (m_pfile->IsOpen())
498                 m_pfile->Close();
499 }
500