OSDN Git Service

resource.h: Add IDS_PLUGIN_DESCRIPTION*
[winmerge-jp/winmerge-jp.git] / Src / InternalPlugins.cpp
1 #include "pch.h"
2 #include "Plugins.h"
3 #define POCO_NO_UNWINDOWS 1
4 #include <Poco/FileStream.h>
5 #include <Poco/SAX/SAXParser.h>
6 #include <Poco/SAX/SAXException.h>
7 #include <Poco/SAX/ContentHandler.h>
8 #include <Poco/SAX/Attributes.h>
9 #include <Poco/Exception.h>
10 #include <vector>
11 #include <list>
12 #include <unordered_set>
13 #include <algorithm>
14 #include <cassert>
15 #include <iostream>
16 #include <sstream>
17 #include <windows.h>
18 #include "MergeApp.h"
19 #include "paths.h"
20 #include "Environment.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDef.h"
23 #include "codepage_detect.h"
24 #include "UniFile.h"
25 #include "WinMergePluginBase.h"
26 #include "TempFile.h"
27
28 using Poco::XML::SAXParser;
29 using Poco::XML::ContentHandler;
30 using Poco::XML::Locator;
31 using Poco::XML::XMLChar;
32 using Poco::XML::XMLString;
33 using Poco::XML::Attributes;
34 using namespace std::literals::string_literals;
35
36 namespace
37 {
38         HRESULT ReadFile(const String& path, String& text)
39         {
40                 UniMemFile file;
41                 if (!file.OpenReadOnly(path))
42                         return HRESULT_FROM_WIN32(GetLastError());
43                 file.ReadBom();
44                 if (!file.HasBom())
45                 {
46                         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
47                         int64_t fileSize = file.GetFileSize();
48                         FileTextEncoding encoding = codepage_detect::Guess(
49                                 paths::FindExtension(path), file.GetBase(), static_cast<size_t>(
50                                         fileSize < static_cast<int64_t>(codepage_detect::BufSize) ?
51                                         fileSize : static_cast<int64_t>(codepage_detect::BufSize)),
52                                 iGuessEncodingType);
53                         file.SetCodepage(encoding.m_codepage);
54                 }
55                 file.ReadStringAll(text);
56                 file.Close();
57                 return S_OK;
58         }
59
60         HRESULT WriteFile(const String& path, const String& text, bool bom = true)
61         {
62                 UniStdioFile fileOut;
63                 if (!fileOut.Open(path, _T("wb")))
64                         return HRESULT_FROM_WIN32(GetLastError());
65                 fileOut.SetUnicoding(ucr::UNICODESET::UTF8);
66                 fileOut.WriteString(text);
67                 fileOut.Close();
68                 return S_OK;
69         }
70 }
71
72 namespace internal_plugin
73 {
74
75 struct Script
76 {
77         String m_body;
78         String m_fileExtension;
79 };
80
81 struct Method
82 {
83         String m_command;
84         std::unique_ptr<Script> m_script;
85 };
86
87 struct Info
88 {
89         Info(const String& name) : m_name(name) {}
90         Info(Info&& info) = default;
91         String m_name;
92         String m_event;
93         String m_description;
94         String m_fileFilters;
95         bool m_isAutomatic = false;
96         String m_unpackedFileExtension;
97         String m_extendedProperties;
98         String m_arguments;
99         std::unique_ptr<Method> m_prediffFile;
100         std::unique_ptr<Method> m_unpackFile;
101         std::unique_ptr<Method> m_packFile;
102         std::unique_ptr<Method> m_unpackFolder;
103         std::unique_ptr<Method> m_packFolder;
104         std::map<String, Method> m_editorScripts;
105 };
106
107 class XMLHandler : public Poco::XML::ContentHandler
108 {
109 public:
110         inline static const std::string Empty = "";
111         inline static const std::string PluginsElement = "plugins";
112         inline static const std::string PluginElement = "plugin";
113         inline static const std::string EventElement = "event";
114         inline static const std::string DescriptionElement = "description";
115         inline static const std::string FileFiltersElement = "file-filters";
116         inline static const std::string IsAutomaticElement = "is-automatic";
117         inline static const std::string UnpackedFileExtensionElement = "unpacked-file-extension";
118         inline static const std::string ExtendedPropertiesElement = "extended-properties";
119         inline static const std::string ArgumentsElement = "arguments";
120         inline static const std::string PrediffFileElement = "prediff-file";
121         inline static const std::string UnpackFileElement = "unpack-file";
122         inline static const std::string PackFileElement = "pack-file";
123         inline static const std::string UnpackFolderElement = "unpack-folder";
124         inline static const std::string PackFolderElement = "pack-folder";
125         inline static const std::string CommandElement = "command";
126         inline static const std::string ScriptElement = "script";
127         inline static const std::string NameAttribute = "name";
128         inline static const std::string ValueAttribute = "value";
129         inline static const std::string FileExtensionAttribute = "fileExtension";
130
131         explicit XMLHandler(std::list<Info>* pPlugins) : m_pPlugins(pPlugins) {}
132
133         void setDocumentLocator(const Locator* loc) {}
134         void startDocument() {}
135         void endDocument() {}
136         void startElement(const XMLString& uri, const XMLString& localName, const XMLString& qname, const Attributes& attributes)
137         {
138                 if (!m_stack.empty())
139                 {
140                         if (m_stack.top() == PluginsElement)
141                         {
142                                 if (localName == PluginElement)
143                                 {
144                                         String name;
145                                         int index = attributes.getIndex(Empty, NameAttribute);
146                                         if (index >= 0)
147                                                 name = ucr::toTString(attributes.getValue(index));
148                                         m_pPlugins->emplace_back(name);
149                                         m_pMethod = nullptr;
150                                 }
151                         }
152                         else if (m_stack.top() == PluginElement)
153                         {
154                                 Info& plugin = m_pPlugins->back();
155                                 String value;
156                                 int index = attributes.getIndex(Empty, ValueAttribute);
157                                 if (index >= 0)
158                                 {
159                                         value = ucr::toTString(attributes.getValue(index));
160                                         if (localName == EventElement)
161                                                 plugin.m_event = value;
162                                         else if (localName == DescriptionElement)
163                                                 plugin.m_description = value;
164                                         else if (localName == FileFiltersElement)
165                                                 plugin.m_fileFilters = value;
166                                         else if (localName == IsAutomaticElement)
167                                         {
168                                                 TCHAR ch = value.c_str()[0];
169                                                 plugin.m_isAutomatic = (ch == 't' || ch == 'T');
170                                         }
171                                         else if (localName == UnpackedFileExtensionElement)
172                                                 plugin.m_unpackedFileExtension = value;
173                                         else if (localName == ExtendedPropertiesElement)
174                                                 plugin.m_extendedProperties = value;
175                                         else if (localName == ArgumentsElement)
176                                                 plugin.m_arguments = value;
177                                 }
178                                 else if (localName == PrediffFileElement)
179                                 {
180                                         plugin.m_prediffFile.reset(new Method());
181                                         m_pMethod = plugin.m_prediffFile.get();
182                                 }
183                                 else if (localName == UnpackFileElement)
184                                 {
185                                         plugin.m_unpackFile.reset(new Method());
186                                         m_pMethod = plugin.m_unpackFile.get();
187                                 }
188                                 else if (localName == PackFileElement)
189                                 {
190                                         plugin.m_packFile.reset(new Method());
191                                         m_pMethod = plugin.m_packFile.get();
192                                 }
193                                 else if (localName == UnpackFolderElement)
194                                 {
195                                         plugin.m_unpackFolder.reset(new Method());
196                                         m_pMethod = plugin.m_unpackFolder.get();
197                                 }
198                                 else if (localName == PackFolderElement)
199                                 {
200                                         plugin.m_packFolder.reset(new Method());
201                                         m_pMethod = plugin.m_packFolder.get();
202                                 }
203                         }
204                         else if (m_pMethod)
205                         {
206                                 if (localName == ScriptElement)
207                                 {
208                                         m_pMethod->m_script.reset(new Script);
209                                         int index = attributes.getIndex(Empty, FileExtensionAttribute);
210                                         if (index >= 0)
211                                                 m_pMethod->m_script->m_fileExtension = ucr::toTString(attributes.getValue(index));
212                                 }
213                         }
214                 }
215                 m_stack.push(localName);
216         }
217         void endElement(const XMLString& uri, const XMLString& localName, const XMLString& qname)
218         {
219                 m_stack.pop();
220         }
221         void characters(const XMLChar ch[], int start, int length)
222         {
223                 if (m_stack.empty())
224                         return;
225                 if (m_stack.top() == CommandElement && m_pMethod)
226                 {
227                         m_pMethod->m_command += xmlch2tstr(ch, length);
228                 }
229                 else if (m_stack.top() == ScriptElement && m_pMethod && m_pMethod->m_script)
230                 {
231                         m_pMethod->m_script->m_body += xmlch2tstr(ch, length);
232                 }
233         }
234         void ignorableWhitespace(const XMLChar ch[], int start, int length) {}
235         void processingInstruction(const XMLString& target, const XMLString& data) {}
236         void startPrefixMapping(const XMLString& prefix, const XMLString& uri) {}
237         void endPrefixMapping(const XMLString& prefix) {}
238         void skippedEntity(const XMLString& name) {}
239
240 private:
241         static String xmlch2tstr(const XMLChar* ch, int length)
242         {
243                 return ucr::toTString(std::string(ch, length));
244         }
245
246         std::list<Info>* m_pPlugins = nullptr;
247         std::stack<std::string> m_stack;
248         Method* m_pMethod = nullptr;
249 };
250
251 class UnpackerGeneratedFromEditorScript : public WinMergePluginBase
252 {
253 public:
254         UnpackerGeneratedFromEditorScript(const PluginInfo& plugin, const std::wstring funcname, int id)
255                 : WinMergePluginBase(
256                         L"FILE_PACK_UNPACK",
257                         strutils::format_string1(_T("Unpacker to execute %1 script (automatically generated)"), funcname),
258                         L"\\.nomatch$", L"")
259                 , m_pDispatch(plugin.m_lpDispatch)
260                 , m_funcid(id)
261                 , m_hasArgumentsProperty(plugin.m_hasArgumentsProperty)
262                 , m_hasVariablesProperty(plugin.m_hasVariablesProperty)
263         {
264                 auto desc = plugin.GetExtendedPropertyValue(funcname + _T(".Description"));
265                 if (desc.has_value())
266                         m_sDescription = *desc;
267                 m_pDispatch->AddRef();
268                 auto menuCaption = plugin.GetExtendedPropertyValue(funcname + _T(".MenuCaption"));
269                 String caption = menuCaption.has_value() ? String{ menuCaption->data(), menuCaption->length() } : funcname;
270                 m_sExtendedProperties = ucr::toUTF16(strutils::format(_T("ProcessType=Editor script;MenuCaption=%s"), caption))
271                         + (plugin.GetExtendedPropertyValue(funcname + _T(".ArgumentsRequired")).has_value() ? L";ArgumentsRequired" : L"");
272                 StringView args = plugin.GetExtendedPropertyValue(funcname + _T(".Arguments")).value_or(_T(""));
273                 m_sArguments = { args.data(), args.length() };
274         }
275
276         virtual ~UnpackerGeneratedFromEditorScript()
277         {
278                 m_pDispatch->Release();
279         }
280
281         HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
282         {
283                 String text;
284                 HRESULT hr = ReadFile(fileSrc, text);
285                 if (FAILED(hr))
286                         return hr;
287                 if (m_hasVariablesProperty && !plugin::InvokePutPluginVariables(ucr::toTString(fileSrc), m_pDispatch))
288                         return E_FAIL;
289                 if (m_hasArgumentsProperty && !plugin::InvokePutPluginArguments(m_sArguments, m_pDispatch))
290                         return E_FAIL;
291                 int changed = 0;
292                 if (!plugin::InvokeTransformText(text, changed, m_pDispatch, m_funcid))
293                         return E_FAIL;
294                 hr = WriteFile(fileDst, text);
295                 if (FAILED(hr))
296                         return hr;
297                 *pSubcode = 0;
298                 *pbChanged = VARIANT_TRUE;
299                 *pbSuccess = VARIANT_TRUE;
300                 return S_OK;
301         }
302
303         HRESULT STDMETHODCALLTYPE PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT subcode, VARIANT_BOOL* pbSuccess) override
304         {
305                 *pbChanged = VARIANT_FALSE;
306                 *pbSuccess = VARIANT_FALSE;
307                 return S_OK;
308         }
309
310 private:
311         IDispatch* m_pDispatch;
312         int m_funcid;
313         bool m_hasArgumentsProperty;
314         bool m_hasVariablesProperty;
315 };
316
317 class InternalPlugin : public WinMergePluginBase
318 {
319 public:
320         InternalPlugin(Info&& info)
321                 : WinMergePluginBase(info.m_event, info.m_description, info.m_fileFilters, info.m_unpackedFileExtension, info.m_extendedProperties, info.m_arguments, info.m_isAutomatic)
322                 , m_info(std::move(info))
323         {
324         }
325
326         virtual ~InternalPlugin()
327         {
328         }
329
330         HRESULT STDMETHODCALLTYPE PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, VARIANT_BOOL* pbSuccess) override
331         {
332                 if (!m_info.m_prediffFile)
333                 {
334                         *pbChanged = VARIANT_FALSE;
335                         *pbSuccess = VARIANT_FALSE;
336                         return S_OK;
337                 }
338                 TempFile scriptFile;
339                 String command = replaceMacros(m_info.m_prediffFile->m_command, fileSrc, fileDst);
340                 if (m_info.m_prediffFile->m_script)
341                 {
342                         createScript(*m_info.m_prediffFile->m_script, scriptFile);
343                         strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
344                 }
345                 DWORD dwExitCode;
346                 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
347
348                 *pbChanged = SUCCEEDED(hr);
349                 *pbSuccess = SUCCEEDED(hr);
350                 return hr;
351         }
352
353         HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
354         {
355                 if (!m_info.m_unpackFile)
356                 {
357                         *pSubcode = 0;
358                         *pbChanged = VARIANT_FALSE;
359                         *pbSuccess = VARIANT_FALSE;
360                         return S_OK;
361                 }
362                 TempFile scriptFile;
363                 String command = replaceMacros(m_info.m_unpackFile->m_command, fileSrc, fileDst);
364                 if (m_info.m_unpackFile->m_script)
365                 {
366                         createScript(*m_info.m_unpackFile->m_script, scriptFile);
367                         strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
368                 }
369                 DWORD dwExitCode;
370                 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
371
372                 *pSubcode = 0;
373                 *pbChanged = SUCCEEDED(hr);
374                 *pbSuccess = SUCCEEDED(hr);
375                 return hr;
376         }
377
378         HRESULT STDMETHODCALLTYPE PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT subcode, VARIANT_BOOL* pbSuccess) override
379         {
380                 if (!m_info.m_packFile)
381                 {
382                         *pbChanged = VARIANT_FALSE;
383                         *pbSuccess = VARIANT_FALSE;
384                         return S_OK;
385                 }
386                 TempFile scriptFile;
387                 String command = replaceMacros(m_info.m_packFile->m_command, fileSrc, fileDst);
388                 if (m_info.m_packFile->m_script)
389                 {
390                         createScript(*m_info.m_packFile->m_script, scriptFile);
391                         strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
392                 }
393                 DWORD dwExitCode;
394                 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
395
396                 *pbChanged = SUCCEEDED(hr);
397                 *pbSuccess = SUCCEEDED(hr);
398                 return hr;
399         }
400
401 protected:
402
403         String replaceMacros(const String& cmd, const String & fileSrc, const String& fileDst)
404         {
405                 String command = cmd;
406                 strutils::replace(command, _T("${SRC_FILE}"), fileSrc);
407                 strutils::replace(command, _T("${DST_FILE}"), fileDst);
408                 std::vector<StringView> vars = strutils::split(m_sVariables, '\0');
409                 for (size_t i = 0; i < vars.size(); ++i)
410                         strutils::replace(command, strutils::format(_T("${%d}"), i), { vars[i].data(), vars[i].length() });
411                 strutils::replace(command, _T("${*}"), m_sArguments);
412                 return command;
413         }
414
415         static HRESULT createScript(const Script& script, TempFile& tempFile)
416         {
417                 String path = tempFile.Create(_T(""), script.m_fileExtension);
418                 return WriteFile(path, script.m_body, false);
419         }
420
421         static HRESULT launchProgram(const String& sCmd, WORD wShowWindow, DWORD &dwExitCode)
422         {
423                 TempFile stderrFile;
424                 String sOutputFile = stderrFile.Create();
425                 if (_wgetenv(L"WINMERGE_HOME") == nullptr)
426                         _wputenv_s(L"WINMERGE_HOME", env::GetProgPath().c_str());
427                 String command = sCmd;
428                 strutils::replace(command, _T("${WINMERGE_HOME}"), env::GetProgPath());
429                 STARTUPINFO stInfo = { sizeof(STARTUPINFO) };
430                 stInfo.dwFlags = STARTF_USESHOWWINDOW;
431                 stInfo.wShowWindow = wShowWindow;
432                 SECURITY_ATTRIBUTES sa{ sizeof(sa) };
433                 sa.bInheritHandle = true;
434                 stInfo.hStdError = CreateFile(sOutputFile.c_str(), GENERIC_READ | GENERIC_WRITE,
435                         FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
436                 stInfo.hStdOutput = stInfo.hStdError;
437                 stInfo.dwFlags |= STARTF_USESTDHANDLES;
438                 PROCESS_INFORMATION processInfo;
439                 bool retVal = !!CreateProcess(nullptr, (LPTSTR)command.c_str(),
440                         nullptr, nullptr, TRUE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
441                         &stInfo, &processInfo);
442                 if (!retVal)
443                         return HRESULT_FROM_WIN32(GetLastError());
444                 WaitForSingleObject(processInfo.hProcess, INFINITE);
445                 GetExitCodeProcess(processInfo.hProcess, &dwExitCode);
446                 CloseHandle(processInfo.hThread);
447                 CloseHandle(processInfo.hProcess);
448                 DWORD dwStdErrorSize = 0;
449                 if (stInfo.hStdError != nullptr && stInfo.hStdError != INVALID_HANDLE_VALUE)
450                 {
451                         DWORD dwStdErrorSizeHigh = 0;
452                         dwStdErrorSize = GetFileSize(stInfo.hStdError, &dwStdErrorSizeHigh);
453                         CloseHandle(stInfo.hStdError);
454                 }
455                 if (dwExitCode != 0 && dwStdErrorSize > 0)
456                 {
457                         String error;
458                         ReadFile(sOutputFile, error);
459                         ICreateErrorInfo* pCreateErrorInfo = nullptr;
460                         if (SUCCEEDED(CreateErrorInfo(&pCreateErrorInfo)))
461                         {
462                                 pCreateErrorInfo->SetSource(const_cast<OLECHAR*>(command.c_str()));
463                                 pCreateErrorInfo->SetDescription(const_cast<OLECHAR*>(ucr::toUTF16(error).c_str()));
464                                 IErrorInfo* pErrorInfo = nullptr;
465                                 pCreateErrorInfo->QueryInterface(&pErrorInfo);
466                                 SetErrorInfo(0, pErrorInfo);
467                                 pErrorInfo->Release();
468                                 pCreateErrorInfo->Release();
469                                 return DISP_E_EXCEPTION;
470                         }
471                 }
472                 return S_OK;
473         }
474
475 private:
476         Info m_info;
477 };
478
479 class EditorScriptGeneratedFromUnpacker: public WinMergePluginBase
480 {
481 public:
482         EditorScriptGeneratedFromUnpacker(const PluginInfo& plugin, const String& funcname)
483                 : WinMergePluginBase(
484                         L"EDITOR_SCRIPT",
485                         plugin.m_description,
486                         plugin.m_filtersTextDefault, L"", plugin.m_extendedProperties, plugin.m_arguments)
487                 , m_pDispatch(plugin.m_lpDispatch)
488         {
489                 m_pDispatch->AddRef();
490                 AddFunction(ucr::toUTF16(funcname), CallUnpackFile);
491         }
492
493         virtual ~EditorScriptGeneratedFromUnpacker()
494         {
495                 m_pDispatch->Release();
496         }
497
498         static HRESULT STDMETHODCALLTYPE CallUnpackFile(IDispatch *pDispatch, BSTR text, BSTR* pbstrResult)
499         {
500                 TempFile src, dst;
501                 String fileSrc = src.Create();
502                 String fileDst = dst.Create();
503                 HRESULT hr = WriteFile(fileSrc, text);
504                 if (FAILED(hr))
505                         return hr;
506                 int changed = 0;
507                 int subcode = 0;
508                 auto* thisObj = static_cast<EditorScriptGeneratedFromUnpacker*>(pDispatch);
509                 auto* pInternalPlugin = dynamic_cast<InternalPlugin*>(thisObj->m_pDispatch);
510                 if (pInternalPlugin)
511                 {
512                         BSTR bstrFileSrc = SysAllocString(ucr::toUTF16(fileSrc).c_str());
513                         BSTR bstrFileDst= SysAllocString(ucr::toUTF16(fileDst).c_str());
514                         VARIANT_BOOL bChanged;
515                         VARIANT_BOOL bSuccess;
516                         hr = pInternalPlugin->UnpackFile(bstrFileSrc, bstrFileDst, &bChanged, &subcode, &bSuccess);
517                         SysFreeString(bstrFileSrc);
518                         SysFreeString(bstrFileDst);
519                         if (FAILED(hr))
520                                 return hr;
521                 }
522                 else
523                 {
524                         if (!plugin::InvokeUnpackFile(fileSrc, fileDst, changed, thisObj->m_pDispatch, subcode))
525                                 return E_FAIL;
526                 }
527                 String unpackedText;
528                 hr = ReadFile(fileDst, unpackedText);
529                 if (FAILED(hr))
530                         return hr;
531                 *pbstrResult = SysAllocStringLen(ucr::toUTF16(unpackedText).c_str(), 
532                         static_cast<unsigned>(unpackedText.length()));
533                 return S_OK;
534         }
535
536 private:
537         IDispatch* m_pDispatch;
538 };
539
540 struct Loader
541 {
542         Loader()
543         {
544                 CAllThreadsScripts::RegisterInternalPluginsLoader(&Load);
545         }
546
547         static bool Load(std::map<String, PluginArrayPtr>& plugins, String& errmsg)
548         {
549                 if (plugins.find(L"EDITOR_SCRIPT") != plugins.end())
550                 {
551                         for (auto plugin : *plugins[L"EDITOR_SCRIPT"])
552                         {
553                                 if (!plugin->m_disabled && plugin->GetExtendedPropertyValue(_T("GenerateUnpacker")).has_value())
554                                 {
555                                         std::vector<String> namesArray;
556                                         std::vector<int> idArray;
557                                         int validFuncs = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
558                                         for (int i = 0; i < validFuncs; ++i)
559                                         {
560                                                 if (plugins.find(L"FILE_PACK_UNPACK") == plugins.end())
561                                                         plugins[L"FILE_PACK_UNPACK"].reset(new PluginArray);
562                                                 PluginInfoPtr pluginNew(new PluginInfo());
563                                                 IDispatch* pDispatch = new UnpackerGeneratedFromEditorScript(*plugin, namesArray[i], idArray[i]);
564                                                 pDispatch->AddRef();
565                                                 pluginNew->MakeInfo(namesArray[i], pDispatch);
566                                                 plugins[L"FILE_PACK_UNPACK"]->push_back(pluginNew);
567                                         }
568                                 }
569                         }
570                 }
571
572                 std::list<Info> internalPlugins;
573                 XMLHandler handler(&internalPlugins);
574                 SAXParser parser;
575                 parser.setContentHandler(&handler);
576                 try
577                 {
578                         for (const auto& path : {
579                                 paths::ConcatPath(env::GetProgPath(), _T("MergePlugins\\Plugins.xml")),
580                                 env::ExpandEnvironmentVariables(_T("%APPDATA%\\WinMerge\\MergePlugins\\Plugins.xml"))
581                                 })
582                         {
583                                 try
584                                 {
585                                         parser.parse(ucr::toUTF8(path));
586                                 }
587                                 catch (Poco::FileNotFoundException&)
588                                 {
589                                 }
590                         }
591                 }
592                 catch (Poco::XML::SAXParseException& e)
593                 {
594                         errmsg = ucr::toTString(e.message());
595                         return false;
596                 }
597
598                 for (auto& info : internalPlugins)
599                 {
600                         String event = info.m_event;
601                         String name = info.m_name;
602                         if (plugins.find(event) == plugins.end())
603                                 plugins[event].reset(new PluginArray);
604                         PluginInfoPtr pluginNew(new PluginInfo());
605                         IDispatch* pDispatch = new InternalPlugin(std::move(info));
606                         pDispatch->AddRef();
607                         pluginNew->MakeInfo(name, pDispatch);
608                         plugins[event]->push_back(pluginNew);
609                 }
610
611                 if (plugins.find(L"FILE_PACK_UNPACK") != plugins.end())
612                 {
613                         for (auto plugin : *plugins[L"FILE_PACK_UNPACK"])
614                         {
615                                 if (!plugin->m_disabled && plugin->GetExtendedPropertyValue(_T("GenerateEditorScript")).has_value())
616                                 {
617                                         if (plugins.find(L"EDITOR_SCRIPT") == plugins.end())
618                                                 plugins[L"EDITOR_SCRIPT"].reset(new PluginArray);
619                                         PluginInfoPtr pluginNew(new PluginInfo());
620                                         auto menuCaption =  plugin->GetExtendedPropertyValue(_T("MenuCaption"));
621                                         String funcname = menuCaption.has_value() ? String{menuCaption->data(), menuCaption->length() } : plugin->m_name;
622                                         IDispatch* pDispatch = new EditorScriptGeneratedFromUnpacker(*plugin, funcname);
623                                         pDispatch->AddRef();
624                                         pluginNew->MakeInfo(plugin->m_name, pDispatch);
625                                         plugins[L"EDITOR_SCRIPT"]->push_back(pluginNew);
626                                 }
627                         }
628                 }
629
630                 return true;
631         }
632 } g_loader;
633
634 }