From 80f5449ae83aa8476de2e7a1cf0df6f2e295e998 Mon Sep 17 00:00:00 2001 From: zznty <94796179+zznty@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:56:59 +0700 Subject: [PATCH] meh --- Kits/Kits.csproj | 2 +- LightPerms.Discord/LightPerms.Discord.csproj | 2 +- .../LightPerms.TorchCommands.csproj | 2 +- LightPerms/LightPerms.csproj | 2 +- .../LuckPerms.Torch.Api.nuspec | 1 + .../Extensions/DelegateExtensions.cs | 78 ++++ .../Extensions/EnumerableExtensions.cs | 6 +- .../Extensions/IteratorExtensions.cs | 26 +- .../Extensions/StreamExtensions.cs | 92 ++++ .../Extensions/UUIDExtensions.cs | 2 +- .../LuckPerms.Torch.Utils.csproj | 27 ++ .../Extensions/DelegateExtensions.cs | 19 - .../MultiplayerManagerExtensions.cs | 1 + .../Extensions/StreamExtensions.cs | 19 - .../Impl/Listeners/LpConnectionListener.cs | 1 + LuckPerms.Torch/Impl/LpContextManager.cs | 1 + LuckPerms.Torch/Impl/LpSenderFactory.cs | 1 + LuckPerms.Torch/Impl/LpTorchBootstrap.cs | 16 +- LuckPerms.Torch/Impl/LpTorchPlugin.cs | 1 + LuckPerms.Torch/LuckPerms.Torch.csproj | 4 + LuckPerms.Torch/ModApi/ModApiManager.cs | 1 + LuckPerms.Torch/manifest.xml | 2 +- LuckPerms.Torch/packages.lock.json | 8 +- Maintenance/Commands.cs | 144 ++++++ Maintenance/ConfigKeys.cs | 20 + .../Extensions/CompletableFutureExtensions.cs | 31 ++ Maintenance/LangKeys.cs | 18 + Maintenance/Maintenance.csproj | 53 +++ Maintenance/Managers/ConfigManager.cs | 38 ++ Maintenance/Managers/LanguageManager.cs | 62 +++ Maintenance/Managers/MaintenanceManager.cs | 179 +++++++ .../Managers/MaintenanceScheduleManager.cs | 181 ++++++++ Maintenance/Patches/SteamQueryPatch.cs | 361 +++++++++++++++ Maintenance/Plugin.cs | 21 + Maintenance/Resources/config.yml | 32 ++ Maintenance/Resources/translations/en.yml | 13 + Maintenance/manifest.xml | 6 + Maintenance/packages.lock.json | 438 ++++++++++++++++++ TorchPlugins.sln | 14 + heh/heh.csproj | 2 +- 40 files changed, 1863 insertions(+), 64 deletions(-) create mode 100644 LuckPerms.Torch.Utils/Extensions/DelegateExtensions.cs rename {LuckPerms.Torch => LuckPerms.Torch.Utils}/Extensions/EnumerableExtensions.cs (81%) rename {LuckPerms.Torch => LuckPerms.Torch.Utils}/Extensions/IteratorExtensions.cs (55%) create mode 100644 LuckPerms.Torch.Utils/Extensions/StreamExtensions.cs rename {LuckPerms.Torch => LuckPerms.Torch.Utils}/Extensions/UUIDExtensions.cs (84%) create mode 100644 LuckPerms.Torch.Utils/LuckPerms.Torch.Utils.csproj delete mode 100644 LuckPerms.Torch/Extensions/DelegateExtensions.cs delete mode 100644 LuckPerms.Torch/Extensions/StreamExtensions.cs create mode 100644 Maintenance/Commands.cs create mode 100644 Maintenance/ConfigKeys.cs create mode 100644 Maintenance/Extensions/CompletableFutureExtensions.cs create mode 100644 Maintenance/LangKeys.cs create mode 100644 Maintenance/Maintenance.csproj create mode 100644 Maintenance/Managers/ConfigManager.cs create mode 100644 Maintenance/Managers/LanguageManager.cs create mode 100644 Maintenance/Managers/MaintenanceManager.cs create mode 100644 Maintenance/Managers/MaintenanceScheduleManager.cs create mode 100644 Maintenance/Patches/SteamQueryPatch.cs create mode 100644 Maintenance/Plugin.cs create mode 100644 Maintenance/Resources/config.yml create mode 100644 Maintenance/Resources/translations/en.yml create mode 100644 Maintenance/manifest.xml create mode 100644 Maintenance/packages.lock.json diff --git a/Kits/Kits.csproj b/Kits/Kits.csproj index 0b04b09..04471e8 100644 --- a/Kits/Kits.csproj +++ b/Kits/Kits.csproj @@ -17,7 +17,7 @@ - + diff --git a/LightPerms.Discord/LightPerms.Discord.csproj b/LightPerms.Discord/LightPerms.Discord.csproj index f115e55..8fc9992 100644 --- a/LightPerms.Discord/LightPerms.Discord.csproj +++ b/LightPerms.Discord/LightPerms.Discord.csproj @@ -18,7 +18,7 @@ - + diff --git a/LightPerms.TorchCommands/LightPerms.TorchCommands.csproj b/LightPerms.TorchCommands/LightPerms.TorchCommands.csproj index a7f9278..e565feb 100644 --- a/LightPerms.TorchCommands/LightPerms.TorchCommands.csproj +++ b/LightPerms.TorchCommands/LightPerms.TorchCommands.csproj @@ -15,7 +15,7 @@ - + diff --git a/LightPerms/LightPerms.csproj b/LightPerms/LightPerms.csproj index 29a5170..9c1aadc 100644 --- a/LightPerms/LightPerms.csproj +++ b/LightPerms/LightPerms.csproj @@ -14,7 +14,7 @@ - + diff --git a/LuckPerms.Torch.Api/LuckPerms.Torch.Api.nuspec b/LuckPerms.Torch.Api/LuckPerms.Torch.Api.nuspec index dad4120..cd9533c 100644 --- a/LuckPerms.Torch.Api/LuckPerms.Torch.Api.nuspec +++ b/LuckPerms.Torch.Api/LuckPerms.Torch.Api.nuspec @@ -8,6 +8,7 @@ + diff --git a/LuckPerms.Torch.Utils/Extensions/DelegateExtensions.cs b/LuckPerms.Torch.Utils/Extensions/DelegateExtensions.cs new file mode 100644 index 0000000..9470c51 --- /dev/null +++ b/LuckPerms.Torch.Utils/Extensions/DelegateExtensions.cs @@ -0,0 +1,78 @@ +using System.Reflection; +using java.lang; +using java.util.concurrent; +using java.util.function; +using net.luckperms.api.@event; + +namespace LuckPerms.Torch.Utils.Extensions; + +public static class DelegateExtensions +{ + private static readonly Func AndThenDefault = + (Func)typeof(Consumer).GetMethod("andThen", + BindingFlags.Static | BindingFlags.NonPublic)!.CreateDelegate(typeof(Func)); + + private static readonly Func AndDefault = + (Func)typeof(Predicate).GetMethod("and", + BindingFlags.Static | BindingFlags.NonPublic)!.CreateDelegate(typeof(Func)); + + private static readonly Func NegateDefault = + (Func)typeof(Predicate).GetMethod("negate", + BindingFlags.Static | BindingFlags.NonPublic)!.CreateDelegate(typeof(Func)); + + private static readonly Func OrDefault = + (Func)typeof(Predicate).GetMethod("or", + BindingFlags.Static | BindingFlags.NonPublic)!.CreateDelegate(typeof(Func)); + + public static Runnable ToRunnable(this Action action) => new DelegateRunnable(action); + + public static Consumer ToConsumer(this Action action) => new DelegateConsumer(action); + + public static Supplier ToSupplier(this Func func) where T : notnull => new DelegateSupplier(func); + + public static Predicate ToPredicate(this Predicate predicate) => new DelegatePredicate(predicate); + + public static Callable ToCallable(this Func func) => new DelegateSupplier(func); + + private sealed class DelegatePredicate(Predicate predicate) : Predicate + { + public bool test(object t) => predicate((T)t); + + public Predicate and(Predicate other) => AndDefault(this, other); + + public Predicate negate() => NegateDefault(this); + + public Predicate or(Predicate other) => OrDefault(this, other); + } + + private sealed class DelegateSupplier(Func func) : Supplier, Callable where T : notnull + { + public object get() => func(); + public object call() => func(); + } + + private sealed class DelegateConsumer(Action action) : Consumer + { + public void accept(object t) + { + action((T)t); + } + + public Consumer andThen(Consumer after) => AndThenDefault(this, after); + } + + // ReSharper disable once InconsistentNaming + // lets make it an overload for convenience + public static void execute(this Executor executor, Action action) => executor.execute(action.ToRunnable()); + + public static EventSubscription subscribe(this EventBus bus, object plugin, Action action) + where TEvent : class, LuckPermsEvent => bus.subscribe(plugin, typeof(TEvent), action.ToConsumer()); + + public static EventSubscription subscribe(this EventBus bus, Action action) + where TEvent : class, LuckPermsEvent => bus.subscribe(typeof(TEvent), action.ToConsumer()); + + 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.Utils/Extensions/EnumerableExtensions.cs similarity index 81% rename from LuckPerms.Torch/Extensions/EnumerableExtensions.cs rename to LuckPerms.Torch.Utils/Extensions/EnumerableExtensions.cs index 40857b4..eb4496e 100644 --- a/LuckPerms.Torch/Extensions/EnumerableExtensions.cs +++ b/LuckPerms.Torch.Utils/Extensions/EnumerableExtensions.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using System.Linq; -using java.util; +using java.util; -namespace LuckPerms.Torch.Extensions; +namespace LuckPerms.Torch.Utils.Extensions; public static class EnumerableExtensions { diff --git a/LuckPerms.Torch/Extensions/IteratorExtensions.cs b/LuckPerms.Torch.Utils/Extensions/IteratorExtensions.cs similarity index 55% rename from LuckPerms.Torch/Extensions/IteratorExtensions.cs rename to LuckPerms.Torch.Utils/Extensions/IteratorExtensions.cs index 115a488..3ada80a 100644 --- a/LuckPerms.Torch/Extensions/IteratorExtensions.cs +++ b/LuckPerms.Torch.Utils/Extensions/IteratorExtensions.cs @@ -1,12 +1,23 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; using java.util; +using java.util.stream; -namespace LuckPerms.Torch.Extensions; +namespace LuckPerms.Torch.Utils.Extensions; public static class IteratorExtensions { + public static StreamEnumerable AsEnumerable(this BaseStream stream) => new(stream); + + public static IteratorEnumerator GetEnumerator(this BaseStream stream) => new(stream.iterator()); + + public struct StreamEnumerable(BaseStream stream) : IEnumerable + { + public IteratorEnumerator GetEnumerator() => new(stream.iterator()); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + public static IteratorEnumerator GetEnumerator(this Iterator iterator) => new(iterator); public static IteratorEnumerable AsEnumerable(this Iterator iterator) => new(iterator); @@ -39,11 +50,8 @@ public static class IteratorExtensions { public IteratorEnumerator GetEnumerator() => new(iterator); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => null!; + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } \ No newline at end of file diff --git a/LuckPerms.Torch.Utils/Extensions/StreamExtensions.cs b/LuckPerms.Torch.Utils/Extensions/StreamExtensions.cs new file mode 100644 index 0000000..8a30168 --- /dev/null +++ b/LuckPerms.Torch.Utils/Extensions/StreamExtensions.cs @@ -0,0 +1,92 @@ +using java.io; +using JavaIOException = java.io.IOException; +using SystemIOException = System.IO.IOException; + +namespace LuckPerms.Torch.Utils.Extensions; + +public static class StreamExtensions +{ + public static InputStream GetInputStream(this Stream stream) + { + if (!stream.CanRead) + throw new ArgumentException("Stream should be readable", nameof(stream)); + + return new WrapperInputStream(stream); + } + + private sealed class WrapperInputStream(Stream stream) : InputStream + { + public override int read() + { + try + { + return stream.ReadByte(); + } + catch (SystemIOException e) + { + throw new JavaIOException(e.Message, e); + } + } + + public override int read(byte[] b, int off, int len) + { + try + { + return stream.Read(b, off, len); + } + catch (SystemIOException e) + { + throw new JavaIOException(e.Message, e); + } + } + + public override long skip(long n) + { + if (!stream.CanSeek) return base.skip(n); + + try + { + return stream.Seek(n, SeekOrigin.Current); + } + catch (SystemIOException e) + { + throw new JavaIOException(e.Message, e); + } + } + + public override int available() + { + try + { + return (int)(stream.Length - stream.Position); + } + catch (SystemIOException e) + { + throw new JavaIOException(e.Message, e); + } + } + + public override void reset() + { + if (!stream.CanSeek) + { + base.reset(); + return; + } + + try + { + stream.Seek(0, SeekOrigin.Begin); + } + catch (SystemIOException e) + { + throw new JavaIOException(e.Message, e); + } + } + + public override void close() + { + stream.Dispose(); + } + } +} \ No newline at end of file diff --git a/LuckPerms.Torch/Extensions/UUIDExtensions.cs b/LuckPerms.Torch.Utils/Extensions/UUIDExtensions.cs similarity index 84% rename from LuckPerms.Torch/Extensions/UUIDExtensions.cs rename to LuckPerms.Torch.Utils/Extensions/UUIDExtensions.cs index a92e8d5..9b198d4 100644 --- a/LuckPerms.Torch/Extensions/UUIDExtensions.cs +++ b/LuckPerms.Torch.Utils/Extensions/UUIDExtensions.cs @@ -1,6 +1,6 @@ using java.util; -namespace LuckPerms.Torch.Extensions; +namespace LuckPerms.Torch.Utils.Extensions; public static class UuidExtensions { diff --git a/LuckPerms.Torch.Utils/LuckPerms.Torch.Utils.csproj b/LuckPerms.Torch.Utils/LuckPerms.Torch.Utils.csproj new file mode 100644 index 0000000..2fc61a6 --- /dev/null +++ b/LuckPerms.Torch.Utils/LuckPerms.Torch.Utils.csproj @@ -0,0 +1,27 @@ + + + + net48 + 12 + enable + enable + 1.0.0 + + + + $(ProjectDir)libs\ + $(LibsPath)api-5.4.jar + + + + + api + true + + + + + + + + diff --git a/LuckPerms.Torch/Extensions/DelegateExtensions.cs b/LuckPerms.Torch/Extensions/DelegateExtensions.cs deleted file mode 100644 index 3142a38..0000000 --- a/LuckPerms.Torch/Extensions/DelegateExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -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/MultiplayerManagerExtensions.cs b/LuckPerms.Torch/Extensions/MultiplayerManagerExtensions.cs index ca1d50b..780eb4a 100644 --- a/LuckPerms.Torch/Extensions/MultiplayerManagerExtensions.cs +++ b/LuckPerms.Torch/Extensions/MultiplayerManagerExtensions.cs @@ -1,4 +1,5 @@ using java.util; +using LuckPerms.Torch.Utils.Extensions; using Torch.API; using Torch.API.Managers; using Torch.Server.Managers; diff --git a/LuckPerms.Torch/Extensions/StreamExtensions.cs b/LuckPerms.Torch/Extensions/StreamExtensions.cs deleted file mode 100644 index 67a3895..0000000 --- a/LuckPerms.Torch/Extensions/StreamExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.IO; -using java.io; -using Torch.Utils; - -namespace LuckPerms.Torch.Extensions; - -internal static class StreamExtensions -{ - public static InputStream GetInputStream(this Stream stream) - { - if (!stream.CanRead) - throw new ArgumentException("Stream should be readable", nameof(stream)); - - var array = stream.ReadToEnd(); // TODO make it not allocate array for an entire stream content - - return new ByteArrayInputStream(array); - } -} \ No newline at end of file diff --git a/LuckPerms.Torch/Impl/Listeners/LpConnectionListener.cs b/LuckPerms.Torch/Impl/Listeners/LpConnectionListener.cs index f5e3311..832e40f 100644 --- a/LuckPerms.Torch/Impl/Listeners/LpConnectionListener.cs +++ b/LuckPerms.Torch/Impl/Listeners/LpConnectionListener.cs @@ -1,5 +1,6 @@ using LuckPerms.Torch.Extensions; using LuckPerms.Torch.PlatformApi; +using LuckPerms.Torch.Utils.Extensions; using me.lucko.luckperms.common.plugin; using me.lucko.luckperms.common.plugin.util; using Torch.API; diff --git a/LuckPerms.Torch/Impl/LpContextManager.cs b/LuckPerms.Torch/Impl/LpContextManager.cs index f10da3b..2625f7e 100644 --- a/LuckPerms.Torch/Impl/LpContextManager.cs +++ b/LuckPerms.Torch/Impl/LpContextManager.cs @@ -2,6 +2,7 @@ using java.util; using LuckPerms.Torch.Extensions; using LuckPerms.Torch.PlatformApi; +using LuckPerms.Torch.Utils.Extensions; using me.lucko.luckperms.common.config; using me.lucko.luckperms.common.context.manager; using me.lucko.luckperms.common.plugin; diff --git a/LuckPerms.Torch/Impl/LpSenderFactory.cs b/LuckPerms.Torch/Impl/LpSenderFactory.cs index fc7ac26..1d8a65a 100644 --- a/LuckPerms.Torch/Impl/LpSenderFactory.cs +++ b/LuckPerms.Torch/Impl/LpSenderFactory.cs @@ -1,5 +1,6 @@ using java.util; using LuckPerms.Torch.Extensions; +using LuckPerms.Torch.Utils.Extensions; using me.lucko.luckperms.common.locale; using me.lucko.luckperms.common.plugin; using me.lucko.luckperms.common.sender; diff --git a/LuckPerms.Torch/Impl/LpTorchBootstrap.cs b/LuckPerms.Torch/Impl/LpTorchBootstrap.cs index e6f5fdd..b0de7fb 100644 --- a/LuckPerms.Torch/Impl/LpTorchBootstrap.cs +++ b/LuckPerms.Torch/Impl/LpTorchBootstrap.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using java.io; using java.lang; @@ -7,6 +8,7 @@ using java.time; using java.util; using java.util.concurrent; using LuckPerms.Torch.Extensions; +using LuckPerms.Torch.Utils.Extensions; using me.lucko.luckperms.common.plugin.bootstrap; using me.lucko.luckperms.common.plugin.classpath; using me.lucko.luckperms.common.plugin.logging; @@ -18,6 +20,7 @@ using Torch.API; using Torch.API.Managers; using Torch.API.Plugins; using Torch.Server.Managers; +using Path = java.nio.file.Path; namespace LuckPerms.Torch.Impl; @@ -30,8 +33,9 @@ public class LpTorchBootstrap : LuckPermsBootstrap public CountDownLatch EnableLatch { get; } = new(1); private readonly ITorchServer _torch; private readonly ITorchPlugin _torchPlugin; - private readonly string _configDir; - + private readonly Path _configDir; + private readonly Path _dataDir; + public LpTorchPlugin Plugin { get; } public LpTorchBootstrap(ITorchServer torch, ITorchPlugin torchPlugin, ILogger logger, string configDir) @@ -39,13 +43,15 @@ public class LpTorchBootstrap : LuckPermsBootstrap _pluginLogger = new NLogPluginLogger(logger); _torch = torch; _torchPlugin = torchPlugin; - _configDir = configDir; + var configDirInfo = Directory.CreateDirectory(configDir); + _configDir = Paths.get(configDirInfo.FullName); + _dataDir = Paths.get(configDirInfo.CreateSubdirectory("data").FullName); Plugin = new(this, torch); } public ITorchPlugin GetTorchPlugin() => _torchPlugin; - public Path getDataDirectory() => Paths.get(_configDir, "data"); + public Path getDataDirectory() => _dataDir; public string identifyClassLoader(ClassLoader classLoader) => classLoader.toString(); @@ -56,7 +62,7 @@ public class LpTorchBootstrap : LuckPermsBootstrap public SchedulerAdapter getScheduler() => _schedulerAdapter ??= new LpSchedulerAdapter(this, _torch); - public Path getConfigDirectory() => Paths.get(_configDir); + public Path getConfigDirectory() => _configDir; public bool isPlayerOnline(UUID uuid) => _torch.CurrentSession?.Managers.GetManager().Players diff --git a/LuckPerms.Torch/Impl/LpTorchPlugin.cs b/LuckPerms.Torch/Impl/LpTorchPlugin.cs index b621f59..e423352 100644 --- a/LuckPerms.Torch/Impl/LpTorchPlugin.cs +++ b/LuckPerms.Torch/Impl/LpTorchPlugin.cs @@ -8,6 +8,7 @@ using LuckPerms.Torch.Impl.Listeners; using LuckPerms.Torch.Impl.Messaging; using LuckPerms.Torch.ModApi; using LuckPerms.Torch.PlatformHooks; +using LuckPerms.Torch.Utils.Extensions; using me.lucko.luckperms.common.api; using me.lucko.luckperms.common.calculator; using me.lucko.luckperms.common.command; diff --git a/LuckPerms.Torch/LuckPerms.Torch.csproj b/LuckPerms.Torch/LuckPerms.Torch.csproj index d99f040..5626d9f 100644 --- a/LuckPerms.Torch/LuckPerms.Torch.csproj +++ b/LuckPerms.Torch/LuckPerms.Torch.csproj @@ -259,4 +259,8 @@ + + + + \ No newline at end of file diff --git a/LuckPerms.Torch/ModApi/ModApiManager.cs b/LuckPerms.Torch/ModApi/ModApiManager.cs index 213fa89..de7b074 100644 --- a/LuckPerms.Torch/ModApi/ModApiManager.cs +++ b/LuckPerms.Torch/ModApi/ModApiManager.cs @@ -5,6 +5,7 @@ using ikvm.extensions; using ikvm.runtime; using java.lang; using LuckPerms.Torch.Extensions; +using LuckPerms.Torch.Utils.Extensions; using Torch.API; using Torch.API.Managers; using VRage.Scripting; diff --git a/LuckPerms.Torch/manifest.xml b/LuckPerms.Torch/manifest.xml index 2a54795..6c24c11 100644 --- a/LuckPerms.Torch/manifest.xml +++ b/LuckPerms.Torch/manifest.xml @@ -2,5 +2,5 @@ LuckPerms.Torch 7E4B3CC8-64FA-416E-8910-AACDF2DA5E2C - v5.4.106.4 + v5.4.106.5 \ No newline at end of file diff --git a/LuckPerms.Torch/packages.lock.json b/LuckPerms.Torch/packages.lock.json index 43d9fbf..ccfa903 100644 --- a/LuckPerms.Torch/packages.lock.json +++ b/LuckPerms.Torch/packages.lock.json @@ -49,7 +49,7 @@ "type": "Direct", "requested": "[1.0.0, )", "resolved": "1.0.0", - "contentHash": "GAf9Mv1t1/qTGHSgDqkiKAc7Xbh36+U8Ce1PuSoJZNKxHVmzbKHc3nSVz0dIBHhLE7Op8k60NfmclDRAQAppbQ==" + "contentHash": "HkuAujKa9IqPPqoA1205teUPBxuNOC9z0xZJkrMlFT0htH02X0ieZ5qh4onwyV10qVKiRBCSLkc5tA8lp1l5ig==" }, "Torch.Server.ReferenceAssemblies": { "type": "Direct", @@ -357,6 +357,12 @@ "type": "Transitive", "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "luckperms.torch.utils": { + "type": "Project", + "dependencies": { + "IKVM": "[8.7.1, )" + } } }, ".NETFramework,Version=v4.8/win-x64": { diff --git a/Maintenance/Commands.cs b/Maintenance/Commands.cs new file mode 100644 index 0000000..c922461 --- /dev/null +++ b/Maintenance/Commands.cs @@ -0,0 +1,144 @@ +using Maintenance.Managers; +using Torch.API.Managers; +using Torch.Commands; + +namespace Maintenance; + +[Category("maintenance")] +public class Commands : CommandModule +{ + private static readonly TimeSpan MaxTime = TimeSpan.FromDays(28); + + private MaintenanceManager MaintenanceManager => + Context.Torch.CurrentSession.Managers.GetManager(); + + private MaintenanceScheduleManager MaintenanceScheduler => + Context.Torch.CurrentSession.Managers.GetManager(); + + private LanguageManager LanguageManager => + Context.Torch.Managers.GetManager(); + + [Command("on", "Set the status of the maintenance mode to enabled.")] + public void On() + { + if (MaintenanceManager.MaintenanceEnabled) + { + Context.Respond(LanguageManager[LangKeys.AlreadyEnabled]); + return; + } + + MaintenanceManager.MaintenanceEnabled = true; + + Context.Respond(LanguageManager[LangKeys.MaintenanceActivated]); + } + + [Command("off", "Set the status of the maintenance mode to disabled.")] + public void Off() + { + if (!MaintenanceManager.MaintenanceEnabled) + { + Context.Respond(LanguageManager[LangKeys.AlreadyDisabled]); + return; + } + + MaintenanceManager.MaintenanceEnabled = false; + MaintenanceScheduler.CancelTimer(null); + + Context.Respond(LanguageManager[LangKeys.MaintenanceDeactivated]); + } + + [Command("starttimer", "Will enable maintenance mode after the time is up.")] + public void StartTimer(string time) + { + var startTime = double.TryParse(time, out var seconds) ? TimeSpan.FromSeconds(seconds) : TimeSpan.Parse(time); + + if (startTime > MaxTime) + { + Context.Respond(LanguageManager[LangKeys.TimerTooLong]); + return; + } + + if (MaintenanceManager.MaintenanceEnabled) + { + Context.Respond(LanguageManager[LangKeys.AlreadyEnabled]); + return; + } + + if (MaintenanceScheduler.CurrentSchedule.StartTime.HasValue) + { + Context.Respond(LanguageManager[LangKeys.TimerAlreadyRunning]); + return; + } + + MaintenanceScheduler.ScheduleTimer(TimerType.Start, startTime); + + Context.Respond(LanguageManager.Format(LangKeys.StartTimerStarted, MaintenanceScheduler.CurrentSchedule)); + } + + [Command("endtimer", + "Will enable maintenance mode for the given time in minutes. After the time is up, it'll be disabled again.")] + public void EndTimer(string duration) + { + var endTime = double.TryParse(duration, out var seconds) ? TimeSpan.FromSeconds(seconds) : TimeSpan.Parse(duration); + + if (endTime > MaxTime) + { + Context.Respond(LanguageManager[LangKeys.TimerTooLong]); + return; + } + + if (MaintenanceScheduler.CurrentSchedule.EndTime.HasValue) + { + Context.Respond(LanguageManager[LangKeys.TimerAlreadyRunning]); + return; + } + + MaintenanceManager.MaintenanceEnabled = true; + MaintenanceScheduler.ScheduleTimer(TimerType.End, endTime); + + Context.Respond(LanguageManager.Format(LangKeys.EndTimerStarted, MaintenanceScheduler.CurrentSchedule)); + } + + [Command("schedule", "Will enable maintenance mode after the given time, then disable it according to the second parameter.")] + public void Schedule(string time, string duration) + { + var startTime = double.TryParse(time, out var startSeconds) ? TimeSpan.FromSeconds(startSeconds) : TimeSpan.Parse(time); + var endTime = double.TryParse(duration, out var endSeconds) ? TimeSpan.FromSeconds(endSeconds) : TimeSpan.Parse(duration); + + if (startTime > MaxTime || endTime > MaxTime) + { + Context.Respond(LanguageManager[LangKeys.TimerTooLong]); + return; + } + + if (MaintenanceScheduler.CurrentSchedule != MaintenanceSchedule.Default) + { + Context.Respond(LanguageManager[LangKeys.TimerAlreadyRunning]); + return; + } + + if (MaintenanceManager.MaintenanceEnabled) + { + Context.Respond(LanguageManager[LangKeys.AlreadyEnabled]); + return; + } + + MaintenanceScheduler.ScheduleMaintenance(startTime, endTime); + + Context.Respond(LanguageManager.Format(LangKeys.ScheduleTimerBroadcast, MaintenanceScheduler.CurrentSchedule)); + } + + [Command("aborttimer", "Cancels a running start-/endtimer")] + public void AbortTimer() + { + if (MaintenanceScheduler.CurrentSchedule == MaintenanceSchedule.Default) + { + Context.Respond(LanguageManager[LangKeys.TimerNotRunning]); + return; + } + + MaintenanceScheduler.CancelTimer(null); + + Context.Respond(LanguageManager[LangKeys.TimerCancelled]); + } +} \ No newline at end of file diff --git a/Maintenance/ConfigKeys.cs b/Maintenance/ConfigKeys.cs new file mode 100644 index 0000000..5082944 --- /dev/null +++ b/Maintenance/ConfigKeys.cs @@ -0,0 +1,20 @@ +namespace Maintenance; + +public static class ConfigKeys +{ + public const string MaintenanceEnabled = "maintenance-enabled"; + + public const string CommandsOnMaintenanceEnable = "commands-on-maintenance-enable"; + public const string CommandsOnMaintenanceDisable = "commands-on-maintenance-disable"; + + public const string EnableWorldMessage = "enable-world-message"; + public const string WorldMessage = "world-message"; + + public const string KickOnlinePlayers = "kick-online-players"; + + public const string Language = "language"; + + public const string ContinueEndTimerAfterRestart = "continue-endtimer-after-restart"; + + public const string TimerBroadcastForSeconds = "timer-broadcast-for-seconds"; +} \ No newline at end of file diff --git a/Maintenance/Extensions/CompletableFutureExtensions.cs b/Maintenance/Extensions/CompletableFutureExtensions.cs new file mode 100644 index 0000000..c7a90fb --- /dev/null +++ b/Maintenance/Extensions/CompletableFutureExtensions.cs @@ -0,0 +1,31 @@ +using java.util.concurrent; +using java.util.function; +using Torch.Utils; + +namespace Maintenance.Extensions; + +public static class CompletableFutureExtensions +{ + [ReflectedStaticMethod(Type = typeof(Consumer), Name = "andThen")] + private static readonly Func AndThenDefault = null!; + + public static Task ToTask(this CompletableFuture completableFuture) + { + var taskCompletionSource = new TaskCompletionSource(); + + completableFuture.thenAccept(new TaskConsumer(taskCompletionSource)); + + return taskCompletionSource.Task; + } + + private sealed class TaskConsumer(TaskCompletionSource completionSource) : Consumer + { + + public void accept(object t) + { + completionSource.SetResult((T)t); + } + + public Consumer andThen(Consumer after) => AndThenDefault(this, after); + } +} \ No newline at end of file diff --git a/Maintenance/LangKeys.cs b/Maintenance/LangKeys.cs new file mode 100644 index 0000000..8eca20a --- /dev/null +++ b/Maintenance/LangKeys.cs @@ -0,0 +1,18 @@ +namespace Maintenance; + +public static class LangKeys +{ + public const string MaintenanceActivated = "maintenanceActivated"; + public const string MaintenanceDeactivated = "maintenanceDeactivated"; + public const string AlreadyEnabled = "alreadyEnabled"; + public const string AlreadyDisabled = "alreadyDisabled"; + public const string EndTimerBroadcast = "endtimerBroadcast"; + public const string EndTimerStarted = "endtimerStarted"; + public const string StartTimerBroadcast = "starttimerBroadcast"; + public const string StartTimerStarted = "starttimerStarted"; + public const string ScheduleTimerBroadcast = "scheduletimerBroadcast"; + public const string TimerAlreadyRunning = "timerAlreadyRunning"; + public const string TimerNotRunning = "timerNotRunning"; + public const string TimerCancelled = "timerCancelled"; + public const string TimerTooLong = "timerTooLong"; +} \ No newline at end of file diff --git a/Maintenance/Maintenance.csproj b/Maintenance/Maintenance.csproj new file mode 100644 index 0000000..20356f9 --- /dev/null +++ b/Maintenance/Maintenance.csproj @@ -0,0 +1,53 @@ + + + + net48 + enable + enable + x64 + 12 + true + true + true + + + + none + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + PreserveNewest + + + + + + + + + + + libs\Steamworks.NET.dll + False + + + + diff --git a/Maintenance/Managers/ConfigManager.cs b/Maintenance/Managers/ConfigManager.cs new file mode 100644 index 0000000..bb1eb2d --- /dev/null +++ b/Maintenance/Managers/ConfigManager.cs @@ -0,0 +1,38 @@ +using System.IO; +using Microsoft.Extensions.Configuration; +using Torch.API.Managers; + +namespace Maintenance.Managers; + +public class ConfigManager(string storagePath) : IManager +{ + private IConfigurationRoot? _configurationRoot; + + public IConfiguration Configuration => _configurationRoot ?? throw new InvalidOperationException("Manager is not attached"); + + public void Attach() + { + var configPath = Path.Combine(storagePath, "config.yml"); + + if (!File.Exists(configPath)) + ExtractDefault(configPath); + + _configurationRoot = new ConfigurationBuilder() + .AddYamlFile(configPath, false, true) + .Build(); + } + + private void ExtractDefault(string path) + { + var name = path[storagePath.Length..].TrimStart(Path.GetInvalidFileNameChars()).Replace('\\', '.'); + + using var stream = typeof(ConfigManager).Assembly.GetManifestResourceStream(name)!; + using var file = File.Create(path); + + stream.CopyTo(file); + } + + public void Detach() + { + } +} \ No newline at end of file diff --git a/Maintenance/Managers/LanguageManager.cs b/Maintenance/Managers/LanguageManager.cs new file mode 100644 index 0000000..6abe047 --- /dev/null +++ b/Maintenance/Managers/LanguageManager.cs @@ -0,0 +1,62 @@ +using System.IO; +using Microsoft.Extensions.Configuration; +using SmartFormat; +using SmartFormat.Core.Settings; +using Torch.API.Managers; +using Torch.Managers; + +namespace Maintenance.Managers; + +public class LanguageManager(string storagePath) : IManager +{ + private readonly string[] _langs = { "en" }; + + public IConfiguration Language => _configurationRoot ?? throw new InvalidOperationException("Manager is not attached"); + + private IConfigurationRoot? _configurationRoot; + + [Manager.Dependency] + private readonly ConfigManager _configManager = null!; + + public SmartFormatter Formatter { get; } = Smart.CreateDefaultSmartFormat(new() + { + CaseSensitivity = CaseSensitivityType.CaseInsensitive + }); + + public string this[string key] => Language[key] ?? throw new KeyNotFoundException(key); + + public void Attach() + { + var langDirectory = Path.Combine(storagePath, "translations"); + + if (!Directory.Exists(langDirectory)) + Directory.CreateDirectory(langDirectory); + + foreach (var lang in _langs) + { + var path = Path.Combine(langDirectory, $"{lang}.yml"); + if (!File.Exists(path)) + ExtractTranslation(path); + } + + _configurationRoot = new ConfigurationBuilder() + .AddYamlFile(Path.Combine(langDirectory, $"{_configManager.Configuration.GetValue(ConfigKeys.Language)!}.yml"), false, true) + .Build(); + } + + public string Format(string key, object obj) => Formatter.Format(this[key], obj); + + private void ExtractTranslation(string path) + { + var lang = path[storagePath.Length..].TrimStart(Path.GetInvalidFileNameChars()).Replace('\\', '.'); + + using var stream = typeof(LanguageManager).Assembly.GetManifestResourceStream(lang)!; + using var file = File.Create(path); + + stream.CopyTo(file); + } + + public void Detach() + { + } +} \ No newline at end of file diff --git a/Maintenance/Managers/MaintenanceManager.cs b/Maintenance/Managers/MaintenanceManager.cs new file mode 100644 index 0000000..c4921d2 --- /dev/null +++ b/Maintenance/Managers/MaintenanceManager.cs @@ -0,0 +1,179 @@ +using java.lang; +using LuckPerms.Torch.Utils.Extensions; +using Maintenance.Extensions; +using Microsoft.Extensions.Configuration; +using net.luckperms.api; +using net.luckperms.api.model.user; +using NLog; +using Sandbox; +using Sandbox.Engine.Multiplayer; +using Torch.API; +using Torch.API.Event; +using Torch.Commands; +using Torch.Managers; +using Torch.Server.Managers; +using Torch.Utils; +using VRage.Game.ModAPI; +using VRage.Network; + +namespace Maintenance.Managers; + +public class MaintenanceManager(ITorchBase torch) : Manager(torch), IEventHandler +{ + public const string BypassPermission = "maintenance.bypass"; + + [ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))] + private static readonly Func ConvertSteamIDFrom64 = null!; + + private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); + + [Dependency] + private readonly IEventManager _eventManager = null!; + + [Dependency] + private readonly ConfigManager _configManager = null!; + + [Dependency] + private readonly MultiplayerManagerDedicated _multiplayerManager = null!; + + [Dependency] + private readonly CommandManager _commandManager = null!; + + private bool _maintenanceEnabled; + private IDisposable? _disposable; + + public bool MaintenanceEnabled + { + get => _maintenanceEnabled; + set + { + if (value == _maintenanceEnabled) return; + _maintenanceEnabled = value; + + Log.Info(_maintenanceEnabled ? "Maintenance Enabled" : "Maintenance Disabled"); + + ExecuteCommandsOnModeChange(); + + if (!_maintenanceEnabled || + !_configManager.Configuration.GetValue(ConfigKeys.KickOnlinePlayers)) return; + + Torch.Invoke(() => + { + Log.Info("Kicking online players"); + foreach (var steamId in _multiplayerManager.Players.Keys) + { + if (!GetIsAllowedToJoin(steamId)) + MyMultiplayer.Static.DisconnectClient(steamId); + } + }); + } + } + + private void ExecuteCommandsOnModeChange() + { + var commandsOnEnable = + _configManager.Configuration.GetSection(ConfigKeys.CommandsOnMaintenanceEnable).GetChildren() + .Select(b => b.Value!).ToArray(); + var commandsOnDisable = + _configManager.Configuration.GetSection(ConfigKeys.CommandsOnMaintenanceDisable).GetChildren() + .Select(b => b.Value!).ToArray(); + + if (commandsOnEnable.Length <= 0 && commandsOnDisable.Length <= 0) return; + + Torch.Invoke(() => + { + switch (_maintenanceEnabled) + { + case true when commandsOnEnable.Length > 0: + { + foreach (var command in commandsOnEnable) + { + _commandManager.HandleCommandFromServer(command, + msg => Log.Info("Feedback from `{0}`: `{1}`", command, msg.Message)); + } + + break; + } + case false when commandsOnDisable.Length > 0: + { + foreach (var command in commandsOnDisable) + { + _commandManager.HandleCommandFromServer(command, + msg => Log.Info("Feedback from `{0}`: `{1}`", command, msg.Message)); + } + + break; + } + } + }); + } + + public override void Attach() + { + _eventManager.RegisterHandler(this); + _disposable = _configManager.Configuration.GetRequiredSection(ConfigKeys.MaintenanceEnabled).GetReloadToken() + .RegisterChangeCallback( + _ => MaintenanceEnabled = _configManager.Configuration.GetValue(ConfigKeys.MaintenanceEnabled), + null); + } + + public override void Detach() + { + _disposable?.Dispose(); + } + + [EventHandler] + private void OnValidateAuthTicket(ref ValidateAuthTicketEvent info) + { + if (!MaintenanceEnabled) return; + + var steamId = info.SteamID; + var response = info.SteamResponse; + + info.FutureVerdict = FutureVerdict(); + + async Task FutureVerdict() + { + if (await GetIsAllowedToJoinAsync(steamId)) return response; + + Log.Info("Rejecting {0}", steamId); + return JoinResult.TicketCanceled; + } + } + + private static async ValueTask GetIsAllowedToJoinAsync(ulong steamId) + { + try + { + var api = LuckPermsProvider.get(); + var user = await api.getUserManager().loadUser(steamId.GetUuid()).ToTask(); + + return user.getCachedData().getPermissionData().checkPermission(BypassPermission).asBoolean(); + } + catch (IllegalStateException) + { + // we dont have api initialized + } + + var stringId = steamId.ToString(); + + return MySandboxGame.ConfigDedicated.Administrators.Any( + b => b == stringId || b == ConvertSteamIDFrom64(steamId)); + } + + private bool GetIsAllowedToJoin(ulong steamId) + { + try + { + var api = LuckPermsProvider.get(); + return api.getPlayerAdapter(typeof(IPlayer)).getPermissionData(_multiplayerManager.Players[steamId]) + .checkPermission(BypassPermission).asBoolean(); + } + catch (IllegalStateException) + { + // we dont have api initialized + } + + return _multiplayerManager.GetUserPromoteLevel(steamId) == MyPromoteLevel.Owner; + } +} \ No newline at end of file diff --git a/Maintenance/Managers/MaintenanceScheduleManager.cs b/Maintenance/Managers/MaintenanceScheduleManager.cs new file mode 100644 index 0000000..ec8ca37 --- /dev/null +++ b/Maintenance/Managers/MaintenanceScheduleManager.cs @@ -0,0 +1,181 @@ +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Configuration; +using Torch.API.Managers; +using Torch.Managers; + +namespace Maintenance.Managers; + +public class MaintenanceScheduleManager(string storagePath) : IManager +{ + [Manager.Dependency] + private readonly MaintenanceManager _maintenanceManager = null!; + + [Manager.Dependency] + private readonly ConfigManager _configManager = null!; + + [Manager.Dependency] + private readonly LanguageManager _languageManager = null!; + + [Manager.Dependency] + private readonly IChatManagerServer _chatManager = null!; + + private MaintenanceSchedule _currentSchedule = MaintenanceSchedule.Default; + + private readonly FileInfo _scheduleFile = new(Path.Combine(storagePath, ".schedule")); + + public MaintenanceSchedule CurrentSchedule + { + get => _currentSchedule; + private set + { + if (value != _currentSchedule) + { + using var file = _scheduleFile.Create(); + JsonSerializer.Serialize(file, value); + } + + _currentSchedule = value; + } + } + + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + public void Attach() + { + Scheduler(); + + if (!_scheduleFile.Exists) return; + + using (var file = _scheduleFile.OpenRead()) + _currentSchedule = JsonSerializer.Deserialize(file)!; + + _scheduleFile.Delete(); + + if (_currentSchedule is not { StartTime: null, EndTime: not null }) return; + + if (!_configManager.Configuration.GetValue(ConfigKeys.ContinueEndTimerAfterRestart)) + CurrentSchedule = MaintenanceSchedule.Default; + + _maintenanceManager.MaintenanceEnabled = true; + } + + private async void Scheduler() + { + var token = _cancellationTokenSource.Token; + + var timerSecondsSection = _configManager.Configuration.GetSection(ConfigKeys.TimerBroadcastForSeconds); + var timerSeconds = timerSecondsSection.GetChildren().Select(b => b.Get()).ToArray(); + + using var disposable = timerSecondsSection.GetReloadToken() + .RegisterChangeCallback(_ => timerSeconds = timerSecondsSection.GetChildren().Select(b => b.Get()).ToArray(), null); + + while (!token.IsCancellationRequested) + { + await Task.Delay(2000, token); + + switch (_maintenanceManager.MaintenanceEnabled) + { + case false when _currentSchedule.StartTime.HasValue: + { + if (timerSeconds.Any(b => Math.Abs(_currentSchedule.Time.TotalSeconds - b) <= 1)) + { + _chatManager.SendMessageAsOther("Maintenance", + Format(_currentSchedule.EndTime.HasValue + ? LangKeys.ScheduleTimerBroadcast + : LangKeys.StartTimerBroadcast)); + } + + if (_currentSchedule.Time.TotalSeconds <= 1) + { + _chatManager.SendMessageAsOther("Maintenance", Format(LangKeys.MaintenanceActivated)); + _maintenanceManager.MaintenanceEnabled = true; + CurrentSchedule = CurrentSchedule with { StartTime = null }; + } + + break; + } + case true when _currentSchedule.EndTime.HasValue: + { + if (timerSeconds.Any(b => Math.Abs(_currentSchedule.Duration.TotalSeconds - b) <= 1)) + { + _chatManager.SendMessageAsOther("Maintenance", Format(LangKeys.EndTimerBroadcast)); + } + + if (_currentSchedule.Duration.TotalSeconds <= 1) + { + _chatManager.SendMessageAsOther("Maintenance", Format(LangKeys.MaintenanceDeactivated)); + _maintenanceManager.MaintenanceEnabled = false; + CurrentSchedule = CurrentSchedule with { EndTime = null }; + } + + break; + } + } + } + + string Format(string key) => _languageManager.Format(key, _currentSchedule); + } + + public void Detach() + { + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + } + + public void ScheduleTimer(TimerType type, TimeSpan duration) + { + if (_maintenanceManager.MaintenanceEnabled && type == TimerType.Start) + throw new InvalidOperationException("Maintenance is already enabled"); + + if (type == TimerType.Start) + CurrentSchedule = CurrentSchedule with { StartTime = DateTimeOffset.Now + duration }; + else + CurrentSchedule = CurrentSchedule with { EndTime = DateTimeOffset.Now + duration }; + } + + public void ScheduleMaintenance(TimeSpan startTime, TimeSpan endTime) + { + if (_maintenanceManager.MaintenanceEnabled) + throw new InvalidOperationException("Maintenance is already enabled"); + + var startDateTime = DateTimeOffset.Now + startTime; + + CurrentSchedule = new(startDateTime, startDateTime + endTime); + } + + public void CancelTimer(TimerType? type) + { + CurrentSchedule = type switch + { + TimerType.Start when !_currentSchedule.StartTime.HasValue => throw new InvalidOperationException( + "No start timer running"), + TimerType.Start => MaintenanceSchedule.Default, + TimerType.End when !_currentSchedule.EndTime.HasValue => throw new InvalidOperationException( + "No end timer running"), + TimerType.End => CurrentSchedule with { EndTime = null }, + _ => MaintenanceSchedule.Default + }; + } +} + +public record MaintenanceSchedule(DateTimeOffset? StartTime, DateTimeOffset? EndTime) +{ + public static MaintenanceSchedule Default => new(null, null); + + [JsonIgnore] + public TimeSpan Time => Round((StartTime ?? throw new InvalidOperationException("No start timer running")) + - DateTimeOffset.Now); + [JsonIgnore] + public TimeSpan Duration => Round((EndTime ?? throw new InvalidOperationException("No end timer running")) + - (StartTime ?? DateTimeOffset.Now)); + + private static TimeSpan Round(TimeSpan timeSpan) => TimeSpan.FromSeconds(Math.Round(timeSpan.TotalSeconds, 0)); +} + +public enum TimerType : byte +{ + Start, + End +} \ No newline at end of file diff --git a/Maintenance/Patches/SteamQueryPatch.cs b/Maintenance/Patches/SteamQueryPatch.cs new file mode 100644 index 0000000..2f9d3a4 --- /dev/null +++ b/Maintenance/Patches/SteamQueryPatch.cs @@ -0,0 +1,361 @@ +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Maintenance.Managers; +using Microsoft.Extensions.Configuration; +using NLog; +using Sandbox; +using Sandbox.Engine.Multiplayer; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Steamworks; +using Torch; +using Torch.API; +using Torch.API.Managers; +using Torch.Managers.PatchManager; +using Torch.Utils; +using VRage.Game; +using VRage.GameServices; +using VRage.Library.Utils; +using VRage.Utils; + +namespace Maintenance.Patches; + +[PatchShim] +public static class SteamQueryPatch +{ + private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); + + [ReflectedMethodInfo(null, "SteamServerEntryPoint", TypeName = "VRage.Steam.MySteamGameServer, VRage.Steam")] + private static readonly MethodInfo EntryPointMethod = null!; + + [ReflectedMethodInfo(typeof(SteamQueryPatch), nameof(Prefix))] + private static readonly MethodInfo PrefixMethod = null!; + +#pragma warning disable CS0618 // Type or member is obsolete + private static ITorchBase Torch => TorchBase.Instance; +#pragma warning restore CS0618 // Type or member is obsolete + + private static bool MaintenanceEnabled => + Torch.CurrentSession?.Managers.GetManager().MaintenanceEnabled is true; + + public static void Patch(PatchContext context) + { + context.GetPattern(EntryPointMethod).Prefixes.Add(PrefixMethod); + } + + private static bool Prefix(IMyGameServer __instance, object argument) + { + var socket = (Socket)argument; + RunServerAsync(__instance, socket); + + return false; + } + + private static async void RunServerAsync(IMyGameServer server, Socket socket) + { + var localEndPoint = socket.LocalEndPoint; + var zeroEndPoint = new IPEndPoint(0L, 0); + // better to calc based on mtu but i dont care + var buffer = new byte[1500]; + + while (server.Running) + { + SocketReceiveFromResult result; + try + { + result = await socket.ReceiveFromAsync(new(buffer), SocketFlags.None, zeroEndPoint); + } + catch (SocketException ex) + { + if (!server.Running) + break; + + try + { + socket.Close(); + } + catch + { + // ignored + } + + Log.Warn($"Received socket exception with error code: {ex.ErrorCode}, {ex.SocketErrorCode}", ex); + Log.Info("Attempting to create new socket."); + socket = new(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + try + { + socket.Bind(localEndPoint); + continue; + } + catch (SocketException e) + { + Log.Fatal(e, "Error binding server endpoint"); + + socket.Close(); + server.GetType().GetProperty("Running")?.SetValue(server, false); + GameServer.Shutdown(); + break; + } + } + + if (!CheckHeader(buffer.AsSpan(0, 4))) + continue; + + if (result.ReceivedBytes > 4 && MySession.Static is not null) + { + Debug.Write("byte is "); + Debug.WriteLine(buffer[4].ToString("X")); + switch (buffer[4]) + { + case 0x54: + await HandleInfoRequestAsync(socket, result.RemoteEndPoint); + continue; + // players request without challenge + case 0x55 when result.ReceivedBytes > 8 && !CheckHeader(buffer.AsSpan(5, 4)): + await HandlePlayersRequestAsync(socket, result.RemoteEndPoint); + continue; + } + } + + var remoteEndPoint = (IPEndPoint)result.RemoteEndPoint; + +#pragma warning disable CS0618 + SteamGameServer.HandleIncomingPacket(buffer, result.ReceivedBytes, (uint)remoteEndPoint.Address.Address, + (ushort)remoteEndPoint.Port); +#pragma warning restore CS0618 + + int length; + while ((length = SteamGameServer.GetNextOutgoingPacket(buffer, buffer.Length, out var addr, out var port)) > + 0) + { +#pragma warning disable CS0618 + remoteEndPoint.Address.Address = addr; +#pragma warning restore CS0618 + remoteEndPoint.Port = port; + + await socket.SendToAsync(new(buffer, 0, length), SocketFlags.None, remoteEndPoint); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool CheckHeader(Span header) => + Unsafe.ReadUnaligned(ref header.GetPinnableReference()) == 0xFFFFFFFF; + + private static Task HandlePlayersRequestAsync(Socket socket, EndPoint sender) + { + if (Sync.Players is null) + return Task.CompletedTask; + + Debug.WriteLine($"players request from {sender}"); + using var ms = new MemoryStream(); + + // header + ms.WriteNoAlloc(0xFFFFFFFF); + ms.WriteByte(0x44); + + if (MaintenanceEnabled) + { + ms.WriteByte(0); // count + return SendAsync(ms, socket, sender); + } + + var players = Sync.Clients.GetClients().Where(b => !b.IsLocal).Select(b => (b.DisplayName, + (float?)(DateTime.Now - Sync.Players.TryGetIdentity((long)b.SteamUserId)?.LastLoginTime)?.TotalSeconds ?? + 0f)).ToArray(); + + var count = (byte)players.Length; + + // total + ms.WriteByte(count); + + for (byte i = 0; i < count; i++) + { + // i + ms.WriteByte(i); + + var (name, duration) = players[i]; + + // name + WriteUtf8String(ms, name); + + // score + ms.WriteNoAlloc(0U); + + // duration + ms.WriteNoAlloc(duration); + } + + return SendAsync(ms, socket, sender); + } + + private static Task HandleInfoRequestAsync(Socket socket, EndPoint sender) + { + if (Sync.Players is null) + return Task.CompletedTask; + + Debug.WriteLine($"info request from {sender}"); + using var ms = new MemoryStream(); + + var mp = MyMultiplayer.Static; + + // header + ms.WriteNoAlloc(0xFFFFFFFF); + ms.WriteByte(0x49); + + // protocol + ms.WriteByte(0x11); + + var maintenanceEnabled = MaintenanceEnabled; + + // name + WriteUtf8String(ms, mp.HostName); + + if (maintenanceEnabled) + { + var config = Torch.Managers.GetManager().Configuration; + var formatter = Torch.Managers.GetManager().Formatter; + + if (config.GetValue(ConfigKeys.EnableWorldMessage)) + { + var randomItem = config.GetSection(ConfigKeys.WorldMessage).GetChildren().Select(b => b.Value!).ToArray() + .GetRandomItem(); + var schedule = Torch.CurrentSession.Managers.GetManager().CurrentSchedule; + + // map + WriteUtf8String(ms, schedule.EndTime.HasValue ? formatter.Format(randomItem, schedule) : randomItem); + } + else + { + // map + WriteUtf8String(ms, mp.WorldName); + } + } + else + { + // map + WriteUtf8String(ms, mp.WorldName); + } + + // folder + WriteUtf8String(ms, "Space Engineers"); + + // game full name + WriteUtf8String(ms, "Space Engineers"); + + // app id + ms.WriteNoAlloc((short)0); + + // players + ms.WriteByte(maintenanceEnabled ? default : (byte)(Sync.Clients.Count - 1)); + + // max players + ms.WriteByte((byte)(maintenanceEnabled ? 0x00 : mp.MaxPlayers)); + + // bots + ms.WriteByte(0x00); + + // server type + ms.WriteByte((byte)'d'); + + // env + ms.WriteByte((byte)'w'); + + // visibility + ms.WriteByte(0x00); + + // vac + ms.WriteByte(0x01); + + // version + WriteUtf8String(ms, MyFinalBuildConstants.APP_VERSION.Version.ToString()); + + // edf (GameID SteamID Keywords Port) + ms.WriteByte(177); + + // edf port + ms.WriteNoAlloc((short)((IPEndPoint)socket.LocalEndPoint).Port); + + // edf steam id + ms.WriteNoAlloc(Sync.MyId); + + // edf keywords + var gameMode = mp.GameMode is MyGameModeEnum.Survival + ? $"S{mp.InventoryMultiplier}-{mp.BlocksInventoryMultiplier}-{mp.AssemblerMultiplier}-{mp.RefineryMultiplier}" + : "C"; + WriteUtf8String( + ms, + $"groupId{MySandboxGame.ConfigDedicated.GroupID} version{MyFinalBuildConstants.APP_VERSION} datahasheRRN1/jJ7J2ZlR7GB1D5PDzn0sQ= mods{mp.ModCount} gamemode{gameMode} view{mp.SyncDistance}"); + + // edf game id + ms.WriteNoAlloc((ulong)244850); + + return SendAsync(ms, socket, sender); + } + + private static async Task SendAsync(MemoryStream ms, Socket socket, EndPoint sender) + { + Debug.WriteLine(string.Join(" ", ms.GetBuffer().Take((int)ms.Length).Select(b => b.ToString("X")))); + + const int packetSize = 1200; + + if (ms.Length < packetSize) + { + await socket.SendToAsync(new(ms.GetBuffer(), 0, (int)ms.Length), SocketFlags.None, sender); + return; + } + + var id = (uint)MyRandom.Instance.Next(); + id &= ~(1 << 31); + + var msLength = ms.Length - 4; + for (var i = 0; i < msLength; i += packetSize) + { + var length = Math.Min(packetSize, (int)msLength - i); + + var buffer = new byte[length + 10]; + + // header + Unsafe.WriteUnaligned(ref buffer.AsSpan(0, 4).GetPinnableReference(), 0xFFFFFFFE); + + // id + Unsafe.WriteUnaligned(ref buffer.AsSpan(4, 4).GetPinnableReference(), id); + + // total + buffer[8] = (byte)Math.Ceiling((float)msLength / packetSize); + + // number + buffer[9] = (byte)(i / packetSize); + + ms.GetBuffer().AsSpan(i + 4, length).CopyTo(buffer.AsSpan(10)); + + await socket.SendToAsync(new(buffer), SocketFlags.None, sender); + } + } + + private static unsafe void WriteUtf8String(Stream stream, string str) + { + if (string.IsNullOrEmpty(str)) + { + stream.WriteByte(0x00); + return; + } + + var chars = str.AsSpan(); + Span bytes = stackalloc byte[Encoding.UTF8.GetByteCount(str)]; + + var length = Encoding.UTF8.GetBytes((char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(chars)), + chars.Length, + (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(bytes)), + bytes.Length); + + stream.WriteNoAlloc((byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(bytes)), 0, length); + stream.WriteByte(0x00); + } +} \ No newline at end of file diff --git a/Maintenance/Plugin.cs b/Maintenance/Plugin.cs new file mode 100644 index 0000000..d24220d --- /dev/null +++ b/Maintenance/Plugin.cs @@ -0,0 +1,21 @@ +using System.IO; +using Maintenance.Managers; +using Torch; +using Torch.API; +using Torch.API.Managers; +using Torch.API.Session; + +namespace Maintenance; + +public class Plugin : TorchPluginBase +{ + public override void Init(ITorchBase torch) + { + var storagePath = Directory.CreateDirectory(Path.Combine(StoragePath, "maintenance")).FullName; + + torch.Managers.AddManager(new ConfigManager(storagePath)); + torch.Managers.AddManager(new LanguageManager(storagePath)); + torch.Managers.GetManager().AddFactory(s => new MaintenanceManager(s.Torch)); + torch.Managers.GetManager().AddFactory(_ => new MaintenanceScheduleManager(storagePath)); + } +} \ No newline at end of file diff --git a/Maintenance/Resources/config.yml b/Maintenance/Resources/config.yml new file mode 100644 index 0000000..e1a3e9b --- /dev/null +++ b/Maintenance/Resources/config.yml @@ -0,0 +1,32 @@ +# Enables maintenance mode. +maintenance-enabled: false + +# Any extra commands inside the arrays will be executed when maintenance is enabled/disabled. +# Example: commands-on-maintenance-enable: ["say hello!", "stop"] +commands-on-maintenance-enable: [] +commands-on-maintenance-disable: [] + +# If set to true, the world name for the servers list from this pool will be chosen. +# If you put in multiple entries, one of them will be chosen randomly on every ping. +# If running an endtimer, the time left can be displayed by including {duration} in the message. +# Additionally you can set custom timestamp format with {duration:hh\\:mm\\:\\ss}. See all available formats at https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings +enable-world-message: true +world-message: +# - "[MAINTENANCE]" +# - "Back in {duration:hh\\:mm}" + +# Set this to false if you do not want players to be kicked when you enable maintenance (new connections will still be blocked). +# ... I don't know why you would want that, but you can disable it. :p +kick-online-players: true + +# Changes the language of command feedback/messages. +# If you find missing translations or want to contribute a new language file, you are very welcome to message me on my Discord server! :) +# Currently available are: en (English) +language: en + +# If enabled and the server is restarted while running an endtimer, the timer will be continued after the restart. +# If the timer ends while the server is offline, maintenance will be disabled as soon as the server starts again. +continue-endtimer-after-restart: true + +# If using the timer command: In what intervals before enabling/disabling maintenance there will be a broadcast. +timer-broadcast-for-seconds: [1200, 900, 600, 300, 120, 60, 30, 20, 10, 5, 4, 3, 2, 1] \ No newline at end of file diff --git a/Maintenance/Resources/translations/en.yml b/Maintenance/Resources/translations/en.yml new file mode 100644 index 0000000..0b0e4b5 --- /dev/null +++ b/Maintenance/Resources/translations/en.yml @@ -0,0 +1,13 @@ +maintenanceActivated: "Maintenance mode is now activated." +maintenanceDeactivated: "Maintenance mode is now deactivated." +alreadyEnabled: "Maintenance is already enabled!" +alreadyDisabled: "Maintenance is already disabled!" +endtimerBroadcast: "Maintenance mode will be disabled in {duration}." +endtimerStarted: "Started timer: Maintenance mode will be deactivated in {duration}." +starttimerBroadcast: "Maintenance mode will be enabled in {time}." +starttimerStarted: "Started timer: Maintenance mode will be activated in {time}." +scheduletimerBroadcast: "Maintenance mode will be enabled in {time} and will last for {duration}." +timerAlreadyRunning: "There is already a timer scheduled!" +timerNotRunning: "There is currently no running timer." +timerCancelled: "The current timer has been disabled." +timerTooLong: "The number has to be less than 40320 (28 days)!" \ No newline at end of file diff --git a/Maintenance/manifest.xml b/Maintenance/manifest.xml new file mode 100644 index 0000000..526796c --- /dev/null +++ b/Maintenance/manifest.xml @@ -0,0 +1,6 @@ + + + Maintenance + 42AF9955-AAA7-442F-9BF4-AAC4FA4A923F + v1.0.0 + \ No newline at end of file diff --git a/Maintenance/packages.lock.json b/Maintenance/packages.lock.json new file mode 100644 index 0000000..a741a67 --- /dev/null +++ b/Maintenance/packages.lock.json @@ -0,0 +1,438 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.8": { + "Alexinea.Extensions.Configuration.Yaml": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "gIeecqFF2YeDrGxRulJvyYCRbQcvmowNnQt5qDg2FUS3bzjtwgahHFioD+yWFGxBYR/vWGbP3h6PGoCUxOzTqA==", + "dependencies": { + "Microsoft.Extensions.Configuration": "7.0.0", + "Microsoft.Extensions.Configuration.FileExtensions": "7.0.0", + "YamlDotNet": "12.1.0" + } + }, + "LuckPerms.Torch.Api": { + "type": "Direct", + "requested": "[5.4.0, )", + "resolved": "5.4.0", + "contentHash": "jwmRs6dJupqqj2V5bf1vMleQIFqKxhsaLxiNvTFFrzsgFjPJf3TDMEe68Qf62/J1Z2gQKvVbSw/LCGCn+ZOWmw==", + "dependencies": { + "IKVM.Maven.Sdk": "1.6.1", + "LuckPerms.Torch.Utils": "1.0.0", + "Torch.Loader": "1.0.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.FileExtensions": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "McP+Lz/EKwvtCv48z0YImw+L1gi1gy5rHhNaNIY2CrjloV+XY8gydT8DjMR6zWeL13AFK+DioVpppwAuO1Gi1w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Physical": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "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.8.1, )", + "resolved": "1.8.1", + "contentHash": "T60CnqUsOC8JT2O9ixmBVBoA+0n4FIkRGwK4IuuPgncsJYd5m44s/IaNMQsCzZ7nxmPHa6A9DEaXSi1/ENx8iA==" + }, + "SmartFormat": { + "type": "Direct", + "requested": "[3.3.0, )", + "resolved": "3.3.0", + "contentHash": "JN19FJuWZwW3G3euSQNTDLAr5wI8ok/VDcBfEN88cZPvzCQMPdyS9s4/fQlEuupXH+IV0wBEbodHVS51t2RIDQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.ValueTuple": "4.5.0" + } + }, + "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" + } + }, + "IKVM": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "CEgKzDhnBq5XoCt0ABREfIn6k0TqF2go7zFAiaI9cjXesPezfquVJRPPn9fl+hO90fOo7eCDnkgDn5B8DoZe1w==", + "dependencies": { + "IKVM.Image": "8.7.1", + "IKVM.MSBuild": "8.7.1", + "Mono.Posix": "7.1.0-final.1.21458.1", + "Mono.Unix": "7.1.0-final.1.21458.1", + "System.Buffers": "4.5.1", + "System.IO.Pipelines": "6.0.3", + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Text.Json": "6.0.6", + "System.ValueTuple": "4.5.0" + } + }, + "IKVM.Image": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "RqwuWeb9cwqmcTKREb71odXvv5NO5+Zvo3SSG2CW1XumqCwCkm7sFdzulTYHEQcVs8GjDUlvLIEnrb6QLsqb7w==", + "dependencies": { + "IKVM.Image.runtime.linux-arm": "8.7.1", + "IKVM.Image.runtime.linux-arm64": "8.7.1", + "IKVM.Image.runtime.linux-musl-arm": "8.7.1", + "IKVM.Image.runtime.linux-musl-arm64": "8.7.1", + "IKVM.Image.runtime.linux-musl-x64": "8.7.1", + "IKVM.Image.runtime.linux-x64": "8.7.1", + "IKVM.Image.runtime.osx-arm64": "8.7.1", + "IKVM.Image.runtime.osx-x64": "8.7.1", + "IKVM.Image.runtime.win-arm64": "8.7.1", + "IKVM.Image.runtime.win-x64": "8.7.1", + "IKVM.Image.runtime.win-x86": "8.7.1" + } + }, + "IKVM.Image.runtime.linux-arm": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "f12l6AKUfM8Q1Dv9ZbkW39Ok2URq8ar9buhvXFtCeDbXxi5euYyYfR/eehM3nSb0wTo3cyvGxqM/G5M/jI9WUg==" + }, + "IKVM.Image.runtime.linux-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "o1URwAXZjfgdkS6R12ZA/l2oKhk5j9f98YjUiQfhzgt63ksVHjz3f5sRELDrsHUCAWNT2GR3ahlPHABuD3WT4A==" + }, + "IKVM.Image.runtime.linux-musl-arm": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "+3ijUii2jwsl0KeZZ3nkiwKkie0qpskLerARjrKdKQ3ylDxVaSwxtQWGJT7Eiu1YpKbyKYXOMptHE1IQqO4Zkg==" + }, + "IKVM.Image.runtime.linux-musl-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "9cXMSPAa8ODf99vuce0wnFV9urYy4iVjAp+pE4ieejqp/Jk2NCtKr7NJs+Q6shs3nsOl6A2aJnxZV5FGnd/wCg==" + }, + "IKVM.Image.runtime.linux-musl-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "K3DO1q2kGYnnyFNZ3K27K3Q19yl5U+at4le9aA9O/Or3zpXi2c1GOFaA+hrVB7DzIrvxXGCR6qKGzM9K8goYmg==" + }, + "IKVM.Image.runtime.linux-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "bG9Sara5Ft7JTf8iMS9RUea4Foi05Vdr7t7VA37SeloTTxlFmNUYSafo+X1U9eOYuv8XUueveid07E+KzMTSGQ==" + }, + "IKVM.Image.runtime.osx-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "vShR5pUZJIsTsc1lUzsNdBINGjPZ20RrUIunQe9W/rCW63El3uKZq2Rboa2E50YXNG3BgmMaCEUmel3nqS8DQw==" + }, + "IKVM.Image.runtime.osx-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "E/IFd3Eqj/6vxk1EqRBD5lpZGwI3qx/Uocqnt98UGr01HPO2ZvNs3Y2kmSmz3hA/xwDrjObCVrdYWKsfzoIwTw==" + }, + "IKVM.Image.runtime.win-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "oSvRLYy3iWgdJyFkbmNr0pl4x9/1zbXvavybpOzTFa7xSg3dfAcysUi8nHgjNpd94z8udrOcKEbm8GQ5DKAdRg==" + }, + "IKVM.Image.runtime.win-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "Od5ynm5wVyzV85CQ5VfUmQAvbrEKjRxEIR1Id2tHugVGPnL3cviEezbokg+tkByIubIx1mAjJ4HGqtcNqTDpmA==" + }, + "IKVM.Image.runtime.win-x86": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "yBXBH2Ad8UjJ6ZhH6eqJyya8a22Et5lAUNQ0S8Vtexmay+oby3880bzvUkQ0rRKF1OceJxm6u3bLC58hnh16eQ==" + }, + "IKVM.Maven.Sdk": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "Gb8bB12uYQGxiFggrT540Sy/71xYQr+8JSrauG5OlKiIcQhh6k3vI7I7hLQAWdd+bzdFYO7rs/BxdPzzV+j3Fg==", + "dependencies": { + "IKVM": "8.7.1" + } + }, + "IKVM.MSBuild": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "gkSV0y7mZ25PgXw9cnTnVFhE/oa9XSxcPkXnYS2I4XQ+moKkPeuZokUJaFy/qN1hcJIL8s9UKo6kUNwjjEFLBQ==", + "dependencies": { + "IKVM.MSBuild.Tools": "8.7.1" + } + }, + "IKVM.MSBuild.Tools": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "MJzc2ymPe27pY3Cz+s6yvr67RuVnAsFKuD0hSdN2wnERpz/K2wl9ZSaJ/egRJRrfs/0R2qqYo4Eer6H9Mcc4ag==", + "dependencies": { + "IKVM.MSBuild.Tools.runtime.linux-arm": "8.7.1", + "IKVM.MSBuild.Tools.runtime.linux-arm64": "8.7.1", + "IKVM.MSBuild.Tools.runtime.linux-x64": "8.7.1", + "IKVM.MSBuild.Tools.runtime.osx-arm64": "8.7.1", + "IKVM.MSBuild.Tools.runtime.osx-x64": "8.7.1", + "IKVM.MSBuild.Tools.runtime.win-arm64": "8.7.1", + "IKVM.MSBuild.Tools.runtime.win-x64": "8.7.1" + } + }, + "IKVM.MSBuild.Tools.runtime.linux-arm": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "2X2meYtplgzEJpZEHpEdrnhbyzKlJmfkOfKMenMM1W707JQ2SsWTDHlWw7PwY3KzPKoDbqNWb69rHS9a99TnUQ==" + }, + "IKVM.MSBuild.Tools.runtime.linux-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "BmhJBqbjn693fLo5G+YyXB7uDF11Ia5k1K7INk1zMYiBcfYZHJUEE29KZ5fvXgspjn6OEazgmOF/PxoxWTBmkQ==" + }, + "IKVM.MSBuild.Tools.runtime.linux-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "zBaGJoJ4pvf3uPNfxreHPGGhQFWUqiLyGcDurTuPGix29T8c3TeCU4z5JAJkhGf7hat/s98pSzqt9T5uAahSDg==" + }, + "IKVM.MSBuild.Tools.runtime.osx-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "XCG8udNMf7WjfUth7K1x63vP/Kra+4p/WByzMHNvLoM47x2XVX8OMyw1N6SGah+cHtsKL8VKkLdlct/b2Cpxbw==" + }, + "IKVM.MSBuild.Tools.runtime.osx-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "boMF3LCpTrkbi2emU263gVHz/TYCgtCVhDMLXTojIaJ8guA5q/nuFufhYljpjECzxzVg3IUIX6jNBu4PEjC+HQ==" + }, + "IKVM.MSBuild.Tools.runtime.win-arm64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "TRssQQBZbeXBKDtF8yo5bLk9mXmnvN1MUucLgP+t+6araaYUohsVJieMoDiJhcVqWgStv88+BHKLCexII/NUFw==" + }, + "IKVM.MSBuild.Tools.runtime.win-x64": { + "type": "Transitive", + "resolved": "8.7.1", + "contentHash": "tS4FAjGeZ0Gvaabb6Jy74Haw20kZu9w8AP9P+h27aYJm9udKqc+XFHRlifPOnXazVbbPOW1PkUtrk4TEsLYeRw==" + }, + "LuckPerms.Torch.Utils": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "lEapa9SvIZDOaD4/vJ05Ire5Ixl3lSLhg6LlEvArVoC1rBGGriNV/9h5rFwS+fVpJOvkUWvwuX6HPO7rfspDgA==", + "dependencies": { + "IKVM": "8.7.1" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0", + "System.ValueTuple": "4.5.0" + } + }, + "Microsoft.Extensions.FileProviders.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.FileProviders.Physical": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "UboiXxpPUpwulHvIAVE36Knq0VSHaAmfrFkegLyBZeaADuKezJ/AIXYAW8F5GBlGk/VaibN2k/Zn1ca8YAfVdA==", + "dependencies": { + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.FileSystemGlobbing": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.FileSystemGlobbing": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "OK+670i7esqlQrPjdIKRbsyMCe9g5kSLpRRQGSr4Q58AOYEe/hCnfLZprh7viNisSUUQZmMrbbuDaIrP+V1ebQ==" + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.NETFramework.ReferenceAssemblies.net48": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==" + }, + "Mono.Posix": { + "type": "Transitive", + "resolved": "7.1.0-final.1.21458.1", + "contentHash": "xhil/0zRkA2MrkyMZXC3dPSDWOhq6YD0vYGl1VnBbjsEZfLQCu7+mJZ/ftnOd0r4qmeHVeNuW6Pt33NoxO671A==", + "dependencies": { + "Mono.Unix": "7.1.0-final.1.21458.1" + } + }, + "Mono.Unix": { + "type": "Transitive", + "resolved": "7.1.0-final.1.21458.1", + "contentHash": "Rhxz4A7By8Q0wEgDqR+mioDsYXGrcYMYPiWE9bSaUKMpG8yAGArhetEQV5Ms6KhKCLdQTlPYLBKPZYoKbAvT/g==" + }, + "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" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "6.0.3", + "contentHash": "ryTgF+iFkpGZY1vRQhfCzX0xTdlV3pyaTTqRu2ETbEv+HlV7O6y7hyQURnghNIXvctl5DuZ//Dpks6HdL/Txgw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "6.0.6", + "contentHash": "GZ+62pLOr544jwSvyXv5ezSfzlFBTjLuPhgOS2dnKuknAA8dPNUGXLKTHf9XdsudU9JpbtweXnE4oEiKEB2T1Q==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "6.0.0", + "System.Threading.Tasks.Extensions": "4.5.4", + "System.ValueTuple": "4.5.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "Torch.Loader": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "HkuAujKa9IqPPqoA1205teUPBxuNOC9z0xZJkrMlFT0htH02X0ieZ5qh4onwyV10qVKiRBCSLkc5tA8lp1l5ig==" + }, + "YamlDotNet": { + "type": "Transitive", + "resolved": "12.1.0", + "contentHash": "iP6tzi3DJ16wyTtEClbG8W6epvc+MvKSBdNcpllzhal40C94WzjWxF2aszcoOSjbESsWXsLY+NoRewgfo8ah6Q==" + } + } + } +} \ No newline at end of file diff --git a/TorchPlugins.sln b/TorchPlugins.sln index c9a2615..2963931 100644 --- a/TorchPlugins.sln +++ b/TorchPlugins.sln @@ -27,6 +27,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{862C7244-2 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "heh", "heh\heh.csproj", "{927CB303-E699-4716-A62E-232AE1125159}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Maintenance", "Maintenance\Maintenance.csproj", "{66F3BF72-D663-44E0-B54C-5193E39839B9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuckPerms.Torch.Utils", "LuckPerms.Torch.Utils\LuckPerms.Torch.Utils.csproj", "{615FD610-A3E6-457F-B8B9-C9D1235A4B2C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +69,14 @@ Global {927CB303-E699-4716-A62E-232AE1125159}.Debug|Any CPU.Build.0 = Debug|Any CPU {927CB303-E699-4716-A62E-232AE1125159}.Release|Any CPU.ActiveCfg = Release|Any CPU {927CB303-E699-4716-A62E-232AE1125159}.Release|Any CPU.Build.0 = Release|Any CPU + {66F3BF72-D663-44E0-B54C-5193E39839B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66F3BF72-D663-44E0-B54C-5193E39839B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66F3BF72-D663-44E0-B54C-5193E39839B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66F3BF72-D663-44E0-B54C-5193E39839B9}.Release|Any CPU.Build.0 = Release|Any CPU + {615FD610-A3E6-457F-B8B9-C9D1235A4B2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {615FD610-A3E6-457F-B8B9-C9D1235A4B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {615FD610-A3E6-457F-B8B9-C9D1235A4B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {615FD610-A3E6-457F-B8B9-C9D1235A4B2C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {AD9B7D1E-386A-4EF2-B475-BCB770537035} = {06CD2354-307D-4A1C-B46B-1D9EB3AAE742} @@ -75,5 +87,7 @@ Global {B1A35416-6CFB-4AE7-A2F2-818E8F7A8C13} = {557A4A51-B8ED-4CA0-866D-D18D219129F3} {8F9D910F-FFE6-4010-921F-5872ACF638BB} = {557A4A51-B8ED-4CA0-866D-D18D219129F3} {927CB303-E699-4716-A62E-232AE1125159} = {862C7244-258E-4BFD-B271-9AA2D3FBE916} + {66F3BF72-D663-44E0-B54C-5193E39839B9} = {2C911BD8-8B11-460E-AB7E-16552949A6FC} + {615FD610-A3E6-457F-B8B9-C9D1235A4B2C} = {862C7244-258E-4BFD-B271-9AA2D3FBE916} EndGlobalSection EndGlobal diff --git a/heh/heh.csproj b/heh/heh.csproj index 3954188..4025015 100644 --- a/heh/heh.csproj +++ b/heh/heh.csproj @@ -16,6 +16,6 @@ - +