OSDN Git Service

8129ae7c3b8e3cf531b2e10c5088f12c9e86718d
[winmerge-jp/winmerge-jp.git] / Src / Plugins.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /**
8  *  @file Plugins.cpp
9  *
10  *  @brief Support for VBS Scriptlets, VB ActiveX DLL, VC++ COM DLL
11  */ 
12
13 #include "pch.h"
14 #include "Plugins.h"
15 #define POCO_NO_UNWINDOWS 1
16 #include <vector>
17 #include <list>
18 #include <unordered_set>
19 #include <algorithm>
20 #include <cassert>
21 #include <iostream>
22 #include <sstream>
23 #include <Poco/Mutex.h>
24 #include <Poco/ScopedLock.h>
25 #include <Poco/RegularExpression.h>
26 #include <windows.h>
27 #include "MergeApp.h"
28 #include "unicoder.h"
29 #include "FileFilterMgr.h"
30 #include "lwdisp.h"
31 #include "resource.h"
32 #include "Exceptions.h"
33 #include "RegKey.h"
34 #include "paths.h"
35 #include "Environment.h"
36 #include "FileFilter.h"
37 #include "coretools.h"
38 #include "OptionsMgr.h"
39 #include "OptionsDef.h"
40 #include "codepage_detect.h"
41 #include "UniFile.h"
42 #include "WinMergePluginBase.h"
43
44 using std::vector;
45 using Poco::RegularExpression;
46 using Poco::FastMutex;
47 using Poco::ScopedLock;
48
49 /**
50  * @brief Category of transformation : define the transformation events
51  *
52  * @note USER categories are calls to scriptlets, or VB ActiveX DLL, or VC COM DLL
53  * Use text definition : if you add one, nothing to do ; 
54  * if you change one, you just have change the dll/scripts for that event
55  */
56 const wchar_t *TransformationCategories[] = 
57 {
58         L"BUFFER_PREDIFF",
59         L"FILE_PREDIFF",
60         L"EDITOR_SCRIPT",
61         L"BUFFER_PACK_UNPACK",
62         L"FILE_PACK_UNPACK",
63         L"FILE_FOLDER_PACK_UNPACK",
64         nullptr,                // last empty : necessary
65 };
66
67 static vector<String> theScriptletList;
68 /// Need to lock the *.sct so the user can't delete them
69 static vector<HANDLE> theScriptletHandleList;
70 static bool scriptletsLoaded=false;
71 static FastMutex scriptletsSem;
72 static std::unordered_map<String, String> customFiltersMap;
73
74 template<class T> struct AutoReleaser
75 {
76         explicit AutoReleaser(T *ptr) : p(ptr) {}
77         ~AutoReleaser() { if (p!=nullptr) p->Release(); }
78         T *p;
79 };
80
81 ////////////////////////////////////////////////////////////////////////////////
82
83 namespace plugin
84 {
85
86 /**
87  * @brief Check for the presence of Windows Script
88  *
89  * .sct plugins require this optional component
90  */
91 bool IsWindowsScriptThere()
92 {
93         CRegKeyEx keyFile;
94         if (!keyFile.QueryRegMachine(_T("SOFTWARE\\Classes\\scriptletfile\\AutoRegister")))
95                 return false;
96
97         String filename = keyFile.ReadString(_T(""), _T(""));
98         keyFile.Close();
99         if (filename.empty())
100                 return false;
101
102         return (paths::DoesPathExist(filename) == paths::IS_EXISTING_FILE);
103 }
104
105 ////////////////////////////////////////////////////////////////////////////////
106 // scriptlet/activeX support for function names
107
108 // list the function IDs and names in a script or activeX dll
109 int GetFunctionsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int>& IdArray, INVOKEKIND wantedKind)
110 {
111         UINT iValidFunc = 0;
112         if (piDispatch != nullptr)
113         {
114                 ITypeInfo *piTypeInfo=nullptr;
115                 unsigned  iTInfo = 0; // 0 for type information of IDispatch itself
116                 LCID  lcid=0; // locale for localized method names (ignore if no localized names)
117                 if (SUCCEEDED(piDispatch->GetTypeInfo(iTInfo, lcid, &piTypeInfo)))
118                 {
119                         TYPEATTR *pTypeAttr=nullptr;
120                         if (SUCCEEDED(piTypeInfo->GetTypeAttr(&pTypeAttr)))
121                         {
122                                 // allocate arrays for the returned structures
123                                 // the names array is NUL terminated
124                                 namesArray.resize(pTypeAttr->cFuncs+1);
125                                 IdArray.resize(pTypeAttr->cFuncs+1);
126
127                                 UINT iMaxFunc = pTypeAttr->cFuncs - 1;
128                                 for (UINT iFunc = 0 ; iFunc <= iMaxFunc ; ++iFunc)
129                                 {
130                                         UINT iFuncDesc = iMaxFunc - iFunc;
131                                         FUNCDESC *pFuncDesc;
132                                         if (SUCCEEDED(piTypeInfo->GetFuncDesc(iFuncDesc, &pFuncDesc)))
133                                         {
134                                                 // exclude properties
135                                                 // exclude IDispatch inherited methods
136                                                 if (pFuncDesc->invkind & wantedKind && !(pFuncDesc->wFuncFlags & 1))
137                                                 {
138                                                         BSTR bstrName;
139                                                         UINT cNames;
140                                                         if (SUCCEEDED(piTypeInfo->GetNames(pFuncDesc->memid,
141                                                                 &bstrName, 1, &cNames)))
142                                                         {
143                                                                 IdArray[iValidFunc] = pFuncDesc->memid;
144                                                                 namesArray[iValidFunc] = ucr::toTString(bstrName);
145                                                                 iValidFunc ++;
146                                                         }
147                                                         SysFreeString(bstrName);
148                                                 }
149                                                 piTypeInfo->ReleaseFuncDesc(pFuncDesc);
150                                         }
151                                 }
152                                 piTypeInfo->ReleaseTypeAttr(pTypeAttr);
153                         }
154                         piTypeInfo->Release();
155                 }
156         }
157         return iValidFunc;
158 }
159
160 int GetMethodsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int> &IdArray)
161 {
162         return GetFunctionsFromScript(piDispatch, namesArray, IdArray, INVOKE_FUNC);
163 }
164 int GetPropertyGetsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int> &IdArray)
165 {
166         return GetFunctionsFromScript(piDispatch, namesArray, IdArray, INVOKE_PROPERTYGET);
167 }
168
169
170 // search a function name in a scriptlet or activeX dll
171 bool SearchScriptForMethodName(LPDISPATCH piDispatch, const wchar_t *functionName)
172 {
173         vector<String> namesArray;
174         vector<int> IdArray;
175         const int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
176
177         const String tfuncname = ucr::toTString(functionName);
178         for (int iFnc = 0 ; iFnc < nFnc ; iFnc++)
179         {
180                 if (namesArray[iFnc] == tfuncname)
181                         return true;
182         }
183         return false;
184 }
185
186 // search a property name (with get interface) in a scriptlet or activeX dll
187 bool SearchScriptForDefinedProperties(IDispatch *piDispatch, const wchar_t *functionName)
188 {
189         vector<String> namesArray;
190         vector<int> IdArray;
191         const int nFnc = GetPropertyGetsFromScript(piDispatch, namesArray, IdArray);
192
193         const String tfuncname = ucr::toTString(functionName);
194         for (int iFnc = 0 ; iFnc < nFnc ; iFnc++)
195         {
196                 if (namesArray[iFnc] == tfuncname)
197                         return true;
198         }
199         return false;
200 }
201
202
203 int CountMethodsInScript(LPDISPATCH piDispatch)
204 {
205         vector<String> namesArray;
206         vector<int> IdArray;
207         return GetMethodsFromScript(piDispatch, namesArray, IdArray);
208 }
209
210 /** 
211  * @return ID of the function or -1 if no function with this index
212  */
213 int GetMethodIDInScript(LPDISPATCH piDispatch, int methodIndex)
214 {
215         int fncID;
216
217         vector<String> namesArray;
218         vector<int> IdArray;
219         const int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
220
221         if (methodIndex < nFnc)
222         {
223                 fncID = IdArray[methodIndex];
224         }
225         else
226         {
227                 fncID = -1;
228         }
229         
230         return fncID;
231 }
232
233 }
234
235 ////////////////////////////////////////////////////////////////////////////////
236 // find scripts/activeX for an event : each event is assigned to a subdirectory 
237
238
239 /**
240  * @brief Get a list of scriptlet file
241  *
242  * @return Returns an array of LPSTR
243  */
244 static void GetScriptletsAt(const String& sSearchPath, const String& extension, vector<String>& scriptlets )
245 {
246         WIN32_FIND_DATA ffi;
247         String strFileSpec = paths::ConcatPath(sSearchPath, _T("*") + extension);
248         HANDLE hff = FindFirstFile(strFileSpec.c_str(), &ffi);
249         
250         if (  hff != INVALID_HANDLE_VALUE )
251         {
252                 do
253                 {
254                         if (!(ffi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
255                         {
256                                 strFileSpec = paths::ConcatPath(sSearchPath, ffi.cFileName);
257                                 if (strFileSpec.substr(strFileSpec.length() - extension.length()) == extension) // excludes *.sct~ files
258                                         scriptlets.push_back(strFileSpec);  
259                         }
260                 }
261                 while (FindNextFile(hff, &ffi));
262                 FindClose(hff);
263         }
264 }
265
266 void PluginInfo::LoadFilterString()
267 {
268         m_filters.clear();
269
270         String sLine(m_filtersText);
271         String sPiece;
272
273         while(1)
274         {
275                 String::size_type pos = sLine.rfind(';');
276                 sPiece = sLine.substr(pos+1);
277                 if (pos == String::npos)
278                         pos = 0;
279                 sLine = sLine.substr(0, pos);
280                 if (sPiece.empty())
281                         break;
282
283                 sPiece = strutils::makeupper(strutils::trim_ws_begin(sPiece));
284
285                 int re_opts = 0;
286                 std::string regexString = ucr::toUTF8(sPiece);
287                 re_opts |= RegularExpression::RE_UTF8;
288                 try
289                 {
290                         m_filters.push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
291                 }
292                 catch (...)
293                 {
294                         // TODO:
295                 }
296         };
297 }
298
299
300 bool PluginInfo::TestAgainstRegList(const String& szTest) const
301 {
302         if (m_filters.empty() || szTest.empty())
303                 return false;
304
305         String sLine = szTest;
306         String sPiece;
307
308         while(!sLine.empty())
309         {
310                 String::size_type pos = sLine.rfind('|');
311                 sPiece = sLine.substr(pos+1);
312                 if (pos == String::npos)
313                         pos = 0;
314                 sLine = sLine.substr(0, pos);
315                 if (sPiece.empty())
316                         continue;
317                 sPiece = strutils::makeupper(strutils::trim_ws_begin(sPiece));
318
319                 if (::TestAgainstRegList(&m_filters, sPiece))
320                         return true;
321         };
322
323         return false;
324 }
325
326 /**
327  * @brief Log technical explanation, in English, of script error
328  */
329 static void
330 ScriptletError(const String & scriptletFilepath, const TCHAR *szError)
331 {
332         String msg = _T("Plugin scriptlet error <")
333                 + scriptletFilepath
334                 + _T("> ")
335                 + szError;
336     LogErrorString(msg);
337 }
338
339 static std::unordered_set<String> GetDisabledPluginList()
340 {
341         std::unordered_set<String> list;
342         std::basic_stringstream<TCHAR> ss(GetOptionsMgr()->GetString(OPT_PLUGINS_DISABLED_LIST));
343         String name;
344         while (std::getline(ss, name, _T('|')))
345                 list.insert(name);
346         return list;
347 }
348
349 static std::unordered_map<String, String> GetCustomFiltersMap()
350 {
351         std::unordered_map<String, String> map;
352         std::basic_stringstream<TCHAR> ss(GetOptionsMgr()->GetString(OPT_PLUGINS_CUSTOM_FILTERS_LIST));
353         String nameAndFiltersText;
354         while (std::getline(ss, nameAndFiltersText, _T('\t')))
355         {
356                 size_t pos = nameAndFiltersText.find_first_of(':');
357                 if (pos != String::npos)
358                 {
359                         String name = nameAndFiltersText.substr(0, pos);
360                         String filtersText = nameAndFiltersText.substr(pos + 1);
361                         map.emplace(name, filtersText);
362                 }
363         }
364         map.emplace(_T("||initialized||"), _T(""));
365         return map;
366 }
367
368 static String GetCustomFilters(const String& name, const String& filtersTextDefault)
369 {
370         FastMutex::ScopedLock lock(scriptletsSem);
371         if (customFiltersMap.empty())
372                 customFiltersMap = GetCustomFiltersMap();
373         if (customFiltersMap.find(name) != customFiltersMap.end())
374                 return customFiltersMap[name];
375         else
376                 return filtersTextDefault;
377 }
378
379 class UnpackerGeneratedFromEditorScript: public WinMergePluginBase
380 {
381 public:
382         UnpackerGeneratedFromEditorScript(IDispatch *pDispatch, const std::wstring funcname, int id)
383                 : WinMergePluginBase(
384                         L"FILE_PACK_UNPACK",
385                         strutils::format_string1(_T("Unpacker to execute %1 script (automatically generated)") , funcname),
386                         _T("\\.nomatch$"))
387                 , m_pDispatch(pDispatch)
388                 , m_funcid(id)
389         {
390                 m_pDispatch->AddRef();
391         }
392
393         virtual ~UnpackerGeneratedFromEditorScript()
394         {
395                 m_pDispatch->Release();
396         }
397
398         static HRESULT ReadFile(const String& path, String& text)
399         {
400                 UniMemFile file;
401                 if (!file.OpenReadOnly(path))
402                         return E_ACCESSDENIED;
403                 file.ReadBom();
404                 if (!file.HasBom())
405                 {
406                         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
407                         int64_t fileSize = file.GetFileSize();
408                         FileTextEncoding encoding = codepage_detect::Guess(
409                                 paths::FindExtension(path), file.GetBase(), static_cast<size_t>(
410                                         fileSize < static_cast<int64_t>(codepage_detect::BufSize) ?
411                                                 fileSize : static_cast<int64_t>(codepage_detect::BufSize)),
412                                 iGuessEncodingType);
413                         file.SetCodepage(encoding.m_codepage);
414                 }
415                 file.ReadStringAll(text);
416                 file.Close();
417                 return S_OK;
418         }
419
420         static HRESULT WriteFile(const String& path, const String& text)
421         {
422                 UniStdioFile fileOut;
423                 if (!fileOut.Open(path, _T("wb")))
424                         return E_ACCESSDENIED;
425                 fileOut.SetUnicoding(ucr::UNICODESET::UTF8);
426                 fileOut.SetBom(true);
427                 fileOut.WriteBom();
428                 fileOut.WriteString(text);
429                 fileOut.Close();
430                 return S_OK;
431         }
432
433         HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
434         {
435                 String text;
436                 HRESULT hr = ReadFile(fileSrc, text);
437                 if (FAILED(hr))
438                         return hr;
439                 int changed = 0;
440                 if (!plugin::InvokeTransformText(text, changed, m_pDispatch, m_funcid))
441                         return E_FAIL;
442                 hr = WriteFile(fileDst, text);
443                 if (FAILED(hr))
444                         return hr;
445                 *pSubcode = 0;
446                 *pbChanged = VARIANT_TRUE;
447                 *pbSuccess = VARIANT_TRUE;
448                 return S_OK;
449         }
450
451 private:
452         IDispatch *m_pDispatch;
453         int m_funcid;
454 };
455
456 /**
457  * @brief Tiny structure that remembers current scriptlet & event info for calling Log
458  */
459 struct ScriptInfo
460 {
461         ScriptInfo(const String & scriptletFilepath)
462                 : m_scriptletFilepath(scriptletFilepath)
463         {
464         }
465         void Log(const TCHAR *szError)
466         {
467                 ScriptletError(m_scriptletFilepath, szError);
468         }
469         const String & m_scriptletFilepath;
470 };
471
472 /**
473  * @brief Try to load a plugin
474  *
475  * @return 1 if loaded plugin successfully, negatives for errors
476  */
477 int PluginInfo::MakeInfo(const String & scriptletFilepath, IDispatch *lpDispatch)
478 {
479         // set up object in case we need to log info
480         ScriptInfo scinfo(scriptletFilepath);
481
482         // Ensure that interface is released if any bad exit or exception
483         AutoReleaser<IDispatch> drv(lpDispatch);
484
485         // Is this plugin for this transformationEvent ?
486         VARIANT ret;
487         // invoke mandatory method get PluginEvent
488         VariantInit(&ret);
489         if (!plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginEvent"))
490         {
491                 scinfo.Log(_T("PluginEvent method missing"));
492                 return -20; // error
493         }
494         HRESULT h = ::invokeW(lpDispatch, &ret, L"PluginEvent", opGet[0], nullptr);
495         if (FAILED(h) || ret.vt != VT_BSTR)
496         {
497                 scinfo.Log(     _T("Error accessing PluginEvent method"));
498                 return -30; // error
499         }
500         m_event = ucr::toTString(ret.bstrVal);
501
502         VariantClear(&ret);
503
504         // plugins PREDIFF or PACK_UNPACK : functions names are mandatory
505         // Check that the plugin offers the requested functions
506         // set the mode for the events which uses it
507         bool bFound = true;
508         if (m_event == _T("BUFFER_PREDIFF"))
509         {
510                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PrediffBufferW");
511         }
512         else if (m_event == _T("FILE_PREDIFF"))
513         {
514                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PrediffFile");
515         }
516         else if (m_event == _T("BUFFER_PACK_UNPACK"))
517         {
518                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackBufferA");
519                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackBufferA");
520         }
521         else if (m_event == _T("FILE_PACK_UNPACK"))
522         {
523                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFile");
524                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFile");
525         }
526         else if (m_event == _T("FILE_FOLDER_PACK_UNPACK"))
527         {
528                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"IsFolder");
529                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFile");
530                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFile");
531                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFolder");
532                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFolder");
533         }
534         if (!bFound)
535         {
536                 // error (Plugin doesn't support the method as it claimed)
537                 scinfo.Log(_T("Plugin doesn't support the method as it claimed"));
538                 return -40; 
539         }
540
541         // plugins EDITOR_SCRIPT : functions names are free
542         // there may be several functions inside one script, count the number of functions
543         if (m_event == _T("EDITOR_SCRIPT"))
544         {
545                 m_nFreeFunctions = plugin::CountMethodsInScript(lpDispatch);
546                 if (m_nFreeFunctions == 0)
547                         // error (Plugin doesn't offer any method, what is this ?)
548                         return -50;
549         }
550
551
552         // get optional property PluginDescription
553         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginDescription"))
554         {
555                 h = ::invokeW(lpDispatch, &ret, L"PluginDescription", opGet[0], nullptr);
556                 if (FAILED(h) || ret.vt != VT_BSTR)
557                 {
558                         scinfo.Log(_T("Plugin had PluginDescription property, but error getting its value"));
559                         return -60; // error (Plugin had PluginDescription property, but error getting its value)
560                 }
561                 m_description = ucr::toTString(ret.bstrVal);
562         }
563         else
564         {
565                 // no description, use filename
566                 m_description = paths::FindFileName(scriptletFilepath);
567         }
568         VariantClear(&ret);
569
570         // get PluginFileFilters
571         bool hasPluginFileFilters = false;
572         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginFileFilters"))
573         {
574                 h = ::invokeW(lpDispatch, &ret, L"PluginFileFilters", opGet[0], nullptr);
575                 if (FAILED(h) || ret.vt != VT_BSTR)
576                 {
577                         scinfo.Log(_T("Plugin had PluginFileFilters property, but error getting its value"));
578                         return -70; // error (Plugin had PluginFileFilters property, but error getting its value)
579                 }
580                 m_filtersTextDefault = ucr::toTString(ret.bstrVal);
581                 hasPluginFileFilters = true;
582         }
583         else
584         {
585                 m_bAutomatic = false;
586                 m_filtersTextDefault = _T(".");
587         }
588         VariantClear(&ret);
589
590         // get optional property PluginIsAutomatic
591         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginIsAutomatic"))
592         {
593                 h = ::invokeW(lpDispatch, &ret, L"PluginIsAutomatic", opGet[0], nullptr);
594                 if (FAILED(h) || ret.vt != VT_BOOL)
595                 {
596                         scinfo.Log(_T("Plugin had PluginIsAutomatic property, but error getting its value"));
597                         return -80; // error (Plugin had PluginIsAutomatic property, but error getting its value)
598                 }
599                 m_bAutomatic = !!ret.boolVal;
600         }
601         else
602         {
603                 if (hasPluginFileFilters)
604                 {
605                         scinfo.Log(_T("Plugin had PluginFileFilters property, but lacked PluginIsAutomatic property"));
606                         // PluginIsAutomatic property is mandatory for Plugins with PluginFileFilters property
607                         return -90;
608                 }
609                 // default to false when Plugin doesn't have property
610                 m_bAutomatic = false;
611         }
612         VariantClear(&ret);
613
614         // get optional property PluginUnpackedFileExtenstion
615         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginUnpackedFileExtension"))
616         {
617                 h = ::invokeW(lpDispatch, &ret, L"PluginUnpackedFileExtension", opGet[0], nullptr);
618                 if (FAILED(h) || ret.vt != VT_BSTR)
619                 {
620                         scinfo.Log(_T("Plugin had PluginUnpackedFileExtension property, but error getting its value"));
621                         return -100; // error (Plugin had PluginUnpackedFileExtenstion property, but error getting its value)
622                 }
623                 m_ext = ucr::toTString(ret.bstrVal);
624         }
625         else
626         {
627                 m_ext.clear();
628         }
629         VariantClear(&ret);
630
631         // keep the filename
632         m_name = paths::FindFileName(scriptletFilepath);
633
634         m_filtersText = GetCustomFilters(m_name, m_filtersTextDefault);
635         LoadFilterString();
636
637         // Clear the autorelease holder
638         drv.p = nullptr;
639
640         m_lpDispatch = lpDispatch;
641
642         m_filepath = scriptletFilepath;
643
644         return 1;
645 }
646
647 /**
648  * @brief Try to load a plugin
649  *
650  * @return 1 if loaded plugin successfully, negatives for errors
651  */
652 int PluginInfo::LoadPlugin(const String & scriptletFilepath)
653 {
654         // set up object in case we need to log info
655         ScriptInfo scinfo(scriptletFilepath);
656
657         // Search for the class "WinMergeScript"
658         LPDISPATCH lpDispatch = CreateDispatchBySource(scriptletFilepath.c_str(), L"WinMergeScript");
659         if (lpDispatch == nullptr)
660         {
661                 scinfo.Log(_T("WinMergeScript entry point not found"));
662                 return -10; // error
663         }
664
665         return MakeInfo(scriptletFilepath, lpDispatch);
666 }
667
668 static void ReportPluginLoadFailure(const String & scriptletFilepath)
669 {
670         AppErrorMessageBox(strutils::format(_T("Exception loading plugin\r\n%s"), scriptletFilepath));
671 }
672
673 /**
674  * @brief Guard call to LoadPlugin with Windows SEH to trap GPFs
675  *
676  * @return same as LoadPlugin (1=has event, 0=doesn't have event, errors are negative)
677  */
678 static int LoadPluginWrapper(PluginInfo & plugin, const String & scriptletFilepath)
679 {
680         SE_Handler seh;
681         try
682         {
683                 return plugin.LoadPlugin(scriptletFilepath);
684         }
685         catch (SE_Exception&)
686         {
687                 ReportPluginLoadFailure(scriptletFilepath);
688         }
689         return false;
690 }
691
692 /**
693  * @brief Return list of all candidate plugins in module path
694  *
695  * Computes list only the first time, and caches it.
696  * Lock the plugins *.sct (.ocx and .dll are locked when the interface is created)
697  */
698 static vector<String>& LoadTheScriptletList()
699 {
700         FastMutex::ScopedLock lock(scriptletsSem);
701         if (!scriptletsLoaded)
702         {
703                 String path = paths::ConcatPath(env::GetProgPath(), _T("MergePlugins"));
704
705                 if (plugin::IsWindowsScriptThere())
706                         GetScriptletsAt(path, _T(".sct"), theScriptletList );           // VBS/JVS scriptlet
707                 else
708                         LogErrorString(_T("\n  .sct plugins disabled (Windows Script Host not found)"));
709                 GetScriptletsAt(path, _T(".ocx"), theScriptletList );           // VB COM object
710                 GetScriptletsAt(path, _T(".dll"), theScriptletList );           // VC++ COM object
711                 scriptletsLoaded = true;
712
713                 // lock the *.sct to avoid them being deleted/moved away
714                 for (size_t i = 0 ; i < theScriptletList.size() ; i++)
715                 {
716                         String scriptlet = theScriptletList.at(i);
717                         if (scriptlet.length() > 4 && strutils::compare_nocase(scriptlet.substr(scriptlet.length() - 4), _T(".sct")) != 0)
718                         {
719                                 // don't need to lock this file
720                                 theScriptletHandleList.push_back(nullptr);
721                                 continue;
722                         }
723
724                         HANDLE hFile;
725                         hFile=CreateFile(scriptlet.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
726                                 0, nullptr);
727                         if (hFile == INVALID_HANDLE_VALUE)
728                         {
729                                 theScriptletList.erase(theScriptletList.begin() + i);
730                                 i --;
731                         }
732                         else
733                         {
734                                 theScriptletHandleList.push_back(hFile);
735                         }
736                 }
737         }
738         return theScriptletList;
739 }
740 /**
741  * @brief Delete the scriptlet list and delete locks to *.sct
742  *
743  * Allow to load it again
744  */
745 static void UnloadTheScriptletList()
746 {
747         FastMutex::ScopedLock lock(scriptletsSem);
748         if (scriptletsLoaded)
749         {
750                 for (size_t i = 0 ; i < theScriptletHandleList.size() ; i++)
751                 {
752                         HANDLE hFile = theScriptletHandleList.at(i);
753                         if (hFile != nullptr)
754                                 CloseHandle(hFile);
755                 }
756
757                 theScriptletHandleList.clear();
758                 theScriptletList.clear();
759                 scriptletsLoaded = false;
760         }
761 }
762
763 /**
764  * @brief Remove a candidate plugin from the cache
765  */
766 static void RemoveScriptletCandidate(const String &scriptletFilepath)
767 {
768         for (size_t i=0; i<theScriptletList.size(); ++i)
769         {
770                 if (scriptletFilepath == theScriptletList[i])
771                 {
772                         HANDLE hFile = theScriptletHandleList.at(i);
773                         if (hFile != nullptr)
774                                 CloseHandle(hFile);
775
776                         theScriptletHandleList.erase(theScriptletHandleList.begin() + i);
777                         theScriptletList.erase(theScriptletList.begin() + i);
778                         return;
779                 }
780         }
781 }
782
783 /** 
784  * @brief Get available scriptlets for an event
785  *
786  * @return Returns an array of valid LPDISPATCH
787  */
788 static std::map<String, PluginArrayPtr> GetAvailableScripts() 
789 {
790         vector<String>& scriptlets = LoadTheScriptletList();
791         std::unordered_set<String> disabled_plugin_list = GetDisabledPluginList();
792         std::map<std::wstring, PluginArrayPtr> plugins;
793
794         std::list<String> badScriptlets;
795         for (size_t i = 0 ; i < scriptlets.size() ; i++)
796         {
797                 // Note all the info about the plugin
798                 PluginInfoPtr plugin(new PluginInfo);
799
800                 String scriptletFilepath = scriptlets.at(i);
801                 int rtn = LoadPluginWrapper(*plugin.get(), scriptletFilepath);
802                 if (rtn == 1)
803                 {
804                         // Plugin has this event
805                         plugin->m_disabled = (disabled_plugin_list.find(plugin->m_name) != disabled_plugin_list.end());
806                         if (plugins.find(plugin->m_event) == plugins.end())
807                                 plugins[plugin->m_event].reset(new PluginArray);
808                         plugins[plugin->m_event]->push_back(plugin);
809                 }
810                 else if (rtn < 0)
811                 {
812                         // Plugin is bad
813                         badScriptlets.push_back(scriptletFilepath);
814                 }
815         }
816
817         if (plugins.find(L"EDITOR_SCRIPT") != plugins.end())
818         {
819                 for (auto plugin : *plugins[L"EDITOR_SCRIPT"])
820                 {
821                         std::vector<String> namesArray;
822                         std::vector<int> idArray;
823                         int validFuncs = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
824                         for (int i = 0; i < validFuncs; ++i)
825                         {
826                                 if (plugins.find(L"FILE_PACK_UNPACK") == plugins.end())
827                                         plugins[L"FILE_PACK_UNPACK"].reset(new PluginArray);
828                                 PluginInfoPtr pluginNew(new PluginInfo());
829                                 IDispatch *pDispatch = new UnpackerGeneratedFromEditorScript(plugin->m_lpDispatch, namesArray[i], idArray[i]);
830                                 pDispatch->AddRef();
831                                 pluginNew->MakeInfo(plugin->m_filepath + _T(":") + namesArray[i], pDispatch);
832                                 plugins[L"FILE_PACK_UNPACK"]->push_back(pluginNew);
833                         }
834                 }
835         }
836
837         // Remove any bad plugins from the cache
838         // This might be a good time to see if the user wants to abort or continue
839         while (!badScriptlets.empty())
840         {
841                 RemoveScriptletCandidate(badScriptlets.front());
842                 badScriptlets.pop_front();
843         }
844
845         return plugins;
846 }
847
848 static void FreeAllScripts(PluginArrayPtr& pArray) 
849 {
850         pArray->clear();
851         pArray.reset();
852 }
853
854 ////////////////////////////////////////////////////////////////////////////////////
855 // class CScriptsOfThread : cache the interfaces during the thread life
856
857 CScriptsOfThread::CScriptsOfThread()
858 {
859         // count number of events
860         int i;
861         for (i = 0 ;  ; i ++)
862                 if (TransformationCategories[i] == nullptr)
863                         break;
864         nTransformationEvents = i;
865
866         // initialize the thread data
867         m_nThreadId = GetCurrentThreadId();
868         m_nLocks = 0;
869         // initialize the plugins pointers
870         typedef PluginArray * LPPluginArray;
871         // CoInitialize the thread, keep the returned value for the destructor 
872         hrInitialize = CoInitialize(nullptr);
873         assert(hrInitialize == S_OK || hrInitialize == S_FALSE);
874 }
875
876 CScriptsOfThread::~CScriptsOfThread()
877 {
878         FreeAllScripts();
879
880         if (hrInitialize == S_OK || hrInitialize == S_FALSE)
881                 CoUninitialize();
882 }
883
884 bool CScriptsOfThread::bInMainThread()
885 {
886         return (CAllThreadsScripts::bInMainThread(this));
887 }
888
889 PluginArray * CScriptsOfThread::GetAvailableScripts(const wchar_t *transformationEvent)
890 {
891         if (m_aPluginsByEvent.empty())
892                 m_aPluginsByEvent = ::GetAvailableScripts();
893         if (auto it = m_aPluginsByEvent.find(transformationEvent); it != m_aPluginsByEvent.end())
894                 return it->second.get();
895         // return a pointer to an empty list
896         static PluginArray noPlugin;
897         return &noPlugin;
898 }
899
900 void CScriptsOfThread::SaveSettings()
901 {
902         std::vector<String> listDisabled;
903         std::vector<String> listCustomFilters;
904         if (m_aPluginsByEvent.empty())
905                 m_aPluginsByEvent = ::GetAvailableScripts();
906         for (auto [key, pArray] : m_aPluginsByEvent)
907         {
908                 for (size_t j = 0; j < pArray->size(); ++j)
909                 {
910                         const PluginInfoPtr & plugin = pArray->at(j);
911                         if (plugin->m_disabled)
912                                 listDisabled.emplace_back(plugin->m_name);
913                         if (plugin->m_filtersTextDefault != plugin->m_filtersText)
914                                 listCustomFilters.emplace_back(plugin->m_name + _T(":") + plugin->m_filtersText);
915                         customFiltersMap.insert_or_assign(plugin->m_name, plugin->m_filtersText);
916                 }
917         }
918         GetOptionsMgr()->SaveOption(OPT_PLUGINS_DISABLED_LIST, strutils::join(listDisabled.begin(), listDisabled.end(), _T("|")));
919         GetOptionsMgr()->SaveOption(OPT_PLUGINS_CUSTOM_FILTERS_LIST, strutils::join(listCustomFilters.begin(), listCustomFilters.end(), _T("\t")));
920 }
921
922 void CScriptsOfThread::FreeAllScripts()
923 {
924         // release all the scripts of the thread
925         for (auto [key, pArray] : m_aPluginsByEvent)
926                 ::FreeAllScripts(pArray);
927
928         // force to reload the scriptlet list
929         UnloadTheScriptletList();
930 }
931
932 void CScriptsOfThread::FreeScriptsForEvent(const wchar_t *transformationEvent)
933 {
934         if (auto it = m_aPluginsByEvent.find(transformationEvent); it != m_aPluginsByEvent.end())
935         {
936                 if (it->second != nullptr)
937                         ::FreeAllScripts(it->second);
938         }
939 }
940
941 PluginInfo* CScriptsOfThread::GetUnpackerPluginByFilter(const String& filteredText)
942 {
943         PluginInfo *plugin = GetAutomaticPluginByFilter(L"FILE_PACK_UNPACK", filteredText);
944         if (plugin == nullptr)
945                 plugin = GetAutomaticPluginByFilter(L"FILE_FOLDER_PACK_UNPACK", filteredText);
946         if (plugin == nullptr)
947                 plugin = GetAutomaticPluginByFilter(L"BUFFER_PACK_UNPACK", filteredText);
948         return plugin;
949 }
950
951 PluginInfo *CScriptsOfThread::GetAutomaticPluginByFilter(const wchar_t *transformationEvent, const String& filteredText)
952 {
953         PluginArray * piFileScriptArray = GetAvailableScripts(transformationEvent);
954         for (size_t step = 0 ; step < piFileScriptArray->size() ; step ++)
955         {
956                 const PluginInfoPtr & plugin = piFileScriptArray->at(step);
957                 if (!plugin->m_bAutomatic || plugin->m_disabled)
958                         continue;
959                 if (!plugin->TestAgainstRegList(filteredText))
960                         continue;
961                 return plugin.get();
962         }
963         return nullptr;
964 }
965
966 PluginInfo * CScriptsOfThread::GetPluginByName(const wchar_t *transformationEvent, const String& name)
967 {
968         if (m_aPluginsByEvent.empty())
969                 m_aPluginsByEvent = ::GetAvailableScripts();
970         for (auto [key, pArray] : m_aPluginsByEvent)
971         {
972                 if (!transformationEvent || key == transformationEvent)
973                 {
974                         for (size_t j = 0; j < pArray->size(); j++)
975                                 if (pArray->at(j)->m_name == name)
976                                         return pArray->at(j).get();
977                 }
978         }
979         return nullptr;
980 }
981
982 PluginInfo *  CScriptsOfThread::GetPluginInfo(LPDISPATCH piScript)
983 {
984         for (auto [key, pArray] : m_aPluginsByEvent)
985         {
986                 if (pArray == nullptr)
987                         continue;
988                 for (size_t j = 0 ; j < pArray->size() ; j++)
989                         if ((*pArray)[j]->m_lpDispatch == piScript)
990                                 return (*pArray)[j].get();
991         }
992
993         return nullptr;
994 }
995
996 ////////////////////////////////////////////////////////////////////////////////////
997 // class CAllThreadsScripts : array of CScriptsOfThread, one per active thread
998
999 std::vector<CScriptsOfThread *> CAllThreadsScripts::m_aAvailableThreads;
1000 FastMutex m_aAvailableThreadsLock;
1001
1002 void CAllThreadsScripts::Add(CScriptsOfThread * scripts)
1003 {
1004         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1005         // add the thread in the array
1006
1007         // register in the array
1008         m_aAvailableThreads.push_back(scripts);
1009 }
1010
1011 void CAllThreadsScripts::Remove(CScriptsOfThread * scripts)
1012 {
1013         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1014         // unregister from the list
1015         std::vector<CScriptsOfThread *>::iterator it;
1016         for (it =  m_aAvailableThreads.begin(); it != m_aAvailableThreads.end(); ++it)
1017                 if ((*it) == scripts)
1018                 {
1019                         m_aAvailableThreads.erase(it);
1020                         break;
1021                 }
1022 }
1023
1024 CScriptsOfThread * CAllThreadsScripts::GetActiveSet()
1025 {
1026         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1027         unsigned long nThreadId = GetCurrentThreadId();
1028         for (size_t i = 0 ; i < m_aAvailableThreads.size() ; i++)
1029                 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
1030                         return m_aAvailableThreads[i];
1031         assert(false);
1032         return nullptr;
1033 }
1034 CScriptsOfThread * CAllThreadsScripts::GetActiveSetNoAssert()
1035 {
1036         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1037         unsigned long nThreadId = GetCurrentThreadId();
1038         for (size_t i = 0 ; i < m_aAvailableThreads.size() ; i++)
1039                 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
1040                         return m_aAvailableThreads[i];
1041         return nullptr;
1042 }
1043
1044 bool CAllThreadsScripts::bInMainThread(CScriptsOfThread * scripts)
1045 {
1046         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1047         return (scripts == m_aAvailableThreads[0]);
1048 }
1049
1050 ////////////////////////////////////////////////////////////////////////////////////
1051 // class CAssureScriptsForThread : control creation/destruction of CScriptsOfThread
1052
1053 CAssureScriptsForThread::CAssureScriptsForThread()
1054 {
1055         CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
1056         if (scripts == nullptr)
1057         {
1058                 scripts = new CScriptsOfThread;
1059                 // insert the script in the repository
1060                 CAllThreadsScripts::Add(scripts);
1061         }
1062         scripts->Lock();
1063 }
1064 CAssureScriptsForThread::~CAssureScriptsForThread()
1065 {
1066         CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
1067         if (scripts == nullptr)
1068                 return;
1069         if (scripts->Unlock())
1070         {
1071                 CAllThreadsScripts::Remove(scripts);
1072                 delete scripts;
1073         }
1074 }
1075
1076 ////////////////////////////////////////////////////////////////////////////////
1077 // wrap invokes with error handlers
1078
1079 /**
1080  * @brief Display a message box with the plugin name and the error message
1081  *
1082  * @note Use MessageBox instead of AfxMessageBox so we can set the caption.
1083  * VB/VBS plugins has an internal error handler, and a message box with caption,
1084  * and we try to reproduce it for other plugins.
1085  */
1086 static void ShowPluginErrorMessage(IDispatch *piScript, LPTSTR description)
1087 {
1088         PluginInfo * pInfo = CAllThreadsScripts::GetActiveSet()->GetPluginInfo(piScript);
1089         assert(pInfo != nullptr);
1090         assert(description != nullptr); 
1091         AppErrorMessageBox(strutils::format(_T("%s: %s"), pInfo->m_name, description));
1092 }
1093
1094 /**
1095  * @brief safe invoke helper (by ordinal)
1096  *
1097  * @note Free all variants passed to it (except ByRef ones) 
1098  */
1099 static HRESULT safeInvokeA(LPDISPATCH pi, VARIANT *ret, DISPID id, LPCCH op, ...)
1100 {
1101         HRESULT h = E_FAIL;
1102         SE_Handler seh;
1103         TCHAR errorText[500];
1104         bool bExceptionCatched = false; 
1105 #ifdef WIN64
1106         int nargs = LOBYTE((UINT_PTR)op);
1107         vector<VARIANT> args(nargs);
1108         va_list list;
1109         va_start(list, op);
1110         for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
1111                 *it = va_arg(list, VARIANT);
1112         va_end(list);
1113 #endif
1114
1115         try 
1116         {
1117 #ifdef WIN64
1118                 h = invokeA(pi, ret, id, op, nargs == 0 ? nullptr : &args[0]);
1119 #else
1120                 h = invokeA(pi, ret, id, op, (VARIANT *)(&op + 1));
1121 #endif
1122         }
1123         catch(SE_Exception& e) 
1124         {
1125                 // structured exception are catched here thanks to class SE_Exception
1126                 if (!(e.GetErrorMessage(errorText, 500, nullptr)))
1127                         // don't localize this as we do not localize the known exceptions
1128                         _tcscpy_safe(errorText, _T("Unknown CException"));
1129                 bExceptionCatched = true;
1130         }
1131         catch(...) 
1132         {
1133                 // don't localize this as we do not localize the known exceptions
1134                 _tcscpy_safe(errorText, _T("Unknown C++ exception"));
1135                 bExceptionCatched = true;
1136         }
1137
1138         if (bExceptionCatched)
1139         {
1140                 ShowPluginErrorMessage(pi, errorText);
1141                 // set h to FAILED
1142                 h = E_FAIL;
1143         }
1144
1145         return h;
1146 }
1147 /**
1148  * @brief safe invoke helper (by function name)
1149  *
1150  * @note Free all variants passed to it (except ByRef ones) 
1151  */
1152 static HRESULT safeInvokeW(LPDISPATCH pi, VARIANT *ret, LPCOLESTR silent, LPCCH op, ...)
1153 {
1154         HRESULT h = E_FAIL;
1155         SE_Handler seh;
1156         TCHAR errorText[500];
1157         bool bExceptionCatched = false;
1158 #ifdef WIN64
1159         int nargs = LOBYTE((UINT_PTR)op);
1160         vector<VARIANT> args(nargs);
1161         va_list list;
1162         va_start(list, op);
1163         for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
1164                 *it = va_arg(list, VARIANT);
1165         va_end(list);
1166 #endif
1167         
1168         try 
1169         {
1170 #ifdef WIN64
1171                 h = invokeW(pi, ret, silent, op, nargs == 0 ? nullptr : &args[0]);
1172 #else
1173                 h = invokeW(pi, ret, silent, op, (VARIANT *)(&op + 1));
1174 #endif
1175         }
1176         catch(SE_Exception& e) 
1177         {
1178                 // structured exception are catched here thanks to class SE_Exception
1179                 if (!(e.GetErrorMessage(errorText, 500, nullptr)))
1180                         // don't localize this as we do not localize the known exceptions
1181                         _tcscpy_safe(errorText, _T("Unknown CException"));
1182                 bExceptionCatched = true;
1183         }
1184         catch(...) 
1185         {
1186                 // don't localize this as we do not localize the known exceptions
1187                 _tcscpy_safe(errorText, _T("Unknown C++ exception"));
1188                 bExceptionCatched = true;
1189         }
1190
1191         if (bExceptionCatched)
1192         {
1193                 ShowPluginErrorMessage(pi, errorText);
1194                 // set h to FAILED
1195                 h = E_FAIL;
1196         }
1197
1198         return h;
1199 }
1200
1201 ////////////////////////////////////////////////////////////////////////////////
1202 // invoke for plugins
1203
1204 namespace plugin
1205 {
1206
1207 /*
1208  * ----- about VariantClear -----
1209  * VariantClear is done in safeInvokeW/safeInvokeA except for :
1210  * - the returned value
1211  * - BYREF arguments
1212  * note : BYREF arguments don't need VariantClear if the refered value
1213  * is deleted in the function destructor. Example :
1214  * {
1215  *   int Value;
1216  *   VARIANT vValue;
1217  *   vValue.plVal = &vValue;
1218  *   ...
1219  */
1220
1221 bool InvokePrediffBuffer(BSTR & bstrBuf, int & nChanged, IDispatch *piScript)
1222 {
1223         UINT nBufSize = SysStringLen(bstrBuf);
1224
1225         // prepare the arguments
1226         // argument text buffer by reference
1227         VARIANT vpbstrBuf;
1228         vpbstrBuf.vt = VT_BYREF | VT_BSTR;
1229         vpbstrBuf.pbstrVal = &bstrBuf;
1230         // argument buffer size by reference
1231         VARIANT vpiSize;
1232         vpiSize.vt = VT_BYREF | VT_I4;
1233         vpiSize.plVal = (long*) &nBufSize;
1234         // argument flag changed (VT_BOOL is short)
1235         VARIANT_BOOL changed = 0;
1236         VARIANT vpboolChanged;
1237         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1238         vpboolChanged.pboolVal = &changed;
1239         // argument return value (VT_BOOL is short)
1240         VARIANT vboolHandled;
1241         vboolHandled.vt = VT_BOOL;
1242         vboolHandled.boolVal = false;
1243
1244         // invoke method by name, reverse order for arguments
1245         // for VC, if the invoked function changes the buffer address, 
1246         // it must free the old buffer with SysFreeString
1247         // VB does it automatically
1248         // VARIANT_BOOL DiffingPreprocessW(BSTR * buffer, UINT * nSize, VARIANT_BOOL * bChanged)
1249         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PrediffBufferW", opFxn[3], 
1250                             vpboolChanged, vpiSize, vpbstrBuf);
1251         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1252         if (bSuccess && changed)
1253         {
1254                 // remove trailing charracters in the rare case that bstrBuf was not resized 
1255                 if (SysStringLen(bstrBuf) != nBufSize)
1256                         bSuccess = !FAILED(SysReAllocStringLen(&bstrBuf, bstrBuf, nBufSize));
1257                 if (bSuccess)
1258                         nChanged ++;
1259         }
1260
1261         // clear the returned variant
1262         VariantClear(&vboolHandled);
1263
1264         return  (bSuccess);
1265 }
1266
1267 bool InvokeUnpackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int & subcode)
1268 {
1269         LONG nArraySize;
1270         SafeArrayGetUBound(array.parray, 0, &nArraySize);
1271         ++nArraySize;
1272
1273         // prepare the arguments
1274         // argument file buffer
1275         VARIANT vparrayBuf;
1276         vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1277         vparrayBuf.pparray = &(array.parray);
1278         // argument buffer size by reference
1279         VARIANT vpiSize;
1280         vpiSize.vt = VT_BYREF | VT_I4;
1281         vpiSize.plVal = (long*) &nArraySize;
1282         // argument flag changed (VT_BOOL is short)
1283         VARIANT_BOOL changed = 0;
1284         VARIANT vpboolChanged;
1285         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1286         vpboolChanged.pboolVal = &changed;
1287         // argument subcode by reference
1288         VARIANT viSubcode;
1289         viSubcode.vt = VT_BYREF | VT_I4;
1290         viSubcode.plVal = (long*) &subcode;
1291         // argument return value (VT_BOOL is short)
1292         VARIANT vboolHandled;
1293         vboolHandled.vt = VT_BOOL;
1294         vboolHandled.boolVal = false;
1295
1296         // invoke method by name, reverse order for arguments
1297         // VARIANT_BOOL UnpackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT * subcode)
1298         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"UnpackBufferA", opFxn[4], 
1299                             viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1300         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1301         if (bSuccess && changed)
1302         {
1303                 // remove trailing charracters if the array was not resized
1304                 LONG nNewArraySize;
1305                 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1306                 ++nNewArraySize;
1307
1308                 if (nNewArraySize != nArraySize)
1309                 {
1310                         SAFEARRAYBOUND sab = {static_cast<ULONG>(nArraySize), 0};
1311                         SafeArrayRedim(array.parray, &sab);
1312                 }
1313                 nChanged ++;
1314         }
1315
1316         // clear the returned variant
1317         VariantClear(&vboolHandled);
1318
1319         return  (bSuccess);
1320 }
1321
1322 bool InvokePackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int subcode)
1323 {
1324         LONG nArraySize;
1325         SafeArrayGetUBound(array.parray, 0, &nArraySize);
1326         ++nArraySize;
1327
1328         // prepare the arguments
1329         // argument file buffer
1330         VARIANT vparrayBuf;
1331         vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1332         vparrayBuf.pparray = &(array.parray);
1333         // argument buffer size by reference
1334         VARIANT vpiSize;
1335         vpiSize.vt = VT_BYREF | VT_I4;
1336         vpiSize.plVal = (long*) &nArraySize;
1337         // argument flag changed (VT_BOOL is short)
1338         VARIANT_BOOL changed = 0;
1339         VARIANT vpboolChanged;
1340         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1341         vpboolChanged.pboolVal = &changed;
1342         // argument subcode
1343         VARIANT viSubcode;
1344         viSubcode.vt = VT_I4;
1345         viSubcode.lVal = subcode;
1346         // argument return value (VT_BOOL is short)
1347         VARIANT vboolHandled;
1348         vboolHandled.vt = VT_BOOL;
1349         vboolHandled.boolVal = false;
1350
1351         // invoke method by name, reverse order for arguments
1352         // VARIANT_BOOL PackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT subcode)
1353         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PackBufferA", opFxn[4], 
1354                             viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1355         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1356         if (bSuccess && changed)
1357         {
1358                 // remove trailing charracters if the array was not resized
1359                 LONG nNewArraySize;
1360                 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1361                 ++nNewArraySize;
1362
1363                 if (nNewArraySize != nArraySize)
1364                 {
1365                         SAFEARRAYBOUND sab = {static_cast<ULONG>(nArraySize), 0};
1366                         SafeArrayRedim(array.parray, &sab);
1367                 }
1368         }
1369
1370         // clear the returned variant
1371         VariantClear(&vboolHandled);
1372
1373         return  (bSuccess);
1374 }
1375
1376
1377 static bool unpack(const wchar_t *method, const String& source, const String& dest, int & nChanged, IDispatch *piScript, int & subCode)
1378 {
1379         // argument text  
1380         VARIANT vbstrSrc;
1381         vbstrSrc.vt = VT_BSTR;
1382         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(source).c_str());
1383         // argument transformed text 
1384         VARIANT vbstrDst;
1385         vbstrDst.vt = VT_BSTR;
1386         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(dest).c_str());
1387         // argument subcode by reference
1388         VARIANT vpiSubcode;
1389         vpiSubcode.vt = VT_BYREF | VT_I4;
1390         vpiSubcode.plVal = (long*) &subCode;
1391         // argument flag changed (VT_BOOL is short)
1392         VARIANT_BOOL changed = 0;
1393         VARIANT vpboolChanged;
1394         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1395         vpboolChanged.pboolVal = &changed;
1396         // argument return value (VT_BOOL is short)
1397         VARIANT vboolHandled;
1398         vboolHandled.vt = VT_BOOL;
1399         vboolHandled.boolVal = false;
1400
1401         // invoke method by name, reverse order for arguments
1402         // VARIANT_BOOL UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT * bSubcode)
1403         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, method, opFxn[4], 
1404                                   vpiSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1405         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1406         if (bSuccess && changed)
1407                 nChanged ++;
1408
1409         // clear the returned variant
1410         VariantClear(&vboolHandled);
1411
1412         return  (bSuccess);
1413 }
1414
1415 static bool pack(const wchar_t *method, const String& source, const String& dest, int & nChanged, IDispatch *piScript, int & subCode)
1416 {
1417         // argument text  
1418         VARIANT vbstrSrc;
1419         vbstrSrc.vt = VT_BSTR;
1420         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(source).c_str());
1421         // argument transformed text 
1422         VARIANT vbstrDst;
1423         vbstrDst.vt = VT_BSTR;
1424         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(dest).c_str());
1425         // argument subcode
1426         VARIANT viSubcode;
1427         viSubcode.vt = VT_I4;
1428         viSubcode.lVal = subCode;
1429         // argument flag changed (VT_BOOL is short)
1430         VARIANT_BOOL changed = 0;
1431         VARIANT vpboolChanged;
1432         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1433         vpboolChanged.pboolVal = &changed;
1434         // argument return value (VT_BOOL is short)
1435         VARIANT vboolHandled;
1436         vboolHandled.vt = VT_BOOL;
1437         vboolHandled.boolVal = false;
1438
1439         // invoke method by name, reverse order for arguments
1440         // VARIANT_BOOL PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT bSubcode)
1441         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, method, opFxn[4], 
1442                                   viSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1443         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1444         if (bSuccess && changed)
1445                 nChanged ++;
1446
1447         // clear the returned variant
1448         VariantClear(&vboolHandled);
1449
1450         return  (bSuccess);
1451 }
1452
1453 bool InvokeUnpackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int & subCode)
1454 {
1455         return unpack(L"UnpackFile", fileSource, fileDest, nChanged, piScript, subCode);
1456 }
1457
1458 bool InvokePackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1459 {
1460         return pack(L"PackFile", fileSource, fileDest, nChanged, piScript, subCode);
1461 }
1462
1463 bool InvokeUnpackFolder(const String& fileSource, const String& folderDest, int & nChanged, IDispatch *piScript, int & subCode)
1464 {
1465         return unpack(L"UnpackFolder", fileSource, folderDest, nChanged, piScript, subCode);
1466 }
1467
1468 bool InvokePackFolder(const String& folderSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1469 {
1470         return pack(L"PackFolder", folderSource, fileDest, nChanged, piScript, subCode);
1471 }
1472
1473 bool InvokePrediffFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript)
1474 {
1475         // argument text  
1476         VARIANT vbstrSrc;
1477         vbstrSrc.vt = VT_BSTR;
1478         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(fileSource).c_str());
1479         // argument transformed text 
1480         VARIANT vbstrDst;
1481         vbstrDst.vt = VT_BSTR;
1482         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(fileDest).c_str());
1483         // argument flag changed (VT_BOOL is short)
1484         VARIANT_BOOL changed = 0;
1485         VARIANT vpboolChanged;
1486         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1487         vpboolChanged.pboolVal = &changed;
1488         // argument return value (VT_BOOL is short)
1489         VARIANT vboolHandled;
1490         vboolHandled.vt = VT_BOOL;
1491         vboolHandled.boolVal = false;
1492
1493         // invoke method by name, reverse order for arguments
1494         // VARIANT_BOOL PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged)
1495         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PrediffFile", opFxn[3], 
1496                             vpboolChanged, vbstrDst, vbstrSrc);
1497         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1498         if (bSuccess && changed)
1499                 nChanged ++;
1500
1501         // clear the returned variant
1502         VariantClear(&vboolHandled);
1503
1504         return  (bSuccess);
1505 }
1506
1507
1508 bool InvokeTransformText(String & text, int & changed, IDispatch *piScript, int fncId)
1509 {
1510         // argument text  
1511         VARIANT pvPszBuf;
1512         pvPszBuf.vt = VT_BSTR;
1513         pvPszBuf.bstrVal = SysAllocString(ucr::toUTF16(text).c_str());
1514         // argument transformed text 
1515         VARIANT vTransformed;
1516         vTransformed.vt = VT_BSTR;
1517         vTransformed.bstrVal = nullptr;
1518
1519         // invoke method by ordinal
1520         // BSTR customFunction(BSTR text)
1521         HRESULT h = ::safeInvokeA(piScript, &vTransformed, fncId, opFxn[1], pvPszBuf);
1522
1523         if (! FAILED(h) && vTransformed.bstrVal)
1524         {
1525                 text = ucr::toTString(vTransformed.bstrVal);
1526                 changed = true;
1527         }
1528         else
1529                 changed = false;
1530
1531         // clear the returned variant
1532         VariantClear(&vTransformed);
1533
1534         return (! FAILED(h));
1535 }
1536
1537 bool InvokeIsFolder(const String& path, IDispatch *piScript)
1538 {
1539         // argument text  
1540         VARIANT vbstrPath;
1541         vbstrPath.vt = VT_BSTR;
1542         vbstrPath.bstrVal = SysAllocString(ucr::toUTF16(path).c_str());
1543
1544         VARIANT vboolHandled;
1545         vboolHandled.vt = VT_BOOL;
1546         vboolHandled.boolVal = false;
1547
1548         // invoke method by name, reverse order for arguments
1549         // VARIANT_BOOL ShowSettingsDialog()
1550         HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"IsFolder", opFxn[1], vbstrPath);
1551         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1552
1553         // clear the returned variant
1554         VariantClear(&vboolHandled);
1555
1556         return (bSuccess);
1557 }
1558
1559 bool InvokeShowSettingsDialog(IDispatch *piScript)
1560 {
1561         VARIANT vboolHandled;
1562         vboolHandled.vt = VT_BOOL;
1563         vboolHandled.boolVal = false;
1564
1565         // invoke method by name, reverse order for arguments
1566         // VARIANT_BOOL ShowSettingsDialog()
1567         HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"ShowSettingsDialog", opFxn[0]);
1568         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1569
1570         // clear the returned variant
1571         VariantClear(&vboolHandled);
1572
1573         return (bSuccess);
1574 }
1575
1576 }