static bool ReadPluginsData(string pluginFile)
        {
            modEntryList  = new Dictionary <string, List <ModEntry> >();
            modPluginList = new Dictionary <string, PluginEntry>();

            Assembly pluginAssembly;

            print("[i] read plugin file: '" + pluginFile.Replace(assemblyFolder, ".\\") + "' ...");
            try
            {
                pluginAssembly = Assembly.LoadFrom(pluginFile); // var hooksAssembly = typeof(GM).Assembly;
            }
            catch (System.Exception ex)
            {
                print("   ERROR: Can't load assembly (" + ex.Message + ")!");
                return(false);
            }

            foreach (Type type in pluginAssembly.GetTypes())
            {
                string pluginTag           = "";
                var    pluginTagAttributes = Attribute.GetCustomAttributes(type)
                                             .Where(e => e.GetType().FullName == "dotNetMT.PluginTagAttribute").ToArray();
                if (pluginTagAttributes.Length > 0)
                {
                    pluginTag = type.FullName;
                    var pta = (PluginTagAttribute)pluginTagAttributes[0];
                    if (pta.pluginName != "")
                    {
                        var entry = new PluginEntry();
                        entry.pluginTag          = pluginTag;
                        entry.pluginName         = pta.pluginName;
                        entry.pluginAuthor       = pta.pluginAuthor;
                        entry.pluginNote         = pta.pluginNote;
                        entry.pluginRequired     = pta.pluginRequired;
                        entry.active             = true;
                        modPluginList[pluginTag] = entry;
                    }
                }

                foreach (MethodInfo method in type.GetMethods())
                {
                    foreach (var pma in Attribute.GetCustomAttributes(method))
                    {
                        ModEntry entry;
                        if (pma.GetType().FullName == "dotNetMT.PluginHookAttribute")
                        {
                            var a = (PluginHookAttribute)pma;
                            entry                = new ModEntry();
                            entry.entryType      = ModEntryType.Hook;
                            entry.method         = method.Name;
                            entry.type           = type.Name;
                            entry.method_        = method;
                            entry.type_          = type;
                            entry.assembly       = a.assemblyName;
                            entry.type2hook      = a.typeName;
                            entry.method2hook    = a.methodName;
                            entry.hookOnBegin    = a.hookOnBegin;
                            entry.parameterCount = a.parameterCount;
                        }
                        else if (pma.GetType().FullName == "dotNetMT.PluginPatchAttribute")
                        {
                            var a = (PluginPatchAttribute)pma;
                            entry           = new ModEntry();
                            entry.entryType = ModEntryType.Patch;
                            entry.method    = method.Name;
                            entry.type      = type.Name;
                            entry.method_   = method;
                            entry.type_     = type;
                            entry.assembly  = a.assemblyName;
                        }
                        else
                        {
                            continue;
                        }

                        entry.pluginTag = pluginTag;

                        if (!modEntryList.ContainsKey(entry.assembly))
                        {
                            modEntryList.Add(entry.assembly, new List <ModEntry>());
                        }
                        modEntryList[entry.assembly].Add(entry);
                    }
                }
            }

            foreach (var k in modPluginList.Keys)
            {
                modPluginList[k].active = DNMT.Config.Get("plugins", k, true);
            }

            return(true);
        }
        //-------------------------------------------------------------------------------------------------------------

        static void InjectHook(ModuleDefinition module, MethodDefinition method, ModEntry entry)
        {
            if (entry.entryType != ModEntryType.Hook)
            {
                return;
            }

            var hookTypeRef   = module.ImportReference(entry.type_);
            var hookMethodRef = module.ImportReference(entry.method_);

            // object[] interceptedArgs;
            // object hookResult;
            var interceptedArgs = new VariableDefinition(method.Module.TypeSystem.Object.MakeArrayType());

            method.Body.Variables.Add(interceptedArgs);
            var hookResult = new VariableDefinition(method.Module.TypeSystem.Object);

            method.Body.Variables.Add(hookResult);

            var numArgs = method.Parameters.Count;
            var hook    = new List <Instruction>();

            // interceptedArgs = new object[numArgs];
            hook.Add(Instruction.Create(OpCodes.Ldc_I4, numArgs));
            hook.Add(Instruction.Create(OpCodes.Newarr, method.Module.TypeSystem.Object));
            hook.Add(Instruction.Create(OpCodes.Stloc, interceptedArgs));

            // rmh = methodof(this).MethodHandle;
            // hook.Add(Instruction.Create(OpCodes.Ldtoken, method));

            if (entry.hookOnBegin || method.ReturnType.FullName.EndsWith("Void"))
            {
                hook.Add(Instruction.Create(OpCodes.Ldnull));
            }
            else
            {
                hook.Add(Instruction.Create(OpCodes.Dup));
                if (method.ReturnType.IsByReference)
                {   // if the arg is a reference type, it must be copied and boxed
                    var refType = (ByReferenceType)method.ReturnType;
                    hook.Add(Instruction.Create(OpCodes.Ldobj, refType.ElementType));
                    hook.Add(Instruction.Create(OpCodes.Box, refType.ElementType));
                }
                else if (method.ReturnType.IsValueType)
                {   // if the arg descends from ValueType, it must be boxed to be converted to an object:
                    hook.Add(Instruction.Create(OpCodes.Box, method.ReturnType));
                }
            }

            // thisObj = static ? null : this;
            if (!method.IsStatic)
            {
                hook.Add(Instruction.Create(OpCodes.Ldarg_0));
            }
            else
            {
                hook.Add(Instruction.Create(OpCodes.Ldnull));
            }

            var i = 0;

            foreach (var param in method.Parameters)
            {
                // interceptedArgs[i] = (object)arg;
                hook.Add(Instruction.Create(OpCodes.Ldloc, interceptedArgs));
                hook.Add(Instruction.Create(OpCodes.Ldc_I4, i));
                hook.Add(Instruction.Create(OpCodes.Ldarg, param));
                if (param.ParameterType.IsByReference)
                {
                    // if the arg is a reference type, it must be copied and boxed
                    var refType = (ByReferenceType)param.ParameterType;
                    hook.Add(Instruction.Create(OpCodes.Ldobj, refType.ElementType));
                    hook.Add(Instruction.Create(OpCodes.Box, refType.ElementType));
                }
                else if (param.ParameterType.IsValueType)
                {
                    // if the arg descends from ValueType, it must be boxed to be converted to an object:
                    hook.Add(Instruction.Create(OpCodes.Box, param.ParameterType));
                }
                hook.Add(Instruction.Create(OpCodes.Stelem_Ref));
                i++;
            }

            // hookResult = HookRegistry.OnCall(rmh, thisObj, interceptedArgs);
            hook.Add(Instruction.Create(OpCodes.Ldloc, interceptedArgs));
            hook.Add(Instruction.Create(OpCodes.Call, hookMethodRef));
            hook.Add(Instruction.Create(OpCodes.Stloc, hookResult));

            // if (hookResult != null) {
            //     return (ReturnType)hookResult;
            // }
            hook.Add(Instruction.Create(OpCodes.Ldloc, hookResult));
            hook.Add(Instruction.Create(OpCodes.Ldnull));
            hook.Add(Instruction.Create(OpCodes.Ceq));

            if (entry.hookOnBegin)
            {
                hook.Add(Instruction.Create(OpCodes.Brtrue_S, method.Body.Instructions.First()));
            }
            else
            {   // do not replace last instruction, as result you can get branches bug (note - change is possible)
                method.Body.Instructions[method.Body.Instructions.Count - 1].OpCode  = OpCodes.Nop;
                method.Body.Instructions[method.Body.Instructions.Count - 1].Operand = null;
                method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
                hook.Add(Instruction.Create(OpCodes.Brtrue_S, method.Body.Instructions.Last()));
            }

            if (!method.ReturnType.FullName.EndsWith("Void"))
            {
                if (!entry.hookOnBegin)
                {
                    hook.Add(Instruction.Create(OpCodes.Pop));
                }
                hook.Add(Instruction.Create(OpCodes.Ldloc, hookResult));
                hook.Add(Instruction.Create(OpCodes.Castclass, method.ReturnType));
                hook.Add(Instruction.Create(OpCodes.Unbox_Any, method.ReturnType));
            }

            hook.Add(Instruction.Create(OpCodes.Ret));
            hook.Reverse();

            method.Body.SimplifyMacros();
            int index = entry.hookOnBegin ? 0 : method.Body.Instructions.Count - 1;

            foreach (var instruction in hook)
            {
                method.Body.Instructions.Insert(index, instruction);
            }
            method.Body.OptimizeMacros();
        }