OSDN Git Service

Improve plugin system (#797)
[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         }
273
274         virtual ~UnpackerGeneratedFromEditorScript()
275         {
276                 m_pDispatch->Release();
277         }
278
279         HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
280         {
281                 String text;
282                 HRESULT hr = ReadFile(fileSrc, text);
283                 if (FAILED(hr))
284                         return hr;
285                 if (m_hasVariablesProperty && !plugin::InvokePutPluginVariables(ucr::toTString(fileSrc), m_pDispatch))
286                         return E_FAIL;
287                 if (m_hasArgumentsProperty && !plugin::InvokePutPluginArguments(m_sArguments, m_pDispatch))
288                         return E_FAIL;
289                 int changed = 0;
290                 if (!plugin::InvokeTransformText(text, changed, m_pDispatch, m_funcid))
291                         return E_FAIL;
292                 hr = WriteFile(fileDst, text);
293                 if (FAILED(hr))
294                         return hr;
295                 *pSubcode = 0;
296                 *pbChanged = VARIANT_TRUE;
297                 *pbSuccess = VARIANT_TRUE;
298                 return S_OK;
299         }
300
301         HRESULT STDMETHODCALLTYPE PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT subcode, VARIANT_BOOL* pbSuccess) override
302         {
303                 *pbChanged = VARIANT_FALSE;
304                 *pbSuccess = VARIANT_FALSE;
305                 return S_OK;
306         }
307
308 private:
309         IDispatch* m_pDispatch;
310         int m_funcid;
311         bool m_hasArgumentsProperty;
312         bool m_hasVariablesProperty;
313 };
314
315 class InternalPlugin : public WinMergePluginBase
316 {
317 public:
318         InternalPlugin(Info&& info)
319                 : WinMergePluginBase(info.m_event, info.m_description, info.m_fileFilters, info.m_unpackedFileExtension, info.m_extendedProperties, info.m_arguments, info.m_isAutomatic)
320                 , m_info(std::move(info))
321         {
322         }
323
324         virtual ~InternalPlugin()
325         {
326         }
327
328         HRESULT STDMETHODCALLTYPE PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, VARIANT_BOOL* pbSuccess) override
329         {
330                 if (!m_info.m_prediffFile)
331                 {
332                         *pbChanged = VARIANT_FALSE;
333                         *pbSuccess = VARIANT_FALSE;
334                         return S_OK;
335                 }
336                 TempFile scriptFile;
337                 String command = replaceMacros(m_info.m_prediffFile->m_command, fileSrc, fileDst);
338                 if (m_info.m_prediffFile->m_script)
339                 {
340                         createScript(*m_info.m_prediffFile->m_script, scriptFile);
341                         strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
342                 }
343                 DWORD dwExitCode;
344                 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
345
346                 *pbChanged = SUCCEEDED(hr);
347                 *pbSuccess = SUCCEEDED(hr);
348                 return hr;
349         }
350
351         HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
352         {
353                 if (!m_info.m_unpackFile)
354                 {
355                         *pSubcode = 0;
356                         *pbChanged = VARIANT_FALSE;
357                         *pbSuccess = VARIANT_FALSE;
358                         return S_OK;
359                 }
360                 TempFile scriptFile;
361                 String command = replaceMacros(m_info.m_unpackFile->m_command, fileSrc, fileDst);
362                 if (m_info.m_unpackFile->m_script)
363                 {
364                         createScript(*m_info.m_unpackFile->m_script, scriptFile);
365                         strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
366                 }
367                 DWORD dwExitCode;
368                 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
369
370                 *pSubcode = 0;
371                 *pbChanged = SUCCEEDED(hr);
372                 *pbSuccess = SUCCEEDED(hr);
373                 return hr;
374         }
375
376         HRESULT STDMETHODCALLTYPE PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT subcode, VARIANT_BOOL* pbSuccess) override
377         {
378                 if (!m_info.m_packFile)
379                 {
380                         *pbChanged = VARIANT_FALSE;
381                         *pbSuccess = VARIANT_FALSE;
382                         return S_OK;
383                 }
384                 TempFile scriptFile;
385                 String command = replaceMacros(m_info.m_packFile->m_command, fileSrc, fileDst);
386                 if (m_info.m_packFile->m_script)
387                 {
388                         createScript(*m_info.m_packFile->m_script, scriptFile);
389                         strutils::replace(command, _T("${SCRIPT_FILE}"), scriptFile.GetPath());
390                 }
391                 DWORD dwExitCode;
392                 HRESULT hr = launchProgram(command, SW_HIDE, dwExitCode);
393
394                 *pbChanged = SUCCEEDED(hr);
395                 *pbSuccess = SUCCEEDED(hr);
396                 return hr;
397         }
398
399 protected:
400
401         String replaceMacros(const String& cmd, const String & fileSrc, const String& fileDst)
402         {
403                 String command = cmd;
404                 strutils::replace(command, _T("${SRC_FILE}"), fileSrc);
405                 strutils::replace(command, _T("${DST_FILE}"), fileDst);
406                 std::vector<StringView> vars = strutils::split(m_sVariables, '\0');
407                 for (size_t i = 0; i < vars.size(); ++i)
408                         strutils::replace(command, strutils::format(_T("${%d}"), i), { vars[i].data(), vars[i].length() });
409                 strutils::replace(command, _T("${*}"), m_sArguments);
410                 return command;
411         }
412
413         static HRESULT createScript(const Script& script, TempFile& tempFile)
414         {
415                 String path = tempFile.Create(_T(""), script.m_fileExtension);
416                 return WriteFile(path, script.m_body, false);
417         }
418
419         static HRESULT launchProgram(const String& sCmd, WORD wShowWindow, DWORD &dwExitCode)
420         {
421                 TempFile stderrFile;
422                 String sOutputFile = stderrFile.Create();
423                 if (_wgetenv(L"WINMERGE_HOME") == nullptr)
424                         _wputenv_s(L"WINMERGE_HOME", env::GetProgPath().c_str());
425                 String command = sCmd;
426                 strutils::replace(command, _T("${WINMERGE_HOME}"), env::GetProgPath());
427                 STARTUPINFO stInfo = { sizeof(STARTUPINFO) };
428                 stInfo.dwFlags = STARTF_USESHOWWINDOW;
429                 stInfo.wShowWindow = wShowWindow;
430                 SECURITY_ATTRIBUTES sa{ sizeof(sa) };
431                 sa.bInheritHandle = true;
432                 stInfo.hStdError = CreateFile(sOutputFile.c_str(), GENERIC_READ | GENERIC_WRITE,
433                         FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
434                 stInfo.hStdOutput = stInfo.hStdError;
435                 stInfo.dwFlags |= STARTF_USESTDHANDLES;
436                 PROCESS_INFORMATION processInfo;
437                 bool retVal = !!CreateProcess(nullptr, (LPTSTR)command.c_str(),
438                         nullptr, nullptr, TRUE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
439                         &stInfo, &processInfo);
440                 if (!retVal)
441                         return HRESULT_FROM_WIN32(GetLastError());
442                 WaitForSingleObject(processInfo.hProcess, INFINITE);
443                 GetExitCodeProcess(processInfo.hProcess, &dwExitCode);
444                 CloseHandle(processInfo.hThread);
445                 CloseHandle(processInfo.hProcess);
446                 DWORD dwStdErrorSize = 0;
447                 if (stInfo.hStdError != nullptr && stInfo.hStdError != INVALID_HANDLE_VALUE)
448                 {
449                         DWORD dwStdErrorSizeHigh = 0;
450                         dwStdErrorSize = GetFileSize(stInfo.hStdError, &dwStdErrorSizeHigh);
451                         CloseHandle(stInfo.hStdError);
452                 }
453                 if (dwExitCode != 0 && dwStdErrorSize > 0)
454                 {
455                         String error;
456                         ReadFile(sOutputFile, error);
457                         ICreateErrorInfo* pCreateErrorInfo = nullptr;
458                         if (SUCCEEDED(CreateErrorInfo(&pCreateErrorInfo)))
459                         {
460                                 pCreateErrorInfo->SetSource(const_cast<OLECHAR*>(command.c_str()));
461                                 pCreateErrorInfo->SetDescription(const_cast<OLECHAR*>(ucr::toUTF16(error).c_str()));
462                                 IErrorInfo* pErrorInfo = nullptr;
463                                 pCreateErrorInfo->QueryInterface(&pErrorInfo);
464                                 SetErrorInfo(0, pErrorInfo);
465                                 pErrorInfo->Release();
466                                 pCreateErrorInfo->Release();
467                                 return DISP_E_EXCEPTION;
468                         }
469                 }
470                 return S_OK;
471         }
472
473 private:
474         Info m_info;
475 };
476
477 class EditorScriptGeneratedFromUnpacker: public WinMergePluginBase
478 {
479 public:
480         EditorScriptGeneratedFromUnpacker(const PluginInfo& plugin, const String& funcname)
481                 : WinMergePluginBase(
482                         L"EDITOR_SCRIPT",
483                         plugin.m_description,
484                         plugin.m_filtersTextDefault, L"", plugin.m_extendedProperties, plugin.m_arguments)
485                 , m_pDispatch(plugin.m_lpDispatch)
486         {
487                 m_pDispatch->AddRef();
488                 AddFunction(ucr::toUTF16(funcname), CallUnpackFile);
489         }
490
491         virtual ~EditorScriptGeneratedFromUnpacker()
492         {
493                 m_pDispatch->Release();
494         }
495
496         static HRESULT STDMETHODCALLTYPE CallUnpackFile(IDispatch *pDispatch, BSTR text, BSTR* pbstrResult)
497         {
498                 TempFile src, dst;
499                 String fileSrc = src.Create();
500                 String fileDst = dst.Create();
501                 HRESULT hr = WriteFile(fileSrc, text);
502                 if (FAILED(hr))
503                         return hr;
504                 int changed = 0;
505                 int subcode = 0;
506                 auto* thisObj = static_cast<EditorScriptGeneratedFromUnpacker*>(pDispatch);
507                 auto* pInternalPlugin = dynamic_cast<InternalPlugin*>(thisObj->m_pDispatch);
508                 if (pInternalPlugin)
509                 {
510                         BSTR bstrFileSrc = SysAllocString(ucr::toUTF16(fileSrc).c_str());
511                         BSTR bstrFileDst= SysAllocString(ucr::toUTF16(fileDst).c_str());
512                         VARIANT_BOOL bChanged;
513                         VARIANT_BOOL bSuccess;
514                         hr = pInternalPlugin->UnpackFile(bstrFileSrc, bstrFileDst, &bChanged, &subcode, &bSuccess);
515                         SysFreeString(bstrFileSrc);
516                         SysFreeString(bstrFileDst);
517                         if (FAILED(hr))
518                                 return hr;
519                 }
520                 else
521                 {
522                         if (!plugin::InvokeUnpackFile(fileSrc, fileDst, changed, thisObj->m_pDispatch, subcode))
523                                 return E_FAIL;
524                 }
525                 String unpackedText;
526                 hr = ReadFile(fileDst, unpackedText);
527                 if (FAILED(hr))
528                         return hr;
529                 *pbstrResult = SysAllocStringLen(ucr::toUTF16(unpackedText).c_str(), 
530                         static_cast<unsigned>(unpackedText.length()));
531                 return S_OK;
532         }
533
534 private:
535         IDispatch* m_pDispatch;
536 };
537
538 struct Loader
539 {
540         Loader()
541         {
542                 CAllThreadsScripts::RegisterInternalPluginsLoader(&Load);
543         }
544
545         static bool Load(std::map<String, PluginArrayPtr>& plugins, String& errmsg)
546         {
547                 if (plugins.find(L"EDITOR_SCRIPT") != plugins.end())
548                 {
549                         for (auto plugin : *plugins[L"EDITOR_SCRIPT"])
550                         {
551                                 if (!plugin->m_disabled && plugin->GetExtendedPropertyValue(_T("GenerateUnpacker")).has_value())
552                                 {
553                                         std::vector<String> namesArray;
554                                         std::vector<int> idArray;
555                                         int validFuncs = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
556                                         for (int i = 0; i < validFuncs; ++i)
557                                         {
558                                                 if (plugins.find(L"FILE_PACK_UNPACK") == plugins.end())
559                                                         plugins[L"FILE_PACK_UNPACK"].reset(new PluginArray);
560                                                 PluginInfoPtr pluginNew(new PluginInfo());
561                                                 IDispatch* pDispatch = new UnpackerGeneratedFromEditorScript(*plugin, namesArray[i], idArray[i]);
562                                                 pDispatch->AddRef();
563                                                 pluginNew->MakeInfo(namesArray[i], pDispatch);
564                                                 plugins[L"FILE_PACK_UNPACK"]->push_back(pluginNew);
565                                         }
566                                 }
567                         }
568                 }
569
570                 std::list<Info> internalPlugins;
571                 XMLHandler handler(&internalPlugins);
572                 SAXParser parser;
573                 parser.setContentHandler(&handler);
574                 try
575                 {
576                         parser.parse(ucr::toUTF8(paths::ConcatPath(env::GetProgPath(), _T("MergePlugins\\Plugins.xml"))));
577                 }
578                 catch (Poco::XML::SAXParseException& e)
579                 {
580                         errmsg = ucr::toTString(e.message());
581                         return false;
582                 }
583                 catch (Poco::FileNotFoundException&)
584                 {
585                 }
586
587                 for (auto& info : internalPlugins)
588                 {
589                         String event = info.m_event;
590                         String name = info.m_name;
591                         if (plugins.find(event) == plugins.end())
592                                 plugins[event].reset(new PluginArray);
593                         PluginInfoPtr pluginNew(new PluginInfo());
594                         IDispatch* pDispatch = new InternalPlugin(std::move(info));
595                         pDispatch->AddRef();
596                         pluginNew->MakeInfo(name, pDispatch);
597                         plugins[event]->push_back(pluginNew);
598                 }
599
600                 if (plugins.find(L"FILE_PACK_UNPACK") != plugins.end())
601                 {
602                         for (auto plugin : *plugins[L"FILE_PACK_UNPACK"])
603                         {
604                                 if (!plugin->m_disabled && plugin->GetExtendedPropertyValue(_T("GenerateEditorScript")).has_value())
605                                 {
606                                         if (plugins.find(L"EDITOR_SCRIPT") == plugins.end())
607                                                 plugins[L"EDITOR_SCRIPT"].reset(new PluginArray);
608                                         PluginInfoPtr pluginNew(new PluginInfo());
609                                         auto menuCaption =  plugin->GetExtendedPropertyValue(_T("MenuCaption"));
610                                         String funcname = menuCaption.has_value() ? String{menuCaption->data(), menuCaption->length() } : plugin->m_name;
611                                         IDispatch* pDispatch = new EditorScriptGeneratedFromUnpacker(*plugin, funcname);
612                                         pDispatch->AddRef();
613                                         pluginNew->MakeInfo(plugin->m_name, pDispatch);
614                                         plugins[L"EDITOR_SCRIPT"]->push_back(pluginNew);
615                                 }
616                         }
617                 }
618
619                 return true;
620         }
621 } g_loader;
622
623 }