OSDN Git Service

Add WinMergePluginBase.h
[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) : m_pDispatch(pDispatch), m_funcid(id)
383         {
384                 m_sDescription =
385                         strutils::format_string1(_T("Unpacker to execute %1 script (automatically generated)") , funcname);
386                 m_sFileFilters = _T(".");
387                 m_bIsAutomatic = true;
388                 m_sEvent = L"FILE_PACK_UNPACK";
389                 m_pDispatch->AddRef();
390         }
391
392         virtual ~UnpackerGeneratedFromEditorScript()
393         {
394         }
395
396         static HRESULT ReadFile(const String& path, String& text)
397         {
398                 UniMemFile file;
399                 if (!file.OpenReadOnly(path))
400                         return E_ACCESSDENIED;
401                 file.ReadBom();
402                 if (!file.HasBom())
403                 {
404                         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
405                         FileTextEncoding encoding = codepage_detect::Guess(
406                                 paths::FindExtension(path), file.GetBase(),
407                                 file.GetFileSize() < codepage_detect::BufSize ? file.GetFileSize() : codepage_detect::BufSize,
408                                 iGuessEncodingType);
409                         file.SetCodepage(encoding.m_codepage);
410                 }
411                 file.ReadStringAll(text);
412                 file.Close();
413                 return S_OK;
414         }
415
416         static HRESULT WriteFile(const String& path, const String& text)
417         {
418                 UniStdioFile fileOut;
419                 if (!fileOut.Open(path, _T("wb")))
420                         return E_ACCESSDENIED;
421                 fileOut.SetUnicoding(ucr::UNICODESET::UTF8);
422                 fileOut.SetBom(true);
423                 fileOut.WriteBom();
424                 fileOut.WriteString(text);
425                 fileOut.Close();
426                 return S_OK;
427         }
428
429         HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
430         {
431                 String text;
432                 HRESULT hr = ReadFile(fileSrc, text);
433                 if (FAILED(hr))
434                         return hr;
435                 int changed = 0;
436                 if (!plugin::InvokeTransformText(text, changed, m_pDispatch, m_funcid))
437                         return E_FAIL;
438                 hr = WriteFile(fileDst, text);
439                 if (FAILED(hr))
440                         return hr;
441                 *pSubcode = 0;
442                 *pbChanged = VARIANT_TRUE;
443                 *pbSuccess = VARIANT_TRUE;
444                 return S_OK;
445         }
446
447 private:
448         IDispatch *m_pDispatch;
449         int m_funcid;
450 };
451
452 /**
453  * @brief Tiny structure that remembers current scriptlet & event info for calling Log
454  */
455 struct ScriptInfo
456 {
457         ScriptInfo(const String & scriptletFilepath)
458                 : m_scriptletFilepath(scriptletFilepath)
459         {
460         }
461         void Log(const TCHAR *szError)
462         {
463                 ScriptletError(m_scriptletFilepath, szError);
464         }
465         const String & m_scriptletFilepath;
466 };
467
468 /**
469  * @brief Try to load a plugin
470  *
471  * @return 1 if loaded plugin successfully, negatives for errors
472  */
473 int PluginInfo::MakeInfo(const String & scriptletFilepath, IDispatch *lpDispatch)
474 {
475         // set up object in case we need to log info
476         ScriptInfo scinfo(scriptletFilepath);
477
478         // Ensure that interface is released if any bad exit or exception
479         AutoReleaser<IDispatch> drv(lpDispatch);
480
481         // Is this plugin for this transformationEvent ?
482         VARIANT ret;
483         // invoke mandatory method get PluginEvent
484         VariantInit(&ret);
485         if (!plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginEvent"))
486         {
487                 scinfo.Log(_T("PluginEvent method missing"));
488                 return -20; // error
489         }
490         HRESULT h = ::invokeW(lpDispatch, &ret, L"PluginEvent", opGet[0], nullptr);
491         if (FAILED(h) || ret.vt != VT_BSTR)
492         {
493                 scinfo.Log(     _T("Error accessing PluginEvent method"));
494                 return -30; // error
495         }
496         m_event = ucr::toTString(ret.bstrVal);
497
498         VariantClear(&ret);
499
500         // plugins PREDIFF or PACK_UNPACK : functions names are mandatory
501         // Check that the plugin offers the requested functions
502         // set the mode for the events which uses it
503         bool bFound = true;
504         if (m_event == _T("BUFFER_PREDIFF"))
505         {
506                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PrediffBufferW");
507         }
508         else if (m_event == _T("FILE_PREDIFF"))
509         {
510                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PrediffFile");
511         }
512         else if (m_event == _T("BUFFER_PACK_UNPACK"))
513         {
514                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackBufferA");
515                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackBufferA");
516         }
517         else if (m_event == _T("FILE_PACK_UNPACK"))
518         {
519                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFile");
520                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFile");
521         }
522         else if (m_event == _T("FILE_FOLDER_PACK_UNPACK"))
523         {
524                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"IsFolder");
525                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFile");
526                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFile");
527                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFolder");
528                 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFolder");
529         }
530         if (!bFound)
531         {
532                 // error (Plugin doesn't support the method as it claimed)
533                 scinfo.Log(_T("Plugin doesn't support the method as it claimed"));
534                 return -40; 
535         }
536
537         // plugins EDITOR_SCRIPT : functions names are free
538         // there may be several functions inside one script, count the number of functions
539         if (m_event == _T("EDITOR_SCRIPT"))
540         {
541                 m_nFreeFunctions = plugin::CountMethodsInScript(lpDispatch);
542                 if (m_nFreeFunctions == 0)
543                         // error (Plugin doesn't offer any method, what is this ?)
544                         return -50;
545         }
546
547
548         // get optional property PluginDescription
549         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginDescription"))
550         {
551                 h = ::invokeW(lpDispatch, &ret, L"PluginDescription", opGet[0], nullptr);
552                 if (FAILED(h) || ret.vt != VT_BSTR)
553                 {
554                         scinfo.Log(_T("Plugin had PluginDescription property, but error getting its value"));
555                         return -60; // error (Plugin had PluginDescription property, but error getting its value)
556                 }
557                 m_description = ucr::toTString(ret.bstrVal);
558         }
559         else
560         {
561                 // no description, use filename
562                 m_description = paths::FindFileName(scriptletFilepath);
563         }
564         VariantClear(&ret);
565
566         // get PluginFileFilters
567         bool hasPluginFileFilters = false;
568         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginFileFilters"))
569         {
570                 h = ::invokeW(lpDispatch, &ret, L"PluginFileFilters", opGet[0], nullptr);
571                 if (FAILED(h) || ret.vt != VT_BSTR)
572                 {
573                         scinfo.Log(_T("Plugin had PluginFileFilters property, but error getting its value"));
574                         return -70; // error (Plugin had PluginFileFilters property, but error getting its value)
575                 }
576                 m_filtersTextDefault = ucr::toTString(ret.bstrVal);
577                 hasPluginFileFilters = true;
578         }
579         else
580         {
581                 m_bAutomatic = false;
582                 m_filtersTextDefault = _T(".");
583         }
584         VariantClear(&ret);
585
586         // get optional property PluginIsAutomatic
587         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginIsAutomatic"))
588         {
589                 h = ::invokeW(lpDispatch, &ret, L"PluginIsAutomatic", opGet[0], nullptr);
590                 if (FAILED(h) || ret.vt != VT_BOOL)
591                 {
592                         scinfo.Log(_T("Plugin had PluginIsAutomatic property, but error getting its value"));
593                         return -80; // error (Plugin had PluginIsAutomatic property, but error getting its value)
594                 }
595                 m_bAutomatic = !!ret.boolVal;
596         }
597         else
598         {
599                 if (hasPluginFileFilters)
600                 {
601                         scinfo.Log(_T("Plugin had PluginFileFilters property, but lacked PluginIsAutomatic property"));
602                         // PluginIsAutomatic property is mandatory for Plugins with PluginFileFilters property
603                         return -90;
604                 }
605                 // default to false when Plugin doesn't have property
606                 m_bAutomatic = false;
607         }
608         VariantClear(&ret);
609
610         // get optional property PluginUnpackedFileExtenstion
611         if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginUnpackedFileExtension"))
612         {
613                 h = ::invokeW(lpDispatch, &ret, L"PluginUnpackedFileExtension", opGet[0], nullptr);
614                 if (FAILED(h) || ret.vt != VT_BSTR)
615                 {
616                         scinfo.Log(_T("Plugin had PluginUnpackedFileExtension property, but error getting its value"));
617                         return -100; // error (Plugin had PluginUnpackedFileExtenstion property, but error getting its value)
618                 }
619                 m_ext = ucr::toTString(ret.bstrVal);
620         }
621         else
622         {
623                 m_ext.clear();
624         }
625         VariantClear(&ret);
626
627         // keep the filename
628         m_name = paths::FindFileName(scriptletFilepath);
629
630         m_filtersText = GetCustomFilters(m_name, m_filtersTextDefault);
631         LoadFilterString();
632
633         // Clear the autorelease holder
634         drv.p = nullptr;
635
636         m_lpDispatch = lpDispatch;
637
638         m_filepath = scriptletFilepath;
639
640         return 1;
641 }
642
643 /**
644  * @brief Try to load a plugin
645  *
646  * @return 1 if loaded plugin successfully, negatives for errors
647  */
648 int PluginInfo::LoadPlugin(const String & scriptletFilepath)
649 {
650         // set up object in case we need to log info
651         ScriptInfo scinfo(scriptletFilepath);
652
653         // Search for the class "WinMergeScript"
654         LPDISPATCH lpDispatch = CreateDispatchBySource(scriptletFilepath.c_str(), L"WinMergeScript");
655         if (lpDispatch == nullptr)
656         {
657                 scinfo.Log(_T("WinMergeScript entry point not found"));
658                 return -10; // error
659         }
660
661         return MakeInfo(scriptletFilepath, lpDispatch);
662 }
663
664 static void ReportPluginLoadFailure(const String & scriptletFilepath)
665 {
666         AppErrorMessageBox(strutils::format(_T("Exception loading plugin\r\n%s"), scriptletFilepath));
667 }
668
669 /**
670  * @brief Guard call to LoadPlugin with Windows SEH to trap GPFs
671  *
672  * @return same as LoadPlugin (1=has event, 0=doesn't have event, errors are negative)
673  */
674 static int LoadPluginWrapper(PluginInfo & plugin, const String & scriptletFilepath)
675 {
676         SE_Handler seh;
677         try
678         {
679                 return plugin.LoadPlugin(scriptletFilepath);
680         }
681         catch (SE_Exception&)
682         {
683                 ReportPluginLoadFailure(scriptletFilepath);
684         }
685         return false;
686 }
687
688 /**
689  * @brief Return list of all candidate plugins in module path
690  *
691  * Computes list only the first time, and caches it.
692  * Lock the plugins *.sct (.ocx and .dll are locked when the interface is created)
693  */
694 static vector<String>& LoadTheScriptletList()
695 {
696         FastMutex::ScopedLock lock(scriptletsSem);
697         if (!scriptletsLoaded)
698         {
699                 String path = paths::ConcatPath(env::GetProgPath(), _T("MergePlugins"));
700
701                 if (plugin::IsWindowsScriptThere())
702                         GetScriptletsAt(path, _T(".sct"), theScriptletList );           // VBS/JVS scriptlet
703                 else
704                         LogErrorString(_T("\n  .sct plugins disabled (Windows Script Host not found)"));
705                 GetScriptletsAt(path, _T(".ocx"), theScriptletList );           // VB COM object
706                 GetScriptletsAt(path, _T(".dll"), theScriptletList );           // VC++ COM object
707                 scriptletsLoaded = true;
708
709                 // lock the *.sct to avoid them being deleted/moved away
710                 for (size_t i = 0 ; i < theScriptletList.size() ; i++)
711                 {
712                         String scriptlet = theScriptletList.at(i);
713                         if (scriptlet.length() > 4 && strutils::compare_nocase(scriptlet.substr(scriptlet.length() - 4), _T(".sct")) != 0)
714                         {
715                                 // don't need to lock this file
716                                 theScriptletHandleList.push_back(nullptr);
717                                 continue;
718                         }
719
720                         HANDLE hFile;
721                         hFile=CreateFile(scriptlet.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
722                                 0, nullptr);
723                         if (hFile == INVALID_HANDLE_VALUE)
724                         {
725                                 theScriptletList.erase(theScriptletList.begin() + i);
726                                 i --;
727                         }
728                         else
729                         {
730                                 theScriptletHandleList.push_back(hFile);
731                         }
732                 }
733         }
734         return theScriptletList;
735 }
736 /**
737  * @brief Delete the scriptlet list and delete locks to *.sct
738  *
739  * Allow to load it again
740  */
741 static void UnloadTheScriptletList()
742 {
743         FastMutex::ScopedLock lock(scriptletsSem);
744         if (scriptletsLoaded)
745         {
746                 for (size_t i = 0 ; i < theScriptletHandleList.size() ; i++)
747                 {
748                         HANDLE hFile = theScriptletHandleList.at(i);
749                         if (hFile != nullptr)
750                                 CloseHandle(hFile);
751                 }
752
753                 theScriptletHandleList.clear();
754                 theScriptletList.clear();
755                 scriptletsLoaded = false;
756         }
757 }
758
759 /**
760  * @brief Remove a candidate plugin from the cache
761  */
762 static void RemoveScriptletCandidate(const String &scriptletFilepath)
763 {
764         for (size_t i=0; i<theScriptletList.size(); ++i)
765         {
766                 if (scriptletFilepath == theScriptletList[i])
767                 {
768                         HANDLE hFile = theScriptletHandleList.at(i);
769                         if (hFile != nullptr)
770                                 CloseHandle(hFile);
771
772                         theScriptletHandleList.erase(theScriptletHandleList.begin() + i);
773                         theScriptletList.erase(theScriptletList.begin() + i);
774                         return;
775                 }
776         }
777 }
778
779 /** 
780  * @brief Get available scriptlets for an event
781  *
782  * @return Returns an array of valid LPDISPATCH
783  */
784 static std::map<String, PluginArrayPtr> GetAvailableScripts() 
785 {
786         vector<String>& scriptlets = LoadTheScriptletList();
787         std::unordered_set<String> disabled_plugin_list = GetDisabledPluginList();
788         std::map<std::wstring, PluginArrayPtr> plugins;
789
790         std::list<String> badScriptlets;
791         for (size_t i = 0 ; i < scriptlets.size() ; i++)
792         {
793                 // Note all the info about the plugin
794                 PluginInfoPtr plugin(new PluginInfo);
795
796                 String scriptletFilepath = scriptlets.at(i);
797                 int rtn = LoadPluginWrapper(*plugin.get(), scriptletFilepath);
798                 if (rtn == 1)
799                 {
800                         // Plugin has this event
801                         plugin->m_disabled = (disabled_plugin_list.find(plugin->m_name) != disabled_plugin_list.end());
802                         if (plugins.find(plugin->m_event) == plugins.end())
803                                 plugins[plugin->m_event].reset(new PluginArray);
804                         plugins[plugin->m_event]->push_back(plugin);
805                 }
806                 else if (rtn < 0)
807                 {
808                         // Plugin is bad
809                         badScriptlets.push_back(scriptletFilepath);
810                 }
811         }
812
813         if (plugins.find(L"EDITOR_SCRIPT") != plugins.end())
814         {
815                 for (auto plugin : *plugins[L"EDITOR_SCRIPT"])
816                 {
817                         std::vector<String> namesArray;
818                         std::vector<int> idArray;
819                         int validFuncs = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
820                         for (int i = 0; i < validFuncs; ++i)
821                         {
822                                 if (plugins.find(L"FILE_PACK_UNPACK") == plugins.end())
823                                         plugins[L"FILE_PACK_UNPACK"].reset(new PluginArray);
824                                 PluginInfoPtr pluginNew(new PluginInfo());
825                                 IDispatch *pDispatch = new UnpackerGeneratedFromEditorScript(plugin->m_lpDispatch, namesArray[i], idArray[i]);
826                                 pDispatch->AddRef();
827                                 pluginNew->MakeInfo(plugin->m_filepath + _T(":") + namesArray[i], pDispatch);
828                                 plugins[L"FILE_PACK_UNPACK"]->push_back(pluginNew);
829                         }
830                 }
831         }
832
833         // Remove any bad plugins from the cache
834         // This might be a good time to see if the user wants to abort or continue
835         while (!badScriptlets.empty())
836         {
837                 RemoveScriptletCandidate(badScriptlets.front());
838                 badScriptlets.pop_front();
839         }
840
841         return plugins;
842 }
843
844 static void FreeAllScripts(PluginArrayPtr& pArray) 
845 {
846         pArray->clear();
847         pArray.reset();
848 }
849
850 ////////////////////////////////////////////////////////////////////////////////////
851 // class CScriptsOfThread : cache the interfaces during the thread life
852
853 CScriptsOfThread::CScriptsOfThread()
854 {
855         // count number of events
856         int i;
857         for (i = 0 ;  ; i ++)
858                 if (TransformationCategories[i] == nullptr)
859                         break;
860         nTransformationEvents = i;
861
862         // initialize the thread data
863         m_nThreadId = GetCurrentThreadId();
864         m_nLocks = 0;
865         // initialize the plugins pointers
866         typedef PluginArray * LPPluginArray;
867         // CoInitialize the thread, keep the returned value for the destructor 
868         hrInitialize = CoInitialize(nullptr);
869         assert(hrInitialize == S_OK || hrInitialize == S_FALSE);
870 }
871
872 CScriptsOfThread::~CScriptsOfThread()
873 {
874         FreeAllScripts();
875
876         if (hrInitialize == S_OK || hrInitialize == S_FALSE)
877                 CoUninitialize();
878 }
879
880 bool CScriptsOfThread::bInMainThread()
881 {
882         return (CAllThreadsScripts::bInMainThread(this));
883 }
884
885 PluginArray * CScriptsOfThread::GetAvailableScripts(const wchar_t *transformationEvent)
886 {
887         if (m_aPluginsByEvent.empty())
888                 m_aPluginsByEvent = ::GetAvailableScripts();
889         if (auto it = m_aPluginsByEvent.find(transformationEvent); it != m_aPluginsByEvent.end())
890                 return it->second.get();
891         // return a pointer to an empty list
892         static PluginArray noPlugin;
893         return &noPlugin;
894 }
895
896 void CScriptsOfThread::SaveSettings()
897 {
898         std::vector<String> listDisabled;
899         std::vector<String> listCustomFilters;
900         if (m_aPluginsByEvent.empty())
901                 m_aPluginsByEvent = ::GetAvailableScripts();
902         for (auto [key, pArray] : m_aPluginsByEvent)
903         {
904                 for (size_t j = 0; j < pArray->size(); ++j)
905                 {
906                         const PluginInfoPtr & plugin = pArray->at(j);
907                         if (plugin->m_disabled)
908                                 listDisabled.emplace_back(plugin->m_name);
909                         if (plugin->m_filtersTextDefault != plugin->m_filtersText)
910                                 listCustomFilters.emplace_back(plugin->m_name + _T(":") + plugin->m_filtersText);
911                         customFiltersMap.insert_or_assign(plugin->m_name, plugin->m_filtersText);
912                 }
913         }
914         GetOptionsMgr()->SaveOption(OPT_PLUGINS_DISABLED_LIST, strutils::join(listDisabled.begin(), listDisabled.end(), _T("|")));
915         GetOptionsMgr()->SaveOption(OPT_PLUGINS_CUSTOM_FILTERS_LIST, strutils::join(listCustomFilters.begin(), listCustomFilters.end(), _T("\t")));
916 }
917
918 void CScriptsOfThread::FreeAllScripts()
919 {
920         // release all the scripts of the thread
921         for (auto [key, pArray] : m_aPluginsByEvent)
922                 ::FreeAllScripts(pArray);
923
924         // force to reload the scriptlet list
925         UnloadTheScriptletList();
926 }
927
928 void CScriptsOfThread::FreeScriptsForEvent(const wchar_t *transformationEvent)
929 {
930         if (auto it = m_aPluginsByEvent.find(transformationEvent); it != m_aPluginsByEvent.end())
931         {
932                 if (it->second != nullptr)
933                         ::FreeAllScripts(it->second);
934         }
935 }
936
937 PluginInfo *CScriptsOfThread::GetAutomaticPluginByFilter(const wchar_t *transformationEvent, const String& filteredText)
938 {
939         PluginArray * piFileScriptArray = GetAvailableScripts(transformationEvent);
940         for (size_t step = 0 ; step < piFileScriptArray->size() ; step ++)
941         {
942                 const PluginInfoPtr & plugin = piFileScriptArray->at(step);
943                 if (!plugin->m_bAutomatic || plugin->m_disabled)
944                         continue;
945                 if (!plugin->TestAgainstRegList(filteredText))
946                         continue;
947                 return plugin.get();
948         }
949         return nullptr;
950 }
951
952 PluginInfo * CScriptsOfThread::GetPluginByName(const wchar_t *transformationEvent, const String& name)
953 {
954         if (m_aPluginsByEvent.empty())
955                 m_aPluginsByEvent = ::GetAvailableScripts();
956         for (auto [key, pArray] : m_aPluginsByEvent)
957         {
958                 if (!transformationEvent || key == transformationEvent)
959                 {
960                         for (size_t j = 0; j < pArray->size(); j++)
961                                 if (pArray->at(j)->m_name == name)
962                                         return pArray->at(j).get();
963                 }
964         }
965         return nullptr;
966 }
967
968 PluginInfo *  CScriptsOfThread::GetPluginInfo(LPDISPATCH piScript)
969 {
970         for (auto [key, pArray] : m_aPluginsByEvent)
971         {
972                 if (pArray == nullptr)
973                         continue;
974                 for (size_t j = 0 ; j < pArray->size() ; j++)
975                         if ((*pArray)[j]->m_lpDispatch == piScript)
976                                 return (*pArray)[j].get();
977         }
978
979         return nullptr;
980 }
981
982 ////////////////////////////////////////////////////////////////////////////////////
983 // class CAllThreadsScripts : array of CScriptsOfThread, one per active thread
984
985 std::vector<CScriptsOfThread *> CAllThreadsScripts::m_aAvailableThreads;
986 FastMutex m_aAvailableThreadsLock;
987
988 void CAllThreadsScripts::Add(CScriptsOfThread * scripts)
989 {
990         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
991         // add the thread in the array
992
993         // register in the array
994         m_aAvailableThreads.push_back(scripts);
995 }
996
997 void CAllThreadsScripts::Remove(CScriptsOfThread * scripts)
998 {
999         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1000         // unregister from the list
1001         std::vector<CScriptsOfThread *>::iterator it;
1002         for (it =  m_aAvailableThreads.begin(); it != m_aAvailableThreads.end(); ++it)
1003                 if ((*it) == scripts)
1004                 {
1005                         m_aAvailableThreads.erase(it);
1006                         break;
1007                 }
1008 }
1009
1010 CScriptsOfThread * CAllThreadsScripts::GetActiveSet()
1011 {
1012         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1013         unsigned long nThreadId = GetCurrentThreadId();
1014         for (size_t i = 0 ; i < m_aAvailableThreads.size() ; i++)
1015                 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
1016                         return m_aAvailableThreads[i];
1017         assert(false);
1018         return nullptr;
1019 }
1020 CScriptsOfThread * CAllThreadsScripts::GetActiveSetNoAssert()
1021 {
1022         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1023         unsigned long nThreadId = GetCurrentThreadId();
1024         for (size_t i = 0 ; i < m_aAvailableThreads.size() ; i++)
1025                 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
1026                         return m_aAvailableThreads[i];
1027         return nullptr;
1028 }
1029
1030 bool CAllThreadsScripts::bInMainThread(CScriptsOfThread * scripts)
1031 {
1032         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1033         return (scripts == m_aAvailableThreads[0]);
1034 }
1035
1036 ////////////////////////////////////////////////////////////////////////////////////
1037 // class CAssureScriptsForThread : control creation/destruction of CScriptsOfThread
1038
1039 CAssureScriptsForThread::CAssureScriptsForThread()
1040 {
1041         CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
1042         if (scripts == nullptr)
1043         {
1044                 scripts = new CScriptsOfThread;
1045                 // insert the script in the repository
1046                 CAllThreadsScripts::Add(scripts);
1047         }
1048         scripts->Lock();
1049 }
1050 CAssureScriptsForThread::~CAssureScriptsForThread()
1051 {
1052         CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
1053         if (scripts == nullptr)
1054                 return;
1055         if (scripts->Unlock())
1056         {
1057                 CAllThreadsScripts::Remove(scripts);
1058                 delete scripts;
1059         }
1060 }
1061
1062 ////////////////////////////////////////////////////////////////////////////////
1063 // wrap invokes with error handlers
1064
1065 /**
1066  * @brief Display a message box with the plugin name and the error message
1067  *
1068  * @note Use MessageBox instead of AfxMessageBox so we can set the caption.
1069  * VB/VBS plugins has an internal error handler, and a message box with caption,
1070  * and we try to reproduce it for other plugins.
1071  */
1072 static void ShowPluginErrorMessage(IDispatch *piScript, LPTSTR description)
1073 {
1074         PluginInfo * pInfo = CAllThreadsScripts::GetActiveSet()->GetPluginInfo(piScript);
1075         assert(pInfo != nullptr);
1076         assert(description != nullptr); 
1077         AppErrorMessageBox(strutils::format(_T("%s: %s"), pInfo->m_name, description));
1078 }
1079
1080 /**
1081  * @brief safe invoke helper (by ordinal)
1082  *
1083  * @note Free all variants passed to it (except ByRef ones) 
1084  */
1085 static HRESULT safeInvokeA(LPDISPATCH pi, VARIANT *ret, DISPID id, LPCCH op, ...)
1086 {
1087         HRESULT h = E_FAIL;
1088         SE_Handler seh;
1089         TCHAR errorText[500];
1090         bool bExceptionCatched = false; 
1091 #ifdef WIN64
1092         int nargs = LOBYTE((UINT_PTR)op);
1093         vector<VARIANT> args(nargs);
1094         va_list list;
1095         va_start(list, op);
1096         for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
1097                 *it = va_arg(list, VARIANT);
1098         va_end(list);
1099 #endif
1100
1101         try 
1102         {
1103 #ifdef WIN64
1104                 h = invokeA(pi, ret, id, op, nargs == 0 ? nullptr : &args[0]);
1105 #else
1106                 h = invokeA(pi, ret, id, op, (VARIANT *)(&op + 1));
1107 #endif
1108         }
1109         catch(SE_Exception& e) 
1110         {
1111                 // structured exception are catched here thanks to class SE_Exception
1112                 if (!(e.GetErrorMessage(errorText, 500, nullptr)))
1113                         // don't localize this as we do not localize the known exceptions
1114                         _tcscpy_safe(errorText, _T("Unknown CException"));
1115                 bExceptionCatched = true;
1116         }
1117         catch(...) 
1118         {
1119                 // don't localize this as we do not localize the known exceptions
1120                 _tcscpy_safe(errorText, _T("Unknown C++ exception"));
1121                 bExceptionCatched = true;
1122         }
1123
1124         if (bExceptionCatched)
1125         {
1126                 ShowPluginErrorMessage(pi, errorText);
1127                 // set h to FAILED
1128                 h = E_FAIL;
1129         }
1130
1131         return h;
1132 }
1133 /**
1134  * @brief safe invoke helper (by function name)
1135  *
1136  * @note Free all variants passed to it (except ByRef ones) 
1137  */
1138 static HRESULT safeInvokeW(LPDISPATCH pi, VARIANT *ret, LPCOLESTR silent, LPCCH op, ...)
1139 {
1140         HRESULT h = E_FAIL;
1141         SE_Handler seh;
1142         TCHAR errorText[500];
1143         bool bExceptionCatched = false;
1144 #ifdef WIN64
1145         int nargs = LOBYTE((UINT_PTR)op);
1146         vector<VARIANT> args(nargs);
1147         va_list list;
1148         va_start(list, op);
1149         for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
1150                 *it = va_arg(list, VARIANT);
1151         va_end(list);
1152 #endif
1153         
1154         try 
1155         {
1156 #ifdef WIN64
1157                 h = invokeW(pi, ret, silent, op, nargs == 0 ? nullptr : &args[0]);
1158 #else
1159                 h = invokeW(pi, ret, silent, op, (VARIANT *)(&op + 1));
1160 #endif
1161         }
1162         catch(SE_Exception& e) 
1163         {
1164                 // structured exception are catched here thanks to class SE_Exception
1165                 if (!(e.GetErrorMessage(errorText, 500, nullptr)))
1166                         // don't localize this as we do not localize the known exceptions
1167                         _tcscpy_safe(errorText, _T("Unknown CException"));
1168                 bExceptionCatched = true;
1169         }
1170         catch(...) 
1171         {
1172                 // don't localize this as we do not localize the known exceptions
1173                 _tcscpy_safe(errorText, _T("Unknown C++ exception"));
1174                 bExceptionCatched = true;
1175         }
1176
1177         if (bExceptionCatched)
1178         {
1179                 ShowPluginErrorMessage(pi, errorText);
1180                 // set h to FAILED
1181                 h = E_FAIL;
1182         }
1183
1184         return h;
1185 }
1186
1187 ////////////////////////////////////////////////////////////////////////////////
1188 // invoke for plugins
1189
1190 namespace plugin
1191 {
1192
1193 /*
1194  * ----- about VariantClear -----
1195  * VariantClear is done in safeInvokeW/safeInvokeA except for :
1196  * - the returned value
1197  * - BYREF arguments
1198  * note : BYREF arguments don't need VariantClear if the refered value
1199  * is deleted in the function destructor. Example :
1200  * {
1201  *   int Value;
1202  *   VARIANT vValue;
1203  *   vValue.plVal = &vValue;
1204  *   ...
1205  */
1206
1207 bool InvokePrediffBuffer(BSTR & bstrBuf, int & nChanged, IDispatch *piScript)
1208 {
1209         UINT nBufSize = SysStringLen(bstrBuf);
1210
1211         // prepare the arguments
1212         // argument text buffer by reference
1213         VARIANT vpbstrBuf;
1214         vpbstrBuf.vt = VT_BYREF | VT_BSTR;
1215         vpbstrBuf.pbstrVal = &bstrBuf;
1216         // argument buffer size by reference
1217         VARIANT vpiSize;
1218         vpiSize.vt = VT_BYREF | VT_I4;
1219         vpiSize.plVal = (long*) &nBufSize;
1220         // argument flag changed (VT_BOOL is short)
1221         VARIANT_BOOL changed = 0;
1222         VARIANT vpboolChanged;
1223         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1224         vpboolChanged.pboolVal = &changed;
1225         // argument return value (VT_BOOL is short)
1226         VARIANT vboolHandled;
1227         vboolHandled.vt = VT_BOOL;
1228         vboolHandled.boolVal = false;
1229
1230         // invoke method by name, reverse order for arguments
1231         // for VC, if the invoked function changes the buffer address, 
1232         // it must free the old buffer with SysFreeString
1233         // VB does it automatically
1234         // VARIANT_BOOL DiffingPreprocessW(BSTR * buffer, UINT * nSize, VARIANT_BOOL * bChanged)
1235         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PrediffBufferW", opFxn[3], 
1236                             vpboolChanged, vpiSize, vpbstrBuf);
1237         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1238         if (bSuccess && changed)
1239         {
1240                 // remove trailing charracters in the rare case that bstrBuf was not resized 
1241                 if (SysStringLen(bstrBuf) != nBufSize)
1242                         bSuccess = !FAILED(SysReAllocStringLen(&bstrBuf, bstrBuf, nBufSize));
1243                 if (bSuccess)
1244                         nChanged ++;
1245         }
1246
1247         // clear the returned variant
1248         VariantClear(&vboolHandled);
1249
1250         return  (bSuccess);
1251 }
1252
1253 bool InvokeUnpackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int & subcode)
1254 {
1255         LONG nArraySize;
1256         SafeArrayGetUBound(array.parray, 0, &nArraySize);
1257         ++nArraySize;
1258
1259         // prepare the arguments
1260         // argument file buffer
1261         VARIANT vparrayBuf;
1262         vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1263         vparrayBuf.pparray = &(array.parray);
1264         // argument buffer size by reference
1265         VARIANT vpiSize;
1266         vpiSize.vt = VT_BYREF | VT_I4;
1267         vpiSize.plVal = (long*) &nArraySize;
1268         // argument flag changed (VT_BOOL is short)
1269         VARIANT_BOOL changed = 0;
1270         VARIANT vpboolChanged;
1271         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1272         vpboolChanged.pboolVal = &changed;
1273         // argument subcode by reference
1274         VARIANT viSubcode;
1275         viSubcode.vt = VT_BYREF | VT_I4;
1276         viSubcode.plVal = (long*) &subcode;
1277         // argument return value (VT_BOOL is short)
1278         VARIANT vboolHandled;
1279         vboolHandled.vt = VT_BOOL;
1280         vboolHandled.boolVal = false;
1281
1282         // invoke method by name, reverse order for arguments
1283         // VARIANT_BOOL UnpackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT * subcode)
1284         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"UnpackBufferA", opFxn[4], 
1285                             viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1286         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1287         if (bSuccess && changed)
1288         {
1289                 // remove trailing charracters if the array was not resized
1290                 LONG nNewArraySize;
1291                 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1292                 ++nNewArraySize;
1293
1294                 if (nNewArraySize != nArraySize)
1295                 {
1296                         SAFEARRAYBOUND sab = {static_cast<ULONG>(nArraySize), 0};
1297                         SafeArrayRedim(array.parray, &sab);
1298                 }
1299                 nChanged ++;
1300         }
1301
1302         // clear the returned variant
1303         VariantClear(&vboolHandled);
1304
1305         return  (bSuccess);
1306 }
1307
1308 bool InvokePackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int subcode)
1309 {
1310         LONG nArraySize;
1311         SafeArrayGetUBound(array.parray, 0, &nArraySize);
1312         ++nArraySize;
1313
1314         // prepare the arguments
1315         // argument file buffer
1316         VARIANT vparrayBuf;
1317         vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1318         vparrayBuf.pparray = &(array.parray);
1319         // argument buffer size by reference
1320         VARIANT vpiSize;
1321         vpiSize.vt = VT_BYREF | VT_I4;
1322         vpiSize.plVal = (long*) &nArraySize;
1323         // argument flag changed (VT_BOOL is short)
1324         VARIANT_BOOL changed = 0;
1325         VARIANT vpboolChanged;
1326         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1327         vpboolChanged.pboolVal = &changed;
1328         // argument subcode
1329         VARIANT viSubcode;
1330         viSubcode.vt = VT_I4;
1331         viSubcode.lVal = subcode;
1332         // argument return value (VT_BOOL is short)
1333         VARIANT vboolHandled;
1334         vboolHandled.vt = VT_BOOL;
1335         vboolHandled.boolVal = false;
1336
1337         // invoke method by name, reverse order for arguments
1338         // VARIANT_BOOL PackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT subcode)
1339         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PackBufferA", opFxn[4], 
1340                             viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1341         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1342         if (bSuccess && changed)
1343         {
1344                 // remove trailing charracters if the array was not resized
1345                 LONG nNewArraySize;
1346                 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1347                 ++nNewArraySize;
1348
1349                 if (nNewArraySize != nArraySize)
1350                 {
1351                         SAFEARRAYBOUND sab = {static_cast<ULONG>(nArraySize), 0};
1352                         SafeArrayRedim(array.parray, &sab);
1353                 }
1354         }
1355
1356         // clear the returned variant
1357         VariantClear(&vboolHandled);
1358
1359         return  (bSuccess);
1360 }
1361
1362
1363 static bool unpack(const wchar_t *method, const String& source, const String& dest, int & nChanged, IDispatch *piScript, int & subCode)
1364 {
1365         // argument text  
1366         VARIANT vbstrSrc;
1367         vbstrSrc.vt = VT_BSTR;
1368         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(source).c_str());
1369         // argument transformed text 
1370         VARIANT vbstrDst;
1371         vbstrDst.vt = VT_BSTR;
1372         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(dest).c_str());
1373         // argument subcode by reference
1374         VARIANT vpiSubcode;
1375         vpiSubcode.vt = VT_BYREF | VT_I4;
1376         vpiSubcode.plVal = (long*) &subCode;
1377         // argument flag changed (VT_BOOL is short)
1378         VARIANT_BOOL changed = 0;
1379         VARIANT vpboolChanged;
1380         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1381         vpboolChanged.pboolVal = &changed;
1382         // argument return value (VT_BOOL is short)
1383         VARIANT vboolHandled;
1384         vboolHandled.vt = VT_BOOL;
1385         vboolHandled.boolVal = false;
1386
1387         // invoke method by name, reverse order for arguments
1388         // VARIANT_BOOL UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT * bSubcode)
1389         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, method, opFxn[4], 
1390                                   vpiSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1391         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1392         if (bSuccess && changed)
1393                 nChanged ++;
1394
1395         // clear the returned variant
1396         VariantClear(&vboolHandled);
1397
1398         return  (bSuccess);
1399 }
1400
1401 static bool pack(const wchar_t *method, const String& source, const String& dest, int & nChanged, IDispatch *piScript, int & subCode)
1402 {
1403         // argument text  
1404         VARIANT vbstrSrc;
1405         vbstrSrc.vt = VT_BSTR;
1406         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(source).c_str());
1407         // argument transformed text 
1408         VARIANT vbstrDst;
1409         vbstrDst.vt = VT_BSTR;
1410         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(dest).c_str());
1411         // argument subcode
1412         VARIANT viSubcode;
1413         viSubcode.vt = VT_I4;
1414         viSubcode.lVal = subCode;
1415         // argument flag changed (VT_BOOL is short)
1416         VARIANT_BOOL changed = 0;
1417         VARIANT vpboolChanged;
1418         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1419         vpboolChanged.pboolVal = &changed;
1420         // argument return value (VT_BOOL is short)
1421         VARIANT vboolHandled;
1422         vboolHandled.vt = VT_BOOL;
1423         vboolHandled.boolVal = false;
1424
1425         // invoke method by name, reverse order for arguments
1426         // VARIANT_BOOL PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT bSubcode)
1427         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, method, opFxn[4], 
1428                                   viSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1429         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1430         if (bSuccess && changed)
1431                 nChanged ++;
1432
1433         // clear the returned variant
1434         VariantClear(&vboolHandled);
1435
1436         return  (bSuccess);
1437 }
1438
1439 bool InvokeUnpackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int & subCode)
1440 {
1441         return unpack(L"UnpackFile", fileSource, fileDest, nChanged, piScript, subCode);
1442 }
1443
1444 bool InvokePackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1445 {
1446         return pack(L"PackFile", fileSource, fileDest, nChanged, piScript, subCode);
1447 }
1448
1449 bool InvokeUnpackFolder(const String& fileSource, const String& folderDest, int & nChanged, IDispatch *piScript, int & subCode)
1450 {
1451         return unpack(L"UnpackFolder", fileSource, folderDest, nChanged, piScript, subCode);
1452 }
1453
1454 bool InvokePackFolder(const String& folderSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1455 {
1456         return pack(L"PackFolder", folderSource, fileDest, nChanged, piScript, subCode);
1457 }
1458
1459 bool InvokePrediffFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript)
1460 {
1461         // argument text  
1462         VARIANT vbstrSrc;
1463         vbstrSrc.vt = VT_BSTR;
1464         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(fileSource).c_str());
1465         // argument transformed text 
1466         VARIANT vbstrDst;
1467         vbstrDst.vt = VT_BSTR;
1468         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(fileDest).c_str());
1469         // argument flag changed (VT_BOOL is short)
1470         VARIANT_BOOL changed = 0;
1471         VARIANT vpboolChanged;
1472         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1473         vpboolChanged.pboolVal = &changed;
1474         // argument return value (VT_BOOL is short)
1475         VARIANT vboolHandled;
1476         vboolHandled.vt = VT_BOOL;
1477         vboolHandled.boolVal = false;
1478
1479         // invoke method by name, reverse order for arguments
1480         // VARIANT_BOOL PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged)
1481         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PrediffFile", opFxn[3], 
1482                             vpboolChanged, vbstrDst, vbstrSrc);
1483         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1484         if (bSuccess && changed)
1485                 nChanged ++;
1486
1487         // clear the returned variant
1488         VariantClear(&vboolHandled);
1489
1490         return  (bSuccess);
1491 }
1492
1493
1494 bool InvokeTransformText(String & text, int & changed, IDispatch *piScript, int fncId)
1495 {
1496         // argument text  
1497         VARIANT pvPszBuf;
1498         pvPszBuf.vt = VT_BSTR;
1499         pvPszBuf.bstrVal = SysAllocString(ucr::toUTF16(text).c_str());
1500         // argument transformed text 
1501         VARIANT vTransformed;
1502         vTransformed.vt = VT_BSTR;
1503         vTransformed.bstrVal = nullptr;
1504
1505         // invoke method by ordinal
1506         // BSTR customFunction(BSTR text)
1507         HRESULT h = ::safeInvokeA(piScript, &vTransformed, fncId, opFxn[1], pvPszBuf);
1508
1509         if (! FAILED(h) && vTransformed.bstrVal)
1510         {
1511                 text = ucr::toTString(vTransformed.bstrVal);
1512                 changed = true;
1513         }
1514         else
1515                 changed = false;
1516
1517         // clear the returned variant
1518         VariantClear(&vTransformed);
1519
1520         return (! FAILED(h));
1521 }
1522
1523 bool InvokeIsFolder(const String& path, IDispatch *piScript)
1524 {
1525         // argument text  
1526         VARIANT vbstrPath;
1527         vbstrPath.vt = VT_BSTR;
1528         vbstrPath.bstrVal = SysAllocString(ucr::toUTF16(path).c_str());
1529
1530         VARIANT vboolHandled;
1531         vboolHandled.vt = VT_BOOL;
1532         vboolHandled.boolVal = false;
1533
1534         // invoke method by name, reverse order for arguments
1535         // VARIANT_BOOL ShowSettingsDialog()
1536         HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"IsFolder", opFxn[1], vbstrPath);
1537         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1538
1539         // clear the returned variant
1540         VariantClear(&vboolHandled);
1541
1542         return (bSuccess);
1543 }
1544
1545 bool InvokeShowSettingsDialog(IDispatch *piScript)
1546 {
1547         VARIANT vboolHandled;
1548         vboolHandled.vt = VT_BOOL;
1549         vboolHandled.boolVal = false;
1550
1551         // invoke method by name, reverse order for arguments
1552         // VARIANT_BOOL ShowSettingsDialog()
1553         HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"ShowSettingsDialog", opFxn[0]);
1554         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1555
1556         // clear the returned variant
1557         VariantClear(&vboolHandled);
1558
1559         return (bSuccess);
1560 }
1561
1562 }