コード例 #1
0
        public void OnSelect(ModListController cntrl)
        {
            Logger.log.Debug($"Selected IPAModCell {Plugin.Name} {Plugin.Version}");

            if (infoView == null)
            {
                PluginLoader.PluginMetadata updateInfo = null;

                try
                {
                    updateInfo = new PluginLoader.PluginMetadata
                    {
                        Name    = Plugin.Name,
                        Id      = Plugin.Name,
                        Version = new SemVer.Version(Plugin.Version)
                    };
                }
                catch (Exception e)
                {
                    Logger.log.Warn($"Could not generate fake update info for {Plugin.Name}");
                    Logger.log.Warn(e);
                }

                infoView = BeatSaberUI.CreateViewController <ModInfoViewController>();
                infoView.Init(icon, Plugin.Name, "v" + Plugin.Version.ToString(), "<color=#BFBFBF><i>Unknown Author</i>",
                              "This mod was written for IPA.\n===\n\n## No metadata is avaliable for this mod.\n\n" +
                              "Please contact the mod author and ask them to port it to BSIPA to provide more information.", updateInfo);
            }

            list.flow.SetSelected(infoView, immediate: list.flow.HasSelected);
        }
コード例 #2
0
        public override bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters)
        {// parameters should be (fully qualified name of provider type)
            if (parameters.Length != 1)
            {
                InvalidMessage = "Incorrect number of parameters";
                return(false);
            }

            RequireLoaded(meta);

            Type getType;

            try
            {
                getType = meta.Assembly.GetType(parameters[0]);
            }
            catch (ArgumentException)
            {
                InvalidMessage = $"Invalid type name {parameters[0]}";
                return(false);
            }
            catch (Exception e) when(e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException)
            {
                string filename;

                switch (e)
                {
                case FileNotFoundException fn:
                    filename = fn.FileName;
                    goto hasFilename;

                case FileLoadException fl:
                    filename = fl.FileName;
                    goto hasFilename;

                case BadImageFormatException bi:
                    filename = bi.FileName;
hasFilename:
                    InvalidMessage = $"Could not find {filename} while loading type";
                    break;

                default:
                    InvalidMessage = $"Error while loading type: {e}";
                    break;
                }

                return(false);
            }

            try
            {
                Config.Config.Register(getType);
                return(true);
            }
            catch (Exception e)
            {
                InvalidMessage = $"Error generated while creating delegate: {e}";
                return(false);
            }
        }
コード例 #3
0
        public void Init(Sprite icon, string name, string version, string author, string description, PluginLoader.PluginMetadata updateInfo, PluginManifest.LinksObject links = null)
        {
            Icon        = icon;
            Name        = name;
            Version     = version;
            Author      = author;
            Description = description;
            UpdateInfo  = updateInfo;

            if (rowTransformOriginal == null)
            {
                rowTransformOriginal = MenuButtonUI.Instance.GetPrivateField <RectTransform>("menuButtonsOriginal");
            }

            // i also have no clue why this is necessary
            rectTransform.anchorMin = new Vector2(0f, 0f);
            rectTransform.anchorMax = new Vector2(0.5f, 1f);

            var go = new GameObject("Info View", typeof(RectTransform));

            go.SetActive(false);
            go.AddComponent <RectMask2D>();
            view = go.AddComponent <ModInfoView>();
            var rt = view.transform as RectTransform;

            rt.SetParent(transform);
            rt.anchorMin        = new Vector2(0f, 0f);
            rt.anchorMax        = new Vector2(1f, 1f);
            rt.anchoredPosition = new Vector2(0f, 0);
            view.Init(this);
            go.SetActive(true);

            SetupLinks(links);
        }
コード例 #4
0
        public override bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters)
        { // parameters should be (name, fully qualified type)
            if (parameters.Length != 2)
            {
                InvalidMessage = "Incorrect number of parameters";
                return(false);
            }

            RequireLoaded(meta);

            Type type;

            try
            {
                type = meta.Assembly.GetType(parameters[1]);
            }
            catch (ArgumentException)
            {
                InvalidMessage = $"Invalid type name {parameters[1]}";
                return(false);
            }
            catch (Exception e) when(e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException)
            {
                var filename = "";

                switch (e)
                {
                case FileNotFoundException fn:
                    filename = fn.FileName;
                    break;

                case FileLoadException fl:
                    filename = fl.FileName;
                    break;

                case BadImageFormatException bi:
                    filename = bi.FileName;
                    break;
                }

                InvalidMessage = $"Could not find {filename} while loading type";
                return(false);
            }

            try
            {
                if (RegisterFeature(parameters[0], type))
                {
                    return(NewFeature = true);
                }

                InvalidMessage = $"Feature with name {parameters[0]} already exists";
                return(false);
            }
            catch (ArgumentException)
            {
                InvalidMessage = $"{type.FullName} not a subclass of {nameof(Feature)}";
                return(false);
            }
        }
コード例 #5
0
        public override bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters)
        {
            selfMeta = meta;

            RequireLoaded(meta);

            return(true);
        }
コード例 #6
0
        public void OnApplicationStart()
        {
            var harmony = HarmonyInstance.Create("com.Shoko84.beatsaber.FightSabers");

            harmony.PatchAll(Assembly.GetExecutingAssembly());
            fightSabersMetadata       = PluginManager.AllPlugins.Select(x => x.Metadata).First(x => x.Name == "FightSabers");
            BSEvents.menuSceneActive += OnMenuSceneActive;
            BSEvents.gameSceneActive += OnGameSceneActive;
            GameplaySetup.instance.AddTab("FS Modifiers", "FightSabers.UI.Views.FightSabersGameplaySetupView.bsml", FightSabersGameplaySetup.instance);
        }
コード例 #7
0
ファイル: Plugin.cs プロジェクト: Pespiri/CountersPlus
        public void Init(IPALogger log, PluginLoader.PluginMetadata metadata)
        {
            Logger = log;

            if (metadata != null)
            {
                PluginVersion = metadata.Version;
                Log("Version number set");
            }
        }
コード例 #8
0
        public void Init(Sprite icon, string name, string version, string author, string description, PluginLoader.PluginMetadata updateInfo, PluginManifest.LinksObject links = null, bool showEnDis = false, ModListFlowCoordinator mlfc = null)
        {
            showEnableDisable      = showEnDis;
            Plugin.OnConfigChaned -= OptHideButton;

            Icon        = icon;
            Name        = name;
            Version     = version;
            Author      = author;
            Description = description;
            UpdateInfo  = updateInfo;

            enabled = !PluginManager.IsDisabled(updateInfo);

            flowController = mlfc;

            if (rowTransformOriginal == null)
            {
                rowTransformOriginal = MenuButtonUI.Instance.GetPrivateField <RectTransform>("menuButtonsOriginal");
            }

            // i also have no clue why this is necessary
            rectTransform.anchorMin = new Vector2(0f, 0f);
            rectTransform.anchorMax = new Vector2(0.5f, 1f);

            var go = new GameObject("Info View", typeof(RectTransform));

            go.SetActive(false);
            go.AddComponent <RectMask2D>();
            view = go.AddComponent <ModInfoView>();
            var rt = view.transform as RectTransform;

            rt.SetParent(transform);
            rt.anchorMin        = new Vector2(0f, 0f);
            rt.anchorMax        = new Vector2(1f, 1f);
            rt.anchoredPosition = new Vector2(0f, 0);
            view.Init(this);
            go.SetActive(true);

            if (showEnDis)
            {
                restartMessage          = BeatSaberUI.CreateText(rectTransform, "A restart is required to apply", new Vector2(11f, 33.5f));
                restartMessage.fontSize = 4f;
                restartMessage.gameObject.SetActive(false);

                enableDisableButton = BeatSaberUI.CreateUIButton(rectTransform, "CreditsButton", new Vector2(33, 32), new Vector2(25, 10), ToggleEnable);
                enableDisableButton.GetComponentInChildren <StartMiddleEndButtonBackgroundController>().SetMiddleSprite();
                UpdateButtonText();

                Plugin.OnConfigChaned += OptHideButton;
                OptHideButton(Plugin.config.Value);
            }

            SetupLinks(links);
        }
コード例 #9
0
        public static Sprite GetEmbeddedIcon(this PluginLoader.PluginMetadata meta)
        {
            if (embeddedIcons.TryGetValue(meta, out var sprite))
            {
                return(sprite);
            }
            var icon = GetEmbeddedIconNoCache(meta);

            embeddedIcons.Add(meta, icon);
            return(icon);
        }
コード例 #10
0
        /// <summary>
        /// Adds an outside MonoBehaviour into the Counters+ system.
        /// <param name="model"/>The CustomCounter object.</param>
        /// <param name="defaults">Default configuration options for your custom counter.</param>
        /// <param name="restrictedPositions">Restrict your Custom Counter to any of these positions. Inputting no parameters would allow the Counter to use all that are available.</param>
        /// </summary>
        public static void Create <T>(T model, CustomConfigModel defaults = null, params ICounterPositions[] restrictedPositions) where T : CustomCounter
        {
            string modCreator = "";

            if (model.Mod != null)
            {
                modCreator = model.Mod.Name;
            }

            if (model.BSIPAMod != null)
            {
                PluginLoader.PluginMetadata pluginMetadata = PluginUtility.GetPluginMetadata(model.BSIPAMod);
                if (pluginMetadata != null)
                {
                    modCreator = pluginMetadata.Name;
                }
            }

            Plugin.Log($"Custom Counter ({model.Name}) added!", Plugin.LogInfo.Notice);

            foreach (CustomConfigModel potential in ConfigLoader.LoadCustomCounters())
            {
                if (potential.DisplayName == model.Name)
                {
                    if (potential.IsNew)
                    {
                        potential.IsNew = false;
                        potential.Save();
                    }
                    return;
                }
            }

            CustomConfigModel counter = new CustomConfigModel(model.Name)
            {
                DisplayName         = model.Name,
                SectionName         = model.SectionName,
                Enabled             = (defaults == null ? true : defaults.Enabled),
                Position            = (defaults == null ? ICounterPositions.BelowCombo : defaults.Position),
                Distance            = (defaults == null ? 2 : defaults.Distance),
                Counter             = model.Counter,
                ModCreator          = modCreator,
                IsNew               = true,
                RestrictedPositions = (restrictedPositions?.Count() == 0 || restrictedPositions == null) ? new ICounterPositions[] { } : restrictedPositions, //Thanks Viscoci for this
            };

            if (string.IsNullOrEmpty(counter.SectionName) || string.IsNullOrEmpty(counter.DisplayName))
            {
                throw new CustomCounterException("Custom Counter properties invalid. Please make sure SectionName and DisplayName are properly assigned.");
            }

            counter.Save();
        }
コード例 #11
0
        public void Init(IPALogger logger, PluginLoader.PluginMetadata metadata)
        {
            if (logger != null)
            {
                Logger.logger = logger;
                Logger.Log("Logger prepared");
            }

            if (metadata != null)
            {
                Plugin.PluginVersion = metadata.Version.ToString();
            }
        }
コード例 #12
0
        public LibraryModCell(ModListController list, PluginLoader.PluginMetadata plugin)
            : base($"{plugin.Name} <size=60%>v{plugin.Version}", plugin.Manifest.Author, null)
        {
            Plugin    = plugin;
            this.list = list;

            if (string.IsNullOrWhiteSpace(subtext))
            {
                subtext = "<color=#BFBFBF><i>Unspecified Author</i></color>";
            }

            icon = Utilities.DefaultLibraryIcon;
        }
コード例 #13
0
 public static Sprite GetIcon(this PluginLoader.PluginMetadata meta)
 {
     if (meta == null)
     {
         return(DefaultBSIPAIcon);
     }
     if (meta.IsBare)
     {
         return(DefaultLibraryIcon);
     }
     else
     {
         return(GetEmbeddedIcon(meta) ?? DefaultBSIPAIcon);
     }
 }
コード例 #14
0
        private static void PluginManager_PluginDisabled(PluginLoader.PluginMetadata plugin, bool needsRestart, WeakReference <BSIPAModCell> _self, PluginManager.PluginDisableDelegate ownDel)
        {
            if (!_self.TryGetTarget(out var self))
            {
                PluginManager.PluginDisabled -= ownDel;
                return;
            }

            if (plugin != self.Plugin)
            {
                return;
            }

            self.Update(false, needsRestart);
        }
コード例 #15
0
        public BSIPAIgnoredModCell(ModListController list, PluginLoader.PluginMetadata plugin)
            : base($"<color=#878787>{plugin.Name} <size=60%>v{plugin.Version}", "", Utilities.DefaultBSIPAIcon)
        {
            Plugin    = plugin;
            this.list = list;

            if (string.IsNullOrWhiteSpace(plugin.Manifest.Author))
            {
                authorText = "<color=#BFBFBF><i>Unspecified Author</i>";
            }
            else
            {
                authorText = plugin.Manifest.Author;
            }
            subtext = string.Format(authorFormat, authorText);
        }
コード例 #16
0
        public BSIPAModCell(ModListController list, PluginLoader.PluginMetadata plugin)
            : base("", "", null)
        {
            Plugin    = plugin;
            this.list = list;

            var thisWeakRef = new WeakReference <BSIPAModCell>(this);

            PluginManager.PluginDisableDelegate reflessDDel = null;
            reflessDDel = disableDel = (p, r) => PluginManager_PluginDisabled(p, r, thisWeakRef, reflessDDel); // some indirection to make it a weak link for GC
            PluginManager.PluginDisabled += reflessDDel;
            PluginManager.PluginEnableDelegate reflessEDel = null;
            reflessEDel = enableDel = (p, r) => PluginManager_PluginEnabled(p, r, thisWeakRef, reflessEDel); // some indirection to make it a weak link for GC
            PluginManager.PluginEnabled += reflessEDel;

            Update(propogate: false);
        }
コード例 #17
0
        /// <summary>
        /// Returns the PluginMetadata of a loaded BSIPA plugin
        /// </summary>
        public static PluginLoader.PluginMetadata GetPluginMetadata(string pluginName)
        {
            if (IsPluginPresent(pluginName))
            {
                PluginLoader.PluginMetadata metadataFromName = PluginManager.GetPlugin(pluginName).Metadata;
                PluginLoader.PluginMetadata metadataFromId   = PluginManager.GetPluginFromId(pluginName).Metadata;

                if (metadataFromName != null)
                {
                    return(metadataFromName);
                }
                else if (metadataFromId != null)
                {
                    return(metadataFromId);
                }
            }
            return(null);
        }
コード例 #18
0
        private static Sprite GetEmbeddedIconNoCache(PluginLoader.PluginMetadata meta)
        {
            if (meta.Assembly == null)
            {
                return(null);
            }
            if (meta.Manifest.IconPath == null)
            {
                return(null);
            }

            try
            {
                return(UIUtilities.LoadSpriteRaw(UIUtilities.GetResource(meta.Assembly, meta.Manifest.IconPath)));
            }
            catch (Exception e)
            {
                Logger.log.Error($"Error loading icon for {meta.Name}");
                Logger.log.Error(e);
                return(null);
            }
        }
コード例 #19
0
 /// <summary>
 /// Initializes the feature with the parameters provided in the definition.
 ///
 /// Note: When no parenthesis are provided, <paramref name="parameters"/> is an empty array.
 /// </summary>
 /// <remarks>
 /// This gets called BEFORE *your* `Init` method.
 ///
 /// Returning <see langword="false" /> does *not* prevent the plugin from being loaded. It simply prevents the feature from being used.
 /// </remarks>
 /// <param name="meta">the metadata of the plugin that is being prepared</param>
 /// <param name="parameters">the parameters passed to the feature definition, or null</param>
 /// <returns><see langword="true"/> if the feature is valid for the plugin, <see langword="false"/> otherwise</returns>
 public abstract bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters);
コード例 #20
0
        // returns false with both outs null for no such feature
        internal static bool TryParseFeature(string featureString, PluginLoader.PluginMetadata plugin,
                                             out Feature feature, out Exception failException, out bool featureValid, out FeatureParse parsed,
                                             FeatureParse?preParsed = null)
        {
            failException = null;
            feature       = null;
            featureValid  = false;

            if (preParsed == null)
            {
                var    builder    = new StringBuilder();
                string name       = null;
                var    parameters = new List <string>();

                bool escape           = false;
                int  parens           = 0;
                bool removeWhitespace = true;
                foreach (var chr in featureString)
                {
                    if (escape)
                    {
                        builder.Append(chr);
                        escape = false;
                    }
                    else
                    {
                        switch (chr)
                        {
                        case '\\':
                            escape = true;
                            break;

                        case '(':
                            parens++;
                            if (parens != 1)
                            {
                                goto default;
                            }
                            removeWhitespace = true;
                            name             = builder.ToString();
                            builder.Clear();
                            break;

                        case ')':
                            parens--;
                            if (parens != 0)
                            {
                                goto default;
                            }
                            goto case ',';

                        case ',':
                            if (parens > 1)
                            {
                                goto default;
                            }
                            parameters.Add(builder.ToString());
                            builder.Clear();
                            removeWhitespace = true;
                            break;

                        default:
                            if (removeWhitespace && !char.IsWhiteSpace(chr))
                            {
                                removeWhitespace = false;
                            }
                            if (!removeWhitespace)
                            {
                                builder.Append(chr);
                            }
                            break;
                        }
                    }
                }

                if (name == null)
                {
                    name = builder.ToString();
                }

                parsed = new FeatureParse(name, parameters.ToArray());

                if (parens != 0)
                {
                    failException = new Exception("Malformed feature definition");
                    return(false);
                }
            }
            else
            {
                parsed = preParsed.Value;
            }

            if (!featureTypes.TryGetValue(parsed.Name, out var featureType))
            {
                return(false);
            }

            try
            {
                if (!(Activator.CreateInstance(featureType) is Feature aFeature))
                {
                    failException = new InvalidCastException("Feature type not a subtype of Feature");
                    return(false);
                }

                featureValid = aFeature.Initialize(plugin, parsed.Parameters);
                feature      = aFeature;
                return(true);
            }
            catch (Exception e)
            {
                failException = e;
                return(false);
            }
        }
コード例 #21
0
ファイル: Plugin.cs プロジェクト: Jackzmc/CountersPlus
 public void Init(IPALogger log, PluginLoader.PluginMetadata metadata)
 {
     Logger.Init(log);
     PluginVersion = metadata?.Version;
 }
コード例 #22
0
ファイル: Plugin.cs プロジェクト: Shoko84/CustomSaberPlugin
        public void Init(IPALogger logger, [Config.Prefer("json")] IConfigProvider cfgProvider, PluginLoader.PluginMetadata metadata)
        {
            Logger.log = logger;
            Configuration.Init(cfgProvider);

            if (metadata != null)
            {
                PluginVersion = metadata.Version;
            }
        }
コード例 #23
0
 /// <summary>
 /// Constructs a ModPrefs object for the provide plugin.
 /// </summary>
 /// <param name="plugin">the plugin to get the preferences file for</param>
 public ModPrefs(PluginLoader.PluginMetadata plugin)
 {
     _instance = new IniFile(Path.Combine(Environment.CurrentDirectory, "UserData", "ModPrefs",
                                          $"{plugin.Name}.ini"));
 }
コード例 #24
0
 public override bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters)
 {
     return(meta.Id != null);
 }
コード例 #25
0
        public override bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters)
        { // parameters should be (assembly qualified lookup type, [fully qualified type]:[method name])
          // method should be static
            if (parameters.Length != 2)
            {
                InvalidMessage = "Incorrect number of parameters";
                return(false);
            }

            RequireLoaded(meta);

            var methodParts = parameters[1].Split(':');

            var type = Type.GetType(parameters[0], false);

            if (type == null)
            {
                InvalidMessage = $"Could not find type {parameters[0]}";
                return(false);
            }

            Type getType;

            try
            {
                getType = meta.Assembly.GetType(methodParts[0]);
            }
            catch (ArgumentException)
            {
                InvalidMessage = $"Invalid type name {methodParts[0]}";
                return(false);
            }
            catch (Exception e) when(e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException)
            {
                string filename;

                switch (e)
                {
                case FileNotFoundException fn:
                    filename = fn.FileName;
                    goto hasFilename;

                case FileLoadException fl:
                    filename = fl.FileName;
                    goto hasFilename;

                case BadImageFormatException bi:
                    filename = bi.FileName;
hasFilename:
                    InvalidMessage = $"Could not find {filename} while loading type";
                    break;

                default:
                    InvalidMessage = $"Error while loading type: {e}";
                    break;
                }

                return(false);
            }

            MethodInfo method;

            try
            {
                method = getType.GetMethod(methodParts[1], BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
                                           null, new[]
                {
                    typeof(object),
                    typeof(ParameterInfo),
                    typeof(PluginLoader.PluginMetadata)
                }, new ParameterModifier[0]);
            }
            catch (Exception e)
            {
                InvalidMessage = $"Error while loading type: {e}";
                return(false);
            }

            if (method == null)
            {
                InvalidMessage = $"Could not find method {methodParts[1]} in type {methodParts[0]}";
                return(false);
            }

            try
            {
                var del = (PluginInitInjector.InjectParameter)Delegate.CreateDelegate(typeof(PluginInitInjector.InjectParameter), null, method);
                PluginInitInjector.AddInjector(type, del);
                return(true);
            }
            catch (Exception e)
            {
                InvalidMessage = $"Error generated while creating delegate: {e}";
                return(false);
            }
        }
コード例 #26
0
 public override bool Initialize(PluginLoader.PluginMetadata meta, string[] parameters)
 {
     Logger.features.Info($"{meta.Name}: {string.Join(" ", parameters)}");
     return(true);
 }
コード例 #27
0
 public override bool BeforeLoad(PluginLoader.PluginMetadata plugin)
 {
     return(plugin != selfMeta);
 }
コード例 #28
0
 /// <summary>
 /// Called before a plugin is loaded. This should never throw an exception. An exception will abort the loading of the plugin with an error.
 /// </summary>
 /// <remarks>
 /// The assembly will still be loaded, but the plugin will not be constructed if this returns <see langword="false" />.
 /// Any features it defines, for example, will still be loaded.
 /// </remarks>
 /// <param name="plugin">the plugin about to be loaded</param>
 /// <returns>whether or not the plugin should be loaded</returns>
 public virtual bool BeforeLoad(PluginLoader.PluginMetadata plugin) => true;
コード例 #29
0
 /// <summary>
 /// Ensures a plugin's assembly is loaded. Do not use unless you need to.
 /// </summary>
 /// <param name="plugin">the plugin to ensure is loaded.</param>
 protected void RequireLoaded(PluginLoader.PluginMetadata plugin) => PluginLoader.Load(plugin);
コード例 #30
0
        public void Init(Sprite icon, string name, string version, string author, string description, PluginLoader.PluginMetadata updateInfo, PluginManifest.LinksObject links = null)
        {
            Icon        = icon;
            Name        = name;
            Version     = version;
            Author      = author;
            Description = description;
            UpdateInfo  = updateInfo;

            if (rowTransformOriginal == null)
            {
                rowTransformOriginal = MenuButtonUI.Instance.GetPrivateField <RectTransform>("menuButtonsOriginal");
            }

            // i also have no clue why this is necessary
            rectTransform.anchorMin = new Vector2(0f, 0f);
            rectTransform.anchorMax = new Vector2(0.5f, 1f);

            var go = new GameObject("Info View", typeof(RectTransform));

            go.SetActive(false);
            go.AddComponent <RectMask2D>();
            view = go.AddComponent <ModInfoView>();
            var rt = view.transform as RectTransform;

            rt.SetParent(transform);
            rt.anchorMin        = new Vector2(0f, 0f);
            rt.anchorMax        = new Vector2(1f, 1f);
            rt.anchoredPosition = new Vector2(0f, 0);
            view.Init(this);
            go.SetActive(true);

            if (links != null)
            {
                rowTransform                  = Instantiate(rowTransformOriginal, rectTransform);
                rowTransform.anchorMin        = new Vector2(0f, 0f);
                rowTransform.anchorMax        = new Vector2(1f, .15f);
                rowTransform.anchoredPosition = new Vector2(-3.5f, -2f);

                foreach (Transform child in rowTransform)
                {
                    child.name = string.Empty;
                    Destroy(child.gameObject);
                }

                if (links.ProjectHome != null)
                {
                    linkHomeButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "Home",
                                                                onClick: () => Process.Start(links.ProjectHome.ToString()));
                    linkHomeButton.GetComponentInChildren <HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
                }
                if (links.ProjectSource != null)
                {
                    linkSourceButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "Source",
                                                                  onClick: () => Process.Start(links.ProjectSource.ToString()));
                    linkSourceButton.GetComponentInChildren <HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
                }
                if (links.Donate != null)
                {
                    linkDonateButton = BeatSaberUI.CreateUIButton(rowTransform, "QuitButton", buttonText: "Donate",
                                                                  onClick: () => Process.Start(links.Donate.ToString()));
                    linkDonateButton.GetComponentInChildren <HorizontalLayoutGroup>().padding = new RectOffset(6, 6, 0, 0);
                }
            }
        }