public void Run(IPluginContext context)
    {
        Dictionary <string, ModInfo> modDict = new();
        var modService = context.Services.Get <IModManager>();

        foreach (ModInfo mod in modService.GetAllModInfo())
        {
            string key      = $"{mod.Name} v{mod.Version} by {mod.Author}";
            string finalKey = key;
            int    count    = 0;
            while (modDict.ContainsKey(finalKey))
            {
                finalKey = key + $" [{count++}]";
            }
            modDict.Add(finalKey, mod);
        }

        var    activeMod  = context.Services.Get <ModInfo>();
        string currentKey = modDict.First(i => i.Value.FolderPath == activeMod.FolderPath).Key;

        var options = new ChangelistOptionForm()
        {
            Mods         = modDict.Select(i => i.Key).OrderBy(i => i).ToList(),
            UnchangedMod = currentKey,
            ChangedMod   = currentKey
        };

        var dialogService = context.Services.Get <IDialogService>();


        var optionService = context.Services.Get <IPluginService>();

        do
        {
            if (!optionService.RequestOptions(options))
            {
                return;
            }

            if (options.UnchangedMod == options.ChangedMod)
            {
                dialogService.ShowMessageBox(MessageBoxArgs.Ok(
                                                 "Invalid Options",
                                                 "The source mod must be different to the destination mod"
                                                 ));
            }
        } while (options.UnchangedMod == options.ChangedMod);

        var unchangedMod = modDict[options.UnchangedMod];
        var changedMod   = modDict[options.ChangedMod];

        var kernelFactory = context.Services.Get <IModServiceGetterFactory>();

        var unchangedServices = unchangedMod.FolderPath == activeMod.FolderPath ? context.Services : kernelFactory.Create(unchangedMod);
        var changedServices   = changedMod.FolderPath == activeMod.FolderPath ? context.Services : kernelFactory.Create(changedMod);

        var changelist = BuildChangelist(options, unchangedServices, changedServices);

        string ext = options.OutputType switch
        {
            OutputType.XML => ".xml",
            OutputType.TSV => ".tsv",
            _ => throw new Exception("Unexpected output type")
        };

        var file = FileUtil.MakeUniquePath(Path.Combine(FileUtil.DesktopDirectory, "changelist" + ext));

        if (options.OutputType == OutputType.XML)
        {
            OutputXml(file, changelist);
            var proc = Process.Start("notepad.exe", file);
        }
        else if (options.OutputType == OutputType.TSV)
        {
            OutputTsv(file, changelist);
            dialogService.ShowMessageBox(MessageBoxArgs.Ok("Generation complete", $"Changelist output to file:\n'{file}'"));
        }
    }
    private static IEnumerable <ChangeInfo> BuildChangelist(ChangelistOptionForm options, IServiceGetter unchangedServices, IServiceGetter changedServices)
    {
        List <ChangeInfo> changelist = new();

        if (options.Ability)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IAbilityService>(), changedServices.Get <IAbilityService>()));
        }
        if (options.BaseWarrior)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IBaseWarriorService>(), changedServices.Get <IBaseWarriorService>()));
        }
        if (options.BattleConfigs)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IBattleConfigService>(), changedServices.Get <IBattleConfigService>()));
        }
        if (options.Building)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IBuildingService>(), changedServices.Get <IBuildingService>()));
        }
        if (options.Episode)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IEpisodeService>(), changedServices.Get <IEpisodeService>()));
        }
        if (options.EventSpeaker)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IEventSpeakerService>(), changedServices.Get <IEventSpeakerService>()));
        }
        if (options.Gimmicks)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IGimmickService>(), changedServices.Get <IGimmickService>()));
        }
        if (options.Item)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IItemService>(), changedServices.Get <IItemService>()));
        }
        if (options.Kingdoms)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IKingdomService>(), changedServices.Get <IKingdomService>()));
        }
        if (options.Move)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IMoveService>(), changedServices.Get <IMoveService>()));
        }
        if (options.Pokemon)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IPokemonService>(), changedServices.Get <IPokemonService>()));
        }
        if (options.ScenarioPokemon)
        {
            changelist.AddRange(GetScenarioPokemonChangelist(unchangedServices.Get <IScenarioPokemonService>(), changedServices.Get <IScenarioPokemonService>()));
        }
        if (options.ScenarioWarrior)
        {
            changelist.AddRange(GetScenarioWarriorChangelist(unchangedServices.Get <IScenarioWarriorService>(), changedServices.Get <IScenarioWarriorService>()));
        }
        if (options.Text)
        {
            changelist.AddRange(GetTextChangelist(unchangedServices.Get <IMsgBlockService>(), changedServices.Get <IMsgBlockService>()));
        }
        if (options.WarriorSkill)
        {
            changelist.AddRange(GenericGetChangelist(unchangedServices.Get <IWarriorSkillService>(), changedServices.Get <IWarriorSkillService>()));
        }

        return(changelist);
    }