diff --git a/LuckPerms.Loader/LuckPerms.Loader.csproj b/LuckPerms.Loader/LuckPerms.Loader.csproj
new file mode 100644
index 0000000..ce9e370
--- /dev/null
+++ b/LuckPerms.Loader/LuckPerms.Loader.csproj
@@ -0,0 +1,63 @@
+
+
+
+ net48
+ 12
+ enable
+ enable
+ true
+ true
+ true
+
+
+
+ false
+ none
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\LuckPerms.Torch\bin\$(Configuration)\$(TargetFramework)\win-x64\
+ $(BaseIntermediateOutputPath)$(Configuration)\$(TargetFramework)\plugin.zip
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LuckPerms.Loader/Plugin.cs b/LuckPerms.Loader/Plugin.cs
new file mode 100644
index 0000000..051062e
--- /dev/null
+++ b/LuckPerms.Loader/Plugin.cs
@@ -0,0 +1,72 @@
+using System.IO.Compression;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using NLog;
+using Torch;
+using Torch.API;
+using Torch.API.Managers;
+using Torch.API.Plugins;
+using Torch.Managers;
+
+namespace LuckPerms.Loader;
+
+public class Plugin : TorchPluginBase
+{
+ private static readonly ITorchPlugin MainPluginInstance;
+ private static readonly ILogger Log = LogManager.GetLogger("LuckPerms.Loader");
+
+ static Plugin()
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ var torch = (ITorchServer)TorchBase.Instance;
+#pragma warning restore CS0618 // Type or member is obsolete
+ var dir = new DirectoryInfo(Path.Combine(torch.InstancePath, "cache", "luckperms.loader"));
+
+ if (dir.Exists)
+ dir.Delete(true);
+
+ Log.Info($"Extracting cache to {dir}");
+
+ using (var pluginStream = typeof(Plugin).Assembly.GetManifestResourceStream("plugin.zip")!)
+ using (var archive = new ZipArchive(pluginStream, ZipArchiveMode.Read))
+ archive.ExtractToDirectory(dir.FullName);
+
+ Log.Info("Injecting LuckPerms");
+
+ AppDomain.CurrentDomain.AssemblyResolve += (_, args) =>
+ {
+ var fileName = args.Name[..args.Name.IndexOf(',')];
+ var path = Path.Combine(dir.FullName, fileName + ".dll");
+
+ return File.Exists(path) ? Assembly.LoadFile(path) : null;
+ };
+
+ var mainAssembly = Assembly.LoadFile(Path.Combine(dir.FullName, "LuckPerms.Torch.dll"));
+
+ var pluginType = mainAssembly.GetType("LuckPerms.Torch.Plugin", true)!;
+
+ // a hacky way to configure JVM
+ RuntimeHelpers.RunClassConstructor(pluginType.TypeHandle);
+
+ TorchBase.RegisterAuxAssembly(mainAssembly);
+
+ MainPluginInstance = (ITorchPlugin)Activator.CreateInstance(pluginType)!;
+
+ if (MainPluginInstance is not TorchPluginBase pluginBase) return;
+
+ pluginBase.Manifest = PluginManifest.Load(Path.Combine(dir.FullName, "manifest.xml"));
+ pluginBase.StoragePath = torch.InstancePath;
+ }
+
+ public override void Init(ITorchBase torch)
+ {
+ var pluginManager = torch.Managers.GetManager();
+
+ pluginManager._plugins.Remove(Manifest.Guid);
+ pluginManager._plugins.Add(Manifest.Guid, MainPluginInstance);
+
+ MainPluginInstance.Init(torch);
+
+ Log.Info("Injected successfully");
+ }
+}
\ No newline at end of file
diff --git a/LuckPerms.Loader/manifest.xml b/LuckPerms.Loader/manifest.xml
new file mode 100644
index 0000000..dc273fa
--- /dev/null
+++ b/LuckPerms.Loader/manifest.xml
@@ -0,0 +1,6 @@
+
+
+ LuckPerms.Loader
+ 7E4B3CC8-64FA-416E-8910-AACDF2DA5E2C
+ v5.4.106
+
\ No newline at end of file
diff --git a/LuckPerms.Loader/packages.lock.json b/LuckPerms.Loader/packages.lock.json
new file mode 100644
index 0000000..467e46a
--- /dev/null
+++ b/LuckPerms.Loader/packages.lock.json
@@ -0,0 +1,67 @@
+{
+ "version": 1,
+ "dependencies": {
+ ".NETFramework,Version=v4.8": {
+ "Krafs.Publicizer": {
+ "type": "Direct",
+ "requested": "[2.2.1, )",
+ "resolved": "2.2.1",
+ "contentHash": "QGI4nMGQbKsuFUUboixVHu4mv3lHB5RejIa7toIlzTmwLkuCYYEpUBJjmy3OpXYyj5dVSZAXVbr4oeMSloE67Q=="
+ },
+ "Microsoft.NETFramework.ReferenceAssemblies": {
+ "type": "Direct",
+ "requested": "[1.0.3, )",
+ "resolved": "1.0.3",
+ "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
+ "dependencies": {
+ "Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
+ }
+ },
+ "PolySharp": {
+ "type": "Direct",
+ "requested": "[1.13.2, )",
+ "resolved": "1.13.2",
+ "contentHash": "XwNhfkr7IeUiH8AE4pzob8YioxfL6nxgAx+fHEeWCObY/NZuBMfWLh39FznXbneKvagiqeeI7quIvZ6P1eVaEA=="
+ },
+ "Torch.Server.ReferenceAssemblies": {
+ "type": "Direct",
+ "requested": "[1.3.1.260-master, )",
+ "resolved": "1.3.1.260-master",
+ "contentHash": "p9fHBwPI2BZDLO2PiSPvJxHQ7lksYf/20BZ0uUxMlSnJk/AvFUpjT6CMxJWow4UuAFG+NcPEI4VhxZ5x9jhGGA==",
+ "dependencies": {
+ "NLog": "4.4.12",
+ "Newtonsoft.Json": "12.0.2",
+ "SpaceEngineersDedicated.ReferenceAssemblies": "1.203.505"
+ }
+ },
+ "Microsoft.NETFramework.ReferenceAssemblies.net48": {
+ "type": "Transitive",
+ "resolved": "1.0.3",
+ "contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ=="
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "12.0.2",
+ "contentHash": "rTK0s2EKlfHsQsH6Yx2smvcTCeyoDNgCW7FEYyV01drPlh2T243PR2DiDXqtC5N4GDm4Ma/lkxfW5a/4793vbA=="
+ },
+ "NLog": {
+ "type": "Transitive",
+ "resolved": "4.4.12",
+ "contentHash": "fODew3BFT2XhAuqhGo2ZYT9OJE0ciGEBfvKXOmAAvaDsP/3ROIf083p8QUnmwKKw4jCkVW06ObX6gn/eFi2Skg=="
+ },
+ "protobuf-net": {
+ "type": "Transitive",
+ "resolved": "1.0.0",
+ "contentHash": "kTGOK0E87473sOImOjgZOnz3kTC2aMLffoRWQLYNuBLJnwNNmjanF9IkevZ9Q7yYLeABQfcF3BpeepuMntMVNw=="
+ },
+ "SpaceEngineersDedicated.ReferenceAssemblies": {
+ "type": "Transitive",
+ "resolved": "1.203.505",
+ "contentHash": "Wq4GIn2ilHyFdLdVKdVhC7iGQ+1FdVsChKY6hyQluFYSSV7oe8bDc9aTZC8XgxNMKCZoQBSVyaYHxQD+74BySQ==",
+ "dependencies": {
+ "protobuf-net": "1.0.0"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/LuckPerms.Torch/Extensions/CommandExtensions.cs b/LuckPerms.Torch/Extensions/CommandExtensions.cs
new file mode 100644
index 0000000..d4644ef
--- /dev/null
+++ b/LuckPerms.Torch/Extensions/CommandExtensions.cs
@@ -0,0 +1,12 @@
+using System.Linq;
+using Torch.Commands;
+
+namespace LuckPerms.Torch.Extensions;
+
+public static class CommandExtensions
+{
+ public static string GetPermissionString(this Command command)
+ {
+ return $"minecraft.command.{string.Join(".", command.Path.Select(b => b.ToLowerInvariant()))}";
+ }
+}
diff --git a/LuckPerms.Torch/Extensions/DelegateExtensions.cs b/LuckPerms.Torch/Extensions/DelegateExtensions.cs
new file mode 100644
index 0000000..3142a38
--- /dev/null
+++ b/LuckPerms.Torch/Extensions/DelegateExtensions.cs
@@ -0,0 +1,19 @@
+using System;
+using java.lang;
+using java.util.concurrent;
+
+namespace LuckPerms.Torch.Extensions;
+
+public static class DelegateExtensions
+{
+ public static Runnable ToRunnable(this Action action) => new DelegateRunnable(action);
+
+ // ReSharper disable once InconsistentNaming
+ // lets make it an overload for convenience
+ public static void execute(this Executor executor, Action action) => executor.execute(action.ToRunnable());
+
+ private sealed class DelegateRunnable(Action action) : Runnable
+ {
+ public void run() => action();
+ }
+}
\ No newline at end of file
diff --git a/LuckPerms.Torch/Extensions/EnumerableExtensions.cs b/LuckPerms.Torch/Extensions/EnumerableExtensions.cs
new file mode 100644
index 0000000..40857b4
--- /dev/null
+++ b/LuckPerms.Torch/Extensions/EnumerableExtensions.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.Linq;
+using java.util;
+
+namespace LuckPerms.Torch.Extensions;
+
+public static class EnumerableExtensions
+{
+ public static Collection ToCollection(this IEnumerable enumerable)
+ {
+ var collection = enumerable is IReadOnlyCollection readOnlyCollection ? new ArrayList(readOnlyCollection.Count) : new ArrayList(((IReadOnlyCollection)(enumerable = enumerable.ToArray())).Count);
+
+ foreach (var t in enumerable)
+ {
+ collection.add(t);
+ }
+
+ return collection;
+ }
+}
\ No newline at end of file
diff --git a/LuckPerms.Torch/Extensions/IteratorExtensions.cs b/LuckPerms.Torch/Extensions/IteratorExtensions.cs
new file mode 100644
index 0000000..6eac4cd
--- /dev/null
+++ b/LuckPerms.Torch/Extensions/IteratorExtensions.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using java.util;
+
+namespace LuckPerms.Torch.Extensions;
+
+public static class IteratorExtensions
+{
+ public static IteratorEnumerator