<commit_before>﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml.Linq;
using Sandbox;
using Sandbox.Game.Entities.Cube;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using Sandbox.ModAPI.Interfaces;
using SpaceEngineers.Game;
using VRage.Plugins;
using VRage.Scripting;
using VRage.Utils;
using IMyTerminalBlock = Sandbox.ModAPI.Ingame.IMyTerminalBlock;

namespace Malware.MDKWhitelistExtractor
{
    public class ExtractorPlugin : IPlugin
    {
        const string ObjectBuilderPrefix = "MyObjectBuilder_";
        CommandLine _commandLine;
        bool _firstInit = true;

        public SpaceEngineersGame Game { get; private set; }

        public void Dispose() { }

        public void Init(object gameInstance)
        {
            _commandLine = new CommandLine(Environment.CommandLine);

            Game = (SpaceEngineersGame)gameInstance;
        }

        public void Update()
        {
            if (_firstInit)
            {
                _firstInit = false;
                MySession.AfterLoading += MySession_AfterLoading;
                MySessionLoader.LoadInventoryScene();
            }
        }

        void WriteWhitelists(string[] targets)
        {
            var ingameTypes = new List<string>();
            var modTypes = new List<string>();
            foreach (var item in MyScriptCompiler.Static.Whitelist.GetWhitelist())
            {
                if ((item.Value & MyWhitelistTarget.Ingame) != 0)
                    ingameTypes.Add(item.Key);
                if ((item.Value & MyWhitelistTarget.ModApi) != 0)
                    modTypes.Add(item.Key);
            }

            foreach (var target in targets)
            {
                File.WriteAllText(target, string.Join(Environment.NewLine, ingameTypes));
                var extension = Path.GetExtension(target);
                var modTarget = Path.ChangeExtension(target, ".mod" + extension);
                File.WriteAllText(modTarget, string.Join(Environment.NewLine, modTypes));
            }
        }

        void GrabTerminalActions(CommandLine commandLine)
        {
            try
            {
                var targetsArgumentIndex = commandLine.IndexOf("-terminalcaches");
                if (targetsArgumentIndex == -1 || targetsArgumentIndex == commandLine.Count - 1)
                    return;
                var targetsArgument = commandLine[targetsArgumentIndex + 1];
                var targets = targetsArgument.Split(';');

                var gameAssembly = Game.GetType().Assembly;
                var blockTypes = FindBlocks(gameAssembly).ToArray();
                var blocks = new List<BlockInfo>();
                var experimentalMode = MySandboxGame.Config.ExperimentalMode;
                MySandboxGame.Config.ExperimentalMode = true;
                try
                {
                    foreach (var blockType in blockTypes)
                    {
                        var instance = (IMyTerminalBlock)Activator.CreateInstance(blockType);
                        var actions = new List<ITerminalAction>(new List<ITerminalAction>());
                        var properties = new List<ITerminalProperty>();
                        instance.GetActions(actions);
                        instance.GetProperties(properties);
                        var blockInfo = new BlockInfo(blockType, FindTypeDefinition(blockType), FindInterface(blockType), actions, properties);
                        if (blockInfo.BlockInterfaceType != null && blocks.All(b => b.BlockInterfaceType != blockInfo.BlockInterfaceType))
                            blocks.Add(blockInfo);
                    }
                }
                finally
                {
                    MySandboxGame.Config.ExperimentalMode = experimentalMode;
                }

                WriteTerminals(blocks, targets);
            }
            catch (ReflectionTypeLoadException e)
            {
                foreach (var loaderException in e.LoaderExceptions) MyLog.Default.Error(loaderException.ToString());
                throw;
            }
        }

        void WriteTerminals(List<BlockInfo> blocks, string[] targets)
        {
            var document = new XDocument(new XElement("terminals"));
            foreach (var blockInfo in blocks)
                // ReSharper disable once PossibleNullReferenceException
                document.Root.Add(blockInfo.ToXElement());

            foreach (var target in targets)
                document.Save(target);
        }

        void MySession_AfterLoading()
        {
            GrabWhitelist(_commandLine);
            GrabTerminalActions(_commandLine);
            Task.Run(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(5));
                MySandboxGame.ExitThreadSafe();
            });
        }

        string FindTypeDefinition(Type block)
        {
            var attr = block.GetCustomAttribute<MyCubeBlockTypeAttribute>();
            if (attr == null)
                return null;
            return attr.ObjectBuilderType.Name.StartsWith(ObjectBuilderPrefix) ? attr.ObjectBuilderType.Name.Substring(ObjectBuilderPrefix.Length) : attr.ObjectBuilderType.Name;
        }

        Type FindInterface(Type block)
        {
            var attr = block.GetCustomAttribute<MyTerminalInterfaceAttribute>();
            return attr?.LinkedTypes.FirstOrDefault(l => l.Namespace?.EndsWith(".Ingame", StringComparison.OrdinalIgnoreCase) ?? false);

            //var interfaces = block.GetInterfaces().Where(i => typeof(IMyTerminalBlock).IsAssignableFrom(i) && (i.Namespace?.EndsWith(".Ingame") ?? false)).ToArray();
            //var candidateInterfaces = interfaces.Where(iface =>
            //    /* bad interface inheritance workaround */
            //    iface != typeof(IMyTerminalBlock) && iface != typeof(IMyFunctionalBlock) &&
            //    /* workaround end */
            //    !interfaces.Any(i => iface != i && iface.IsAssignableFrom(i))).ToArray();
            //return candidateInterfaces.SingleOrDefault();
        }

        IEnumerable<Type> FindBlocks(Assembly gameAssembly, HashSet<AssemblyName> visitedAssemblies = null)
        {
            visitedAssemblies = visitedAssemblies ?? new HashSet<AssemblyName>(new AssemblyNameComparer());
            visitedAssemblies.Add(gameAssembly.GetName());
            var companyAttribute = gameAssembly.GetCustomAttribute<AssemblyCompanyAttribute>();
            if (companyAttribute?.Company == "Microsoft Corporation")
                yield break;

            var types = gameAssembly.DefinedTypes.Where(type => type.HasAttribute<MyCubeBlockTypeAttribute>());
            foreach (var type in types)
            {
                if (type.IsAbstract)<commit_msg>Fixed WhitelistExtractor crash (#264)<commit_after>﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Xml.Linq;
using Sandbox;
using Sandbox.Game.Entities.Cube;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using Sandbox.ModAPI.Interfaces;
using SpaceEngineers.Game;
using VRage.Plugins;
using VRage.Scripting;
using VRage.Utils;
using IMyTerminalBlock = Sandbox.ModAPI.Ingame.IMyTerminalBlock;

namespace Malware.MDKWhitelistExtractor
{
    public class ExtractorPlugin : IPlugin
    {
        const string ObjectBuilderPrefix = "MyObjectBuilder_";
        CommandLine _commandLine;
        bool _firstInit = true;

        public SpaceEngineersGame Game { get; private set; }

        public void Dispose() { }

        public void Init(object gameInstance)
        {
            _commandLine = new CommandLine(Environment.CommandLine);

            Game = (SpaceEngineersGame)gameInstance;
        }

        public void Update()
        {
            if (_firstInit)
            {
                _firstInit = false;
                MySession.AfterLoading += MySession_AfterLoading;
                MySessionLoader.LoadInventoryScene();
            }
        }

        void WriteWhitelists(string[] targets)
        {
            var ingameTypes = new List<string>();
            var modTypes = new List<string>();
            foreach (var item in MyScriptCompiler.Static.Whitelist.GetWhitelist())
            {
                if ((item.Value & MyWhitelistTarget.Ingame) != 0)
                    ingameTypes.Add(item.Key);
                if ((item.Value & MyWhitelistTarget.ModApi) != 0)
                    modTypes.Add(item.Key);
            }

            foreach (var target in targets)
            {
                File.WriteAllText(target, string.Join(Environment.NewLine, ingameTypes));
                var extension = Path.GetExtension(target);
                var modTarget = Path.ChangeExtension(target, ".mod" + extension);
                File.WriteAllText(modTarget, string.Join(Environment.NewLine, modTypes));
            }
        }

        void GrabTerminalActions(CommandLine commandLine)
        {
            try
            {
                var targetsArgumentIndex = commandLine.IndexOf("-terminalcaches");
                if (targetsArgumentIndex == -1 || targetsArgumentIndex == commandLine.Count - 1)
                    return;
                var targetsArgument = commandLine[targetsArgumentIndex + 1];
                var targets = targetsArgument.Split(';');

                var gameAssembly = Game.GetType().Assembly;
                var blockTypes = FindBlocks(gameAssembly).ToArray();
                var blocks = new List<BlockInfo>();
                var experimentalMode = MySandboxGame.Config.ExperimentalMode;
                MySandboxGame.Config.ExperimentalMode = true;
                try
                {
                    foreach (var blockType in blockTypes)
                    {
                        var instance = (IMyTerminalBlock)Activator.CreateInstance(blockType);
                        var actions = new List<ITerminalAction>(new List<ITerminalAction>());
                        var properties = new List<ITerminalProperty>();
                        instance.GetActions(actions);
                        instance.GetProperties(properties);
                        var blockInfo = new BlockInfo(blockType, FindTypeDefinition(blockType), FindInterface(blockType), actions, properties);
                        if (blockInfo.BlockInterfaceType != null && blocks.All(b => b.BlockInterfaceType != blockInfo.BlockInterfaceType))
                            blocks.Add(blockInfo);
                    }
                }
                finally
                {
                    MySandboxGame.Config.ExperimentalMode = experimentalMode;
                }

                WriteTerminals(blocks, targets);
            }
            catch (ReflectionTypeLoadException e)
            {
                foreach (var loaderException in e.LoaderExceptions) MyLog.Default.Error(loaderException.ToString());
                throw;
            }
        }

        void WriteTerminals(List<BlockInfo> blocks, string[] targets)
        {
            var document = new XDocument(new XElement("terminals"));
            foreach (var blockInfo in blocks)
                // ReSharper disable once PossibleNullReferenceException
                document.Root.Add(blockInfo.ToXElement());

            foreach (var target in targets)
                document.Save(target);
        }

        void MySession_AfterLoading()
        {
            GrabWhitelist(_commandLine);
            GrabTerminalActions(_commandLine);
            Task.Run(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(5));
                MySandboxGame.ExitThreadSafe();
            });
        }

        string FindTypeDefinition(Type block)
        {
            var attr = block.GetCustomAttribute<MyCubeBlockTypeAttribute>();
            if (attr == null)
                return null;
            return attr.ObjectBuilderType.Name.StartsWith(ObjectBuilderPrefix) ? attr.ObjectBuilderType.Name.Substring(ObjectBuilderPrefix.Length) : attr.ObjectBuilderType.Name;
        }

        Type FindInterface(Type block)
        {
            var attr = block.GetCustomAttribute<MyTerminalInterfaceAttribute>();
            return attr?.LinkedTypes.FirstOrDefault(l => l.Namespace?.EndsWith(".Ingame", StringComparison.OrdinalIgnoreCase) ?? false);

            //var interfaces = block.GetInterfaces().Where(i => typeof(IMyTerminalBlock).IsAssignableFrom(i) && (i.Namespace?.EndsWith(".Ingame") ?? false)).ToArray();
            //var candidateInterfaces = interfaces.Where(iface =>
            //    /* bad interface inheritance workaround */
            //    iface != typeof(IMyTerminalBlock) && iface != typeof(IMyFunctionalBlock) &&
            //    /* workaround end */
            //    !interfaces.Any(i => iface != i && iface.IsAssignableFrom(i))).ToArray();
            //return candidateInterfaces.SingleOrDefault();
        }

        IEnumerable<Type> FindBlocks(Assembly gameAssembly, HashSet<AssemblyName> visitedAssemblies = null)
        {
            visitedAssemblies = visitedAssemblies ?? new HashSet<AssemblyName>(new AssemblyNameComparer());
            visitedAssemblies.Add(gameAssembly.GetName());
            var companyAttribute = gameAssembly.GetCustomAttribute<AssemblyCompanyAttribute>();
            if (companyAttribute?.Company == "Microsoft Corporation" || companyAttribute?.Company == "ProtoBuf.Net.Core")
                yield break;

            var types = gameAssembly.DefinedTypes.Where(type => type.HasAttribute<MyCubeBlockTypeAttribute>());
            foreach (var type in types)
            {
                if (type.IsAbstract)
{'fraction_non_alphanumeric': 0.09270429412609507, 'fraction_numerical': 0.001077121930202499, 'mean_word_length': 1.985423365487674, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2759078', 'n_tokens_mistral': 3475, 'n_tokens_neox': 3304, 'n_words': 664}