Inheritance: TagLayoutWriter
        public override bool Execute(List<string> args)
        {
            if (args.Count != 2)
                return false;
            var type = args[0];
            var outDir = args[1];
            TagLayoutWriter writer;
            switch (type)
            {
                case "csharp":
                    writer = new CSharpLayoutWriter();
                    break;
                case "cpp":
                    writer = new CppLayoutWriter();
                    break;
                default:
                    return false;
            }
            Directory.CreateDirectory(outDir);
            var count = 0;
            using (var stream = _info.OpenCacheRead())
            {
                foreach (var groupTag in _cache.Tags.NonNull().Select(t => t.Group.Tag).Distinct())
                {
                    TagLayoutGuess layout = null;
                    TagInstance lastTag = null;
                    foreach (var tag in _cache.Tags.FindAllInGroup(groupTag))
                    {
                        Console.Write("Analyzing ");
                        TagPrinter.PrintTagShort(tag);

                        lastTag = tag;
                        var analyzer = new TagAnalyzer(_cache);
                        var data = _cache.ExtractTag(stream, tag);
                        var tagLayout = analyzer.Analyze(data);
                        if (layout != null)
                            layout.Merge(tagLayout);
                        else
                            layout = tagLayout;
                    }
                    if (layout != null && lastTag != null)
                    {
                        Console.WriteLine("Writing {0} layout", groupTag);
                        var name = _info.StringIDs.GetString(lastTag.Group.Name);
                        var tagLayout = LayoutGuessFinalizer.MakeLayout(layout, name, groupTag);
                        var path = Path.Combine(outDir, writer.GetSuggestedFileName(tagLayout));
                        writer.WriteLayout(tagLayout, path);
                        count++;
                    }
                }
            }
            Console.WriteLine("Successfully generated {0} layouts!", count);
            return true;
        }
        public override bool Execute(List<string> args)
        {
            if (args.Count != 3)
                return false;
            var inDir = args[0];
            var type = args[1];
            var outDir = args[2];
            TagLayoutWriter writer;
            switch (type)
            {
                case "csharp":
                    writer = new CSharpLayoutWriter();
                    break;
                case "cpp":
                    writer = new CppLayoutWriter();
                    break;
                default:
                    return false;
            }
            Directory.CreateDirectory(outDir);

            // For each tag whose tag group hasn't been processed yet, load its
            // plugin into a TagLayout and then write it using the layout
            // writer for the output type. We need an actual tag reference in
            // order to look up the group name without using a static table.
            var processedGroups = new HashSet<Tag>();
            var numConflicts = 0;
            foreach (var tag in Info.Cache.Tags.NonNull().Where(tag => !processedGroups.Contains(tag.Group.Tag)))
            {
                processedGroups.Add(tag.Group.Tag);

                // Get the plugin path and skip it if it doesn't exist
                var pluginFileName = SanitizeGroupTagName(tag.Group.Tag.ToString()) + ".xml";
                var pluginPath = Path.Combine(inDir, pluginFileName);
                if (!File.Exists(pluginPath))
                {
                    Console.Error.WriteLine("WARNING: No plugin found for the '{0}' tag group", tag.Group.Tag);
                    continue;
                }

                Console.WriteLine("Converting {0}...", pluginFileName);

                // Load the plugin into a layout
                AssemblyPluginLoadResults loadedPlugin;
                var groupName = Info.StringIDs.GetString(tag.Group.Name);
                using (var reader = XmlReader.Create(pluginPath))
                    loadedPlugin = AssemblyPluginLoader.LoadPlugin(reader, groupName, tag.Group.Tag);

                // Warn the user about conflicts
                numConflicts += loadedPlugin.Conflicts.Count;
                foreach (var conflict in loadedPlugin.Conflicts)
                    Console.WriteLine("WARNING: Field \"{0}\" at offset 0x{1:X} in block \"{2}\" conflicts!", conflict.Name, conflict.Offset, conflict.Block ?? "(root)");

                // Write it
                var outPath = Path.Combine(outDir, writer.GetSuggestedFileName(loadedPlugin.Layout));
                writer.WriteLayout(loadedPlugin.Layout, outPath);
            }
            Console.WriteLine("Successfully converted {0} plugins!", processedGroups.Count);
            if (numConflicts > 0)
                Console.WriteLine("However, {0} conflicts were found. You MUST fix these yourself!", numConflicts);
            return true;
        }