diff --git a/Directory.Build.props b/Directory.Build.props
index 5dfaea9e8..9cf1e0d45 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -22,7 +22,7 @@
changes!
-->
3.0.26
- 50
+ 51
$(NitrocidModAPIVersionMajor).$(NitrocidModAPIVersionChangeset)
diff --git a/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs b/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs
index 5a3ba37c4..b5fc785ca 100644
--- a/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs
+++ b/public/Nitrocid.Addons/Nitrocid.Extras.Calendar/CalendarInit.cs
@@ -37,6 +37,7 @@
using Nitrocid.Extras.Calendar.Calendar;
using Nitrocid.Kernel.Time.Calendars;
using EventInfo = Nitrocid.Extras.Calendar.Calendar.Events.EventInfo;
+using Nitrocid.Shell.Homepage;
namespace Nitrocid.Extras.Calendar
{
@@ -247,6 +248,9 @@ void IAddon.FinalizeAddon()
EventManager.LoadEvents();
ReminderManager.LoadReminders();
DebugWriter.WriteDebug(DebugLevel.I, "Loaded events & reminders.");
+
+ // Add the calendar option to the homepage
+ HomepageTools.RegisterBuiltinAction(/* Localizable */ "Calendar", CalendarTui.OpenInteractive);
}
void IAddon.StartAddon()
@@ -262,6 +266,7 @@ void IAddon.StopAddon()
EventManager.CalendarEvents.Clear();
CommandManager.UnregisterAddonCommands(ShellType.Shell, [.. addonCommands.Select((ci) => ci.Command)]);
ConfigTools.UnregisterBaseSetting(nameof(CalendarConfig));
+ HomepageTools.UnregisterBuiltinAction("Calendar");
}
}
}
diff --git a/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs b/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs
index 0f4aee44b..7de63d69c 100644
--- a/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs
+++ b/public/Nitrocid/Kernel/Exceptions/KernelExceptionMessages.cs
@@ -149,6 +149,7 @@ internal static class KernelExceptionMessages
{ KernelExceptionType.Bootloader, Translate.DoTranslation("There was an error when trying to process a bootloader operation.") },
{ KernelExceptionType.Alarm, Translate.DoTranslation("There was an error when trying to process an alarm system operation.") },
{ KernelExceptionType.Widget, Translate.DoTranslation("There was an error when trying to process a widget system operation. If you're sure that this widget is registered properly, please make sure that you've written the widget class name properly.") },
+ { KernelExceptionType.Homepage, Translate.DoTranslation("The homepage tools has encountered an error when trying to process your request. Please make sure that you've entered all the necessary data correctly.") },
};
internal static string GetFinalExceptionMessage(KernelExceptionType exceptionType, string message, Exception? e, params object[] vars)
diff --git a/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs b/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs
index 0889e008a..4e60e7a65 100644
--- a/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs
+++ b/public/Nitrocid/Kernel/Exceptions/KernelExceptionType.cs
@@ -496,5 +496,9 @@ public enum KernelExceptionType
/// There was an error when trying to process a widget system operation. If you're sure that this widget is registered properly, please make sure that you've written the widget class name properly.
///
Widget,
+ ///
+ /// The homepage tools has encountered an error when trying to process your request. Please make sure that you've entered all the necessary data correctly.
+ ///
+ Homepage,
}
}
diff --git a/public/Nitrocid/Shell/Homepage/HomepageTools.cs b/public/Nitrocid/Shell/Homepage/HomepageTools.cs
index 161128b63..81785d336 100644
--- a/public/Nitrocid/Shell/Homepage/HomepageTools.cs
+++ b/public/Nitrocid/Shell/Homepage/HomepageTools.cs
@@ -34,6 +34,8 @@
using Nitrocid.Users.Login.Widgets;
using Nitrocid.Users.Login.Widgets.Implementations;
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Text;
using System.Threading;
using Terminaux.Base;
@@ -61,6 +63,16 @@ public static class HomepageTools
{
internal static bool isHomepageEnabled = true;
private static bool isOnHomepage = false;
+ private static Dictionary choiceActionsAddons = [];
+ private static Dictionary choiceActionsCustom = [];
+ private static Dictionary choiceActionsBuiltin = new()
+ {
+ { /* Localizable */ "File Manager", OpenFileManagerCli },
+ { /* Localizable */ "Alarm Manager", OpenAlarmCli },
+ { /* Localizable */ "Notifications", OpenNotificationsCli },
+ { /* Localizable */ "Task Manager", OpenTaskManagerCli },
+ { /* Localizable */ "About Nitrocid", OpenAboutBox },
+ };
private static readonly Keybinding[] bindings =
[
// Keyboard
@@ -85,14 +97,7 @@ public static void OpenHomepage()
var homeScreen = new Screen();
int choiceIdx = 0;
bool settingsHighlighted = false;
- InputChoiceInfo[] choices =
- [
- new("1", Translate.DoTranslation("File Manager")),
- new("2", Translate.DoTranslation("Alarm Manager")),
- new("3", Translate.DoTranslation("Notifications")),
- new("4", Translate.DoTranslation("Task Manager")),
- new("5", Translate.DoTranslation("About Nitrocid")),
- ];
+ var choices = PopulateChoices();
try
{
@@ -194,8 +199,9 @@ public static void OpenHomepage()
builder.Append(CenteredTextColor.RenderCenteredOneLine(settingsButtonPosY + 1, Translate.DoTranslation("Settings"), settingsHighlighted ? new Color(ConsoleColors.Black) : KernelColorTools.GetColor(KernelColorType.NeutralText), settingsHighlighted ? KernelColorTools.GetColor(KernelColorType.TuiPaneSelectedSeparator) : ColorTools.CurrentBackgroundColor, settingsButtonPosX + 1, settingsButtonWidth + settingsButtonPosX + 5 - ConsoleWrapper.WindowWidth % 2));
// Populate the available options
+ var availableChoices = choices.Select((tuple) => tuple.Item1).ToArray();
builder.Append(BorderColor.RenderBorder(settingsButtonPosX, clockTop, widgetWidth - 1 + ConsoleWrapper.WindowWidth % 2, widgetHeight + 2, KernelColorTools.GetColor(!settingsHighlighted ? KernelColorType.TuiPaneSelectedSeparator : KernelColorType.TuiPaneSeparator)));
- builder.Append(SelectionInputTools.RenderSelections(choices, settingsButtonPosX + 1, clockTop + 1, choiceIdx, widgetHeight + 2, widgetWidth - 1 + ConsoleWrapper.WindowWidth % 2, foregroundColor: KernelColorTools.GetColor(KernelColorType.NeutralText), selectedForegroundColor: KernelColorTools.GetColor(KernelColorType.TuiPaneSelectedSeparator)));
+ builder.Append(SelectionInputTools.RenderSelections(availableChoices, settingsButtonPosX + 1, clockTop + 1, choiceIdx, widgetHeight + 2, widgetWidth - 1 + ConsoleWrapper.WindowWidth % 2, foregroundColor: KernelColorTools.GetColor(KernelColorType.NeutralText), selectedForegroundColor: KernelColorTools.GetColor(KernelColorType.TuiPaneSelectedSeparator)));
// Return the resulting homepage
return builder.ToString();
@@ -205,67 +211,12 @@ public static void OpenHomepage()
// Helper function
void DoAction(int choiceIdx)
{
- switch (choiceIdx)
- {
- case 0:
- {
- var tui = new FileManagerCli
- {
- firstPanePath = PathsManagement.HomePath,
- secondPanePath = PathsManagement.HomePath
- };
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Open"), ConsoleKey.Enter, (entry1, _, entry2, _) => tui.Open(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy"), ConsoleKey.F1, (entry1, _, entry2, _) => tui.CopyFileOrDir(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move"), ConsoleKey.F2, (entry1, _, entry2, _) => tui.MoveFileOrDir(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Delete"), ConsoleKey.F3, (entry1, _, entry2, _) => tui.RemoveFileOrDir(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Up"), ConsoleKey.F4, (_, _, _, _) => tui.GoUp()));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Info"), ConsoleKey.F5, (entry1, _, entry2, _) => tui.PrintFileSystemEntry(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Go To"), ConsoleKey.F6, (_, _, _, _) => tui.GoTo()));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy To"), ConsoleKey.F1, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.CopyTo(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move to"), ConsoleKey.F2, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.MoveTo(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Rename"), ConsoleKey.F9, (entry1, _, entry2, _) => tui.Rename(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("New Folder"), ConsoleKey.F10, (_, _, _, _) => tui.MakeDir()));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Hash"), ConsoleKey.F11, (entry1, _, entry2, _) => tui.Hash(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Verify"), ConsoleKey.F12, (entry1, _, entry2, _) => tui.Verify(entry1, entry2)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Preview"), ConsoleKey.P, (entry1, _, entry2, _) => tui.Preview(entry1, entry2)));
- InteractiveTuiTools.OpenInteractiveTui(tui);
- }
- break;
- case 1:
- {
- var tui = new AlarmCli();
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Add"), ConsoleKey.A, (_, _, _, _) => tui.Start(), true));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Remove"), ConsoleKey.Delete, (alarm, _, _, _) => tui.Stop(alarm)));
- InteractiveTuiTools.OpenInteractiveTui(tui);
- }
- break;
- case 2:
- {
- var tui = new NotificationsCli();
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss"), ConsoleKey.Delete, (notif, _, _, _) => tui.Dismiss(notif)));
- tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss All"), ConsoleKey.Delete, ConsoleModifiers.Control, (_, _, _, _) => tui.DismissAll()));
- InteractiveTuiTools.OpenInteractiveTui(tui);
- }
- break;
- case 3:
- {
- var tui = new TaskManagerCli();
- tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Kill"), ConsoleKey.F1, (thread, _, _, _) => tui.KillThread(thread)));
- tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Switch"), ConsoleKey.F2, (_, _, _, _) => tui.SwitchMode()));
- InteractiveTuiTools.OpenInteractiveTui(tui);
- }
- break;
- case 4:
- InfoBoxButtonsColor.WriteInfoBoxButtons(
- Translate.DoTranslation("About Nitrocid"),
- [new InputChoiceInfo(Translate.DoTranslation("Close"), Translate.DoTranslation("Close"))],
- Translate.DoTranslation("Nitrocid KS simulates our future kernel, the Nitrocid Kernel.") + "\n\n" +
- Translate.DoTranslation("Version") + $": {KernelMain.VersionFullStr}" + "\n" +
- Translate.DoTranslation("Mod API") + $": {KernelMain.ApiVersion}" + "\n\n" +
- Translate.DoTranslation("Copyright (C) 2018-2024 Aptivi - All rights reserved") + " - https://aptivi.github.io"
- );
- break;
- }
+ if (choiceIdx < 0 || choiceIdx >= choices.Length)
+ return;
+
+ // Now, do the action!
+ var action = choices[choiceIdx].Item2;
+ action.Invoke();
}
// Render the thing and wait for a keypress
@@ -418,5 +369,205 @@ [new InputChoiceInfo(Translate.DoTranslation("Close"), Translate.DoTranslation("
ColorTools.LoadBack();
}
}
+
+ private static (InputChoiceInfo, Action)[] PopulateChoices()
+ {
+ // Variables
+ var choices = new List<(InputChoiceInfo, Action)>();
+
+ // First, deal with the builtin choices that are added by the core kernel
+ foreach (var choiceAction in choiceActionsBuiltin)
+ {
+ // Sanity checks
+ string key = choiceAction.Key;
+ var action = choiceAction.Value;
+ if (action is null)
+ continue;
+ if (string.IsNullOrEmpty(key))
+ continue;
+
+ // Add this action
+ var inputChoiceInfo = new InputChoiceInfo($"{choices.Count + 1}", Translate.DoTranslation(key));
+ choices.Add((inputChoiceInfo, action));
+ }
+
+ // Then, deal with the choices that are added by the addons
+ foreach (var choiceAction in choiceActionsAddons)
+ {
+ // Sanity checks
+ string key = choiceAction.Key;
+ var action = choiceAction.Value;
+ if (action is null)
+ continue;
+ if (string.IsNullOrEmpty(key))
+ continue;
+
+ // Add this action
+ var inputChoiceInfo = new InputChoiceInfo($"{choices.Count + 1}", Translate.DoTranslation(key));
+ choices.Add((inputChoiceInfo, action));
+ }
+
+ // Now, deal with the custom choices that are added by the mods
+ foreach (var choiceAction in choiceActionsCustom)
+ {
+ // Sanity checks
+ string key = choiceAction.Key;
+ var action = choiceAction.Value;
+ if (action is null)
+ continue;
+ if (string.IsNullOrEmpty(key))
+ continue;
+
+ // Add this action
+ var inputChoiceInfo = new InputChoiceInfo($"{choices.Count + 1}", Translate.DoTranslation(key));
+ choices.Add((inputChoiceInfo, action));
+ }
+
+ // Finally, return the result for the homepage to recognize them
+ return [.. choices];
+ }
+
+ ///
+ /// Checks to see if the homepage action is registered or not
+ ///
+ /// Action name to search (case sensitive)
+ /// True if it exists; false otherwise
+ public static bool IsHomepageActionRegistered(string actionName)
+ {
+ if (string.IsNullOrEmpty(actionName))
+ return false;
+ if (IsHomepageActionBuiltin(actionName))
+ return true;
+ var actions = choiceActionsAddons.Union(choiceActionsCustom).Select((kvp) => kvp.Key).ToArray();
+ return actions.Contains(actionName);
+ }
+
+ ///
+ /// Checks to see if the homepage action is bulitin or not
+ ///
+ /// Action name to search (case sensitive)
+ /// True if it exists; false otherwise
+ public static bool IsHomepageActionBuiltin(string actionName)
+ {
+ if (string.IsNullOrEmpty(actionName))
+ return false;
+ var actions = choiceActionsBuiltin.Select((kvp) => kvp.Key).ToArray();
+ return actions.Contains(actionName);
+ }
+
+ ///
+ /// Registers a custom action
+ ///
+ /// Action name (case sensitive)
+ /// Action to delegate a specific function to
+ ///
+ public static void RegisterAction(string actionName, Action? action)
+ {
+ if (string.IsNullOrEmpty(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified."));
+ if (action is null)
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action is not specified."));
+ if (IsHomepageActionRegistered(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action already exists."));
+ choiceActionsCustom.Add(actionName, action);
+ }
+
+ ///
+ /// Unregisters a custom action
+ ///
+ /// Action name to delete (case sensitive)
+ ///
+ public static void UnregisterAction(string actionName)
+ {
+ if (string.IsNullOrEmpty(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified."));
+ if (!IsHomepageActionRegistered(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action doesn't exist."));
+ if (IsHomepageActionBuiltin(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Built-in action can't be removed."));
+ choiceActionsCustom.Remove(actionName);
+ }
+
+ internal static void RegisterBuiltinAction(string actionName, Action? action)
+ {
+ if (string.IsNullOrEmpty(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified."));
+ if (action is null)
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action is not specified."));
+ if (IsHomepageActionRegistered(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action already exists."));
+ choiceActionsAddons.Add(actionName, action);
+ }
+
+ internal static void UnregisterBuiltinAction(string actionName)
+ {
+ if (string.IsNullOrEmpty(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action name is not specified."));
+ if (!IsHomepageActionRegistered(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Action doesn't exist."));
+ if (IsHomepageActionBuiltin(actionName))
+ throw new KernelException(KernelExceptionType.Homepage, Translate.DoTranslation("Built-in action can't be removed."));
+ choiceActionsAddons.Remove(actionName);
+ }
+
+ private static void OpenFileManagerCli()
+ {
+ var tui = new FileManagerCli
+ {
+ firstPanePath = PathsManagement.HomePath,
+ secondPanePath = PathsManagement.HomePath
+ };
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Open"), ConsoleKey.Enter, (entry1, _, entry2, _) => tui.Open(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy"), ConsoleKey.F1, (entry1, _, entry2, _) => tui.CopyFileOrDir(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move"), ConsoleKey.F2, (entry1, _, entry2, _) => tui.MoveFileOrDir(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Delete"), ConsoleKey.F3, (entry1, _, entry2, _) => tui.RemoveFileOrDir(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Up"), ConsoleKey.F4, (_, _, _, _) => tui.GoUp()));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Info"), ConsoleKey.F5, (entry1, _, entry2, _) => tui.PrintFileSystemEntry(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Go To"), ConsoleKey.F6, (_, _, _, _) => tui.GoTo()));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Copy To"), ConsoleKey.F1, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.CopyTo(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Move to"), ConsoleKey.F2, ConsoleModifiers.Shift, (entry1, _, entry2, _) => tui.MoveTo(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Rename"), ConsoleKey.F9, (entry1, _, entry2, _) => tui.Rename(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("New Folder"), ConsoleKey.F10, (_, _, _, _) => tui.MakeDir()));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Hash"), ConsoleKey.F11, (entry1, _, entry2, _) => tui.Hash(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Verify"), ConsoleKey.F12, (entry1, _, entry2, _) => tui.Verify(entry1, entry2)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Preview"), ConsoleKey.P, (entry1, _, entry2, _) => tui.Preview(entry1, entry2)));
+ InteractiveTuiTools.OpenInteractiveTui(tui);
+ }
+
+ private static void OpenAlarmCli()
+ {
+ var tui = new AlarmCli();
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Add"), ConsoleKey.A, (_, _, _, _) => tui.Start(), true));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Remove"), ConsoleKey.Delete, (alarm, _, _, _) => tui.Stop(alarm)));
+ InteractiveTuiTools.OpenInteractiveTui(tui);
+ }
+
+ private static void OpenNotificationsCli()
+ {
+ var tui = new NotificationsCli();
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss"), ConsoleKey.Delete, (notif, _, _, _) => tui.Dismiss(notif)));
+ tui.Bindings.Add(new InteractiveTuiBinding(Translate.DoTranslation("Dismiss All"), ConsoleKey.Delete, ConsoleModifiers.Control, (_, _, _, _) => tui.DismissAll()));
+ InteractiveTuiTools.OpenInteractiveTui(tui);
+ }
+
+ private static void OpenTaskManagerCli()
+ {
+ var tui = new TaskManagerCli();
+ tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Kill"), ConsoleKey.F1, (thread, _, _, _) => tui.KillThread(thread)));
+ tui.Bindings.Add(new InteractiveTuiBinding<(int, object)>(Translate.DoTranslation("Switch"), ConsoleKey.F2, (_, _, _, _) => tui.SwitchMode()));
+ InteractiveTuiTools.OpenInteractiveTui(tui);
+ }
+
+ private static void OpenAboutBox()
+ {
+ InfoBoxButtonsColor.WriteInfoBoxButtons(
+ Translate.DoTranslation("About Nitrocid"),
+ [new InputChoiceInfo(Translate.DoTranslation("Close"), Translate.DoTranslation("Close"))],
+ Translate.DoTranslation("Nitrocid KS simulates our future kernel, the Nitrocid Kernel.") + "\n\n" +
+ Translate.DoTranslation("Version") + $": {KernelMain.VersionFullStr}" + "\n" +
+ Translate.DoTranslation("Mod API") + $": {KernelMain.ApiVersion}" + "\n\n" +
+ Translate.DoTranslation("Copyright (C) 2018-2024 Aptivi - All rights reserved") + " - https://aptivi.github.io"
+ );
+ }
}
}