Decompiled source of ValheimRcon v1.4.1
ValheimRcon.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Splatform; using UnityEngine; using UnityEngine.Pool; using UnityEngine.Profiling; using ValheimRcon.Commands; using ValheimRcon.Commands.Modification; using ValheimRcon.Commands.Search; using ValheimRcon.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Valheim Rcon")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Valheim Rcon")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("43d6353e-ae3d-424e-8d9d-b274ab342a3e")] [assembly: AssemblyFileVersion("1.4.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.4.1.0")] [module: UnverifiableCode] internal static class ThreadingUtil { private class DisposableThread : IDisposable { private Thread _thread; internal DisposableThread(Thread thread) { _thread = thread; } public void Dispose() { _thread.Abort(); } } private class MainThreadDispatcher : MonoBehaviour { private static MainThreadDispatcher _instance; private ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>(); private ConcurrentQueue<IEnumerator> _coroutinesQueue = new ConcurrentQueue<IEnumerator>(); public static MainThreadDispatcher GetInstante() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("MainThreadDispatcher", new Type[1] { typeof(MainThreadDispatcher) }); Object.DontDestroyOnLoad((Object)val); _instance = val.GetComponent<MainThreadDispatcher>(); } return _instance; } public void AddAction(Action action) { _queue.Enqueue(action); } public void AddCoroutine(IEnumerator coroutine) { _coroutinesQueue.Enqueue(coroutine); } private void Update() { Action result; while (_queue.Count > 0 && _queue.TryDequeue(out result)) { result?.Invoke(); } IEnumerator result2; while (_coroutinesQueue.Count > 0 && _coroutinesQueue.TryDequeue(out result2)) { ((MonoBehaviour)this).StartCoroutine(result2); } } } [CompilerGenerated] private sealed class <DelayedActionCoroutine>d__6 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float delay; public Action action; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedActionCoroutine>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; action?.Invoke(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } internal static IDisposable RunPeriodical(Action action, int periodMilliseconds) { return new Timer(delegate { action?.Invoke(); }, null, 0, periodMilliseconds); } internal static IDisposable RunPeriodicalInSingleThread(Action action, int periodMilliseconds) { Thread thread = new Thread((ParameterizedThreadStart)delegate { while (true) { action?.Invoke(); Thread.Sleep(periodMilliseconds); } }); thread.Start(); return new DisposableThread(thread); } internal static void RunInMainThread(Action action) { MainThreadDispatcher.GetInstante().AddAction(action); } internal static void RunCoroutine(IEnumerator coroutine) { MainThreadDispatcher.GetInstante().AddCoroutine(coroutine); } internal static void RunDelayed(float delay, Action action) { MainThreadDispatcher.GetInstante().AddCoroutine(DelayedActionCoroutine(delay, action)); } internal static IDisposable RunThread(Action action) { Thread thread = new Thread(action.Invoke); thread.Start(); return new DisposableThread(thread); } [IteratorStateMachine(typeof(<DelayedActionCoroutine>d__6))] internal static IEnumerator DelayedActionCoroutine(float delay, Action action) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedActionCoroutine>d__6(0) { delay = delay, action = action }; } } namespace ValheimRcon { public interface IRconCommand { string Command { get; } string Description { get; } Task<CommandResult> HandleCommandAsync(CommandArgs args); } internal static class Discord { private static class FormUpload { internal class FileParameter { public byte[] File; public string FileName; public string ContentType; public FileParameter(byte[] file, string filename, string contenttype) { File = file; FileName = filename; ContentType = contenttype; } } private static readonly Encoding Encoding = Encoding.UTF8; public static HttpWebResponse MultipartFormDataPost(string postUrl, Dictionary<string, object> postParameters) { string text = $"----------{Guid.NewGuid():N}"; string contentType = "multipart/form-data; boundary=" + text; byte[] multipartFormData = GetMultipartFormData(postParameters, text); return PostForm(postUrl, contentType, multipartFormData); } private static HttpWebResponse PostForm(string postUrl, string contentType, byte[] formData) { if (!(WebRequest.Create(postUrl) is HttpWebRequest httpWebRequest)) { throw new ArgumentException("request is not a http request"); } httpWebRequest.Method = "POST"; httpWebRequest.ContentType = contentType; httpWebRequest.CookieContainer = new CookieContainer(); httpWebRequest.ContentLength = formData.Length; using (Stream stream = httpWebRequest.GetRequestStream()) { stream.Write(formData, 0, formData.Length); stream.Close(); } return httpWebRequest.GetResponse() as HttpWebResponse; } private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary) { MemoryStream memoryStream = new MemoryStream(); bool flag = false; foreach (KeyValuePair<string, object> postParameter in postParameters) { if (flag) { memoryStream.Write(Encoding.GetBytes("\r\n"), 0, Encoding.GetByteCount("\r\n")); } flag = true; if (postParameter.Value is FileParameter) { FileParameter fileParameter = (FileParameter)postParameter.Value; string s = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n", boundary, postParameter.Key, fileParameter.FileName ?? postParameter.Key, fileParameter.ContentType ?? "application/octet-stream"); memoryStream.Write(Encoding.GetBytes(s), 0, Encoding.GetByteCount(s)); memoryStream.Write(fileParameter.File, 0, fileParameter.File.Length); } else { string s2 = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{postParameter.Key}\"\r\n\r\n{postParameter.Value}"; memoryStream.Write(Encoding.GetBytes(s2), 0, Encoding.GetByteCount(s2)); } } string s3 = "\r\n--" + boundary + "--\r\n"; memoryStream.Write(Encoding.GetBytes(s3), 0, Encoding.GetByteCount(s3)); memoryStream.Position = 0L; byte[] array = new byte[memoryStream.Length]; memoryStream.Read(array, 0, array.Length); memoryStream.Close(); return array; } } internal static string Send(string mssgBody, string userName, string webhook) { Dictionary<string, object> postParameters = new Dictionary<string, object> { { "username", userName }, { "content", mssgBody } }; HttpWebResponse httpWebResponse = FormUpload.MultipartFormDataPost(webhook, postParameters); StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); streamReader.Close(); httpWebResponse.Close(); streamReader.Dispose(); httpWebResponse.Dispose(); return result; } internal static string SendFile(string mssgBody, string filename, string fileformat, string filepath, string userName, string webhook) { FileStream fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read); byte[] array = new byte[fileStream.Length]; fileStream.Read(array, 0, array.Length); fileStream.Close(); Dictionary<string, object> postParameters = new Dictionary<string, object> { { "filename", filename }, { "fileformat", fileformat }, { "file", new FormUpload.FileParameter(array, filename, "application/msexcel") }, { "username", userName }, { "content", mssgBody } }; HttpWebResponse httpWebResponse = FormUpload.MultipartFormDataPost(webhook, postParameters); string result = new StreamReader(httpWebResponse.GetResponseStream()).ReadToEnd(); httpWebResponse.Close(); fileStream.Dispose(); httpWebResponse.Dispose(); return result; } } internal class DiscordService : IDisposable { private struct Message { public string text; public string filePath; } private const string Name = "RCON"; private readonly IDisposable _thread; private readonly string _webhook; private readonly ConcurrentQueue<Message> _queue = new ConcurrentQueue<Message>(); public DiscordService(string webhook) { _webhook = webhook; _thread = ThreadingUtil.RunPeriodicalInSingleThread(SendQueuedMessage, 333); } public void SendResult(string text, string filePath) { _queue.Enqueue(new Message { filePath = filePath, text = text }); } private void SendQueuedMessage() { if (string.IsNullOrEmpty(_webhook) || !_queue.TryDequeue(out var result)) { return; } try { string filePath = result.filePath; if (string.IsNullOrEmpty(filePath)) { Discord.Send(result.text, "RCON", _webhook); } else { string fileName = Path.GetFileName(filePath); string extension = Path.GetExtension(filePath); Discord.SendFile(result.text, fileName, extension, filePath, "RCON", _webhook); } Log.Debug("Sent to discord " + result.text); } catch (Exception arg) { Log.Error($"Cannot send to discord {result.text}\n{arg}"); } } public void Dispose() { _thread.Dispose(); } } internal static class FormattingUtils { public static string ToDisplayFormat(this Vector3 vector) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({vector.x:0.##} {vector.y:0.##} {vector.z:0.##})"; } public static string ToDisplayFormat(this Quaternion quaternion) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return ((Quaternion)(ref quaternion)).eulerAngles.ToDisplayFormat(); } public static string ToDisplayFormat(this float value) { return $"{value:0.##}"; } } public static class Log { private static ManualLogSource _instance; public static void CreateInstance(ManualLogSource source) { _instance = source; } public static void Info(object msg) { _instance.LogInfo((object)FormatMessage(msg)); } public static void Message(object msg) { _instance.LogMessage((object)FormatMessage(msg)); } public static void Debug(object msg) { _instance.LogDebug((object)FormatMessage(msg)); } public static void Warning(object msg) { _instance.LogWarning((object)FormatMessage(msg)); } public static void Error(object msg) { _instance.LogError((object)FormatMessage(msg)); } public static void Fatal(object msg) { _instance.LogFatal((object)FormatMessage(msg)); } private static string FormatMessage(object msg) { return $"[{DateTime.UtcNow:G}] {msg}"; } } public static class PlayerUtils { public static string GetPlayerInfo(this ZNetPeer peer) { return peer.m_playerName + "(" + peer.GetSteamId() + ")"; } public static void InvokeRoutedRpcToZdo(this ZNetPeer peer, string rpc, params object[] args) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = peer.GetZDO(); ZRoutedRpc.instance.InvokeRoutedRPC(zDO.GetOwner(), zDO.m_uid, rpc, args); } public static ZDO GetZDO(this ZNetPeer peer) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return ZDOMan.instance.GetZDO(peer.m_characterID); } public static string GetSteamId(this ZNetPeer peer) { return peer.m_rpc.GetSocket().GetHostName(); } public static long GetPlayerId(this ZNetPeer peer) { ZDO zDO = peer.GetZDO(); if (zDO == null) { return 0L; } return zDO.GetLong(ZDOVars.s_playerID, 0L); } public static void WritePlayerInfo(this ZNetPeer peer, StringBuilder sb) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) sb.AppendFormat("{0} Steam ID:{1}", peer.m_playerName, peer.GetSteamId()); sb.AppendFormat(" Position: {0}({1})", peer.GetRefPos().ToDisplayFormat(), ZoneSystem.GetZone(peer.GetRefPos())); ZDO zDO = peer.GetZDO(); if (zDO != null) { sb.AppendFormat(" Player ID:{0}", peer.GetPlayerId()); sb.AppendFormat(" HP:{0}/{1}", zDO.GetFloat(ZDOVars.s_health, 0f).ToDisplayFormat(), zDO.GetFloat(ZDOVars.s_maxHealth, 0f).ToDisplayFormat()); } } } [BepInProcess("valheim_server.exe")] [BepInPlugin("org.tristan.rcon", "Valheim Rcon", "1.4.1")] public class Plugin : BaseUnityPlugin { [HarmonyPatch] private class Patches { [HarmonyFinalizer] [HarmonyPatch(typeof(ZNet), "UpdatePlayerList")] private static void ZNet_UpdatePlayerList(ZNet __instance) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) PlayerInfo val = default(PlayerInfo); if (!ZNet.TryGetPlayerByPlatformUserID(CommandsUserInfo.UserId, ref val) && __instance.m_players.Count != 0) { string value = ServerChatName.Value; val = default(PlayerInfo); val.m_name = value; val.m_userInfo = new CrossNetworkUserInfo { m_displayName = value, m_id = CommandsUserInfo.UserId }; val.m_serverAssignedDisplayName = value; PlayerInfo item = val; __instance.m_players.Add(item); } } } public const string Guid = "org.tristan.rcon"; public const string Name = "Valheim Rcon"; public const string Version = "1.4.1"; private const int MaxDiscordMessageLength = 1900; private const int TruncatedMessageLength = 200; public static ConfigEntry<string> DiscordUrl; public static ConfigEntry<string> Password; public static ConfigEntry<int> Port; public static ConfigEntry<string> ServerChatName; private DiscordService _discordService; private StringBuilder _builder = new StringBuilder(); public static readonly UserInfo CommandsUserInfo = new UserInfo { Name = string.Empty, UserId = new PlatformUserID("Bot", 0uL, false) }; private void Awake() { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown Log.CreateInstance(((BaseUnityPlugin)this).Logger); Port = ((BaseUnityPlugin)this).Config.Bind<int>("1. Rcon", "Port", 2458, "Port to receive RCON commands"); Password = ((BaseUnityPlugin)this).Config.Bind<string>("1. Rcon", "Password", System.Guid.NewGuid().ToString(), "Password for RCON packages validation"); DiscordUrl = ((BaseUnityPlugin)this).Config.Bind<string>("2. Discord", "Webhook url", "", "Discord webhook for sending command results"); ServerChatName = ((BaseUnityPlugin)this).Config.Bind<string>("3. Chat", "Server name", "Server", "Name of server to display messages sent with rcon command"); CommandsUserInfo.Name = ServerChatName.Value; _discordService = new DiscordService(DiscordUrl.Value); Object.DontDestroyOnLoad((Object)new GameObject("RconProxy", new Type[1] { typeof(RconProxy) })); RconProxy.Instance.OnCommandCompleted += SendResultToDiscord; RconCommandsUtil.RegisterAllCommands(Assembly.GetExecutingAssembly()); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); } private void OnDestroy() { _discordService.Dispose(); } private void SendResultToDiscord(IRconPeer peer, string command, IReadOnlyList<string> args, CommandResult result) { if (!string.IsNullOrEmpty(DiscordUrl.Value)) { string text = command + " " + string.Join(" ", args); _builder.Clear(); _builder.AppendLine("> " + peer.Endpoint + " -> " + text); if (_builder.Length > 1900) { string value = RconCommandsUtil.TruncateMessage(_builder.ToString(), 200); _builder.Clear(); _builder.Append(value); _builder.AppendLine("..."); } int num = 1900 - _builder.Length; if (result.Text.Length > num) { string value2 = RconCommandsUtil.TruncateMessage(result.Text, 200); _builder.AppendLine(value2); _builder.Append("*--- message truncated ---*"); _discordService.SendResult(_builder.ToString(), result.AttachedFilePath); string text2 = Path.Combine(Paths.CachePath, $"{DateTime.UtcNow.Ticks}.txt"); FileHelpers.EnsureDirectoryExists(text2); File.WriteAllText(text2, result.Text); _discordService.SendResult("**Full message**", text2); } else { _builder.Append(result.Text); _discordService.SendResult(_builder.ToString(), result.AttachedFilePath); } } } } public static class RconCommandsUtil { public static string TruncateMessage(string message, int maxLength) { if (message.Length <= maxLength) { return message; } return message.Substring(0, maxLength) + "..."; } public static string TruncateMessageByBytes(string message, int maxBytes) { if (string.IsNullOrEmpty(message)) { return message; } if (Encoding.UTF8.GetBytes(message).Length <= maxBytes) { return message; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; for (int i = 0; i < message.Length; i++) { int byteCount = Encoding.UTF8.GetByteCount(message[i].ToString()); if (num + byteCount > maxBytes) { break; } stringBuilder.Append(message[i]); num += byteCount; } return stringBuilder.ToString(); } public static void RegisterAllCommands(Assembly assembly) { if ((Object)(object)RconProxy.Instance == (Object)null) { Log.Error("RconProxy not initialized"); return; } Log.Info("Registering rcon commands..."); Type commandInterfaceType = typeof(IRconCommand); foreach (Type item in from t in assembly.GetTypes() where t != null where !t.IsAbstract && t.IsClass where t.GetConstructor(Type.EmptyTypes) != null where t.GetInterfaces().Contains(commandInterfaceType) where t.GetCustomAttribute<ExcludeAttribute>() == null select t) { RconProxy.Instance.RegisterCommand(item); } } } public class RconProxy : MonoBehaviour { internal delegate void CompletedCommandDelegate(IRconPeer peer, string command, IReadOnlyList<string> args, CommandResult result); [HarmonyPatch] private class Patches { [HarmonyFinalizer] [HarmonyPatch(typeof(ZNet), "LoadWorld")] private static void ZNet_LoadWorld() { Instance.Startup(); } [HarmonyPrefix] [HarmonyPatch(typeof(Game), "Shutdown")] private static void Game_Shutdown() { Instance.ShutDown(); } } private class ListCommand : RconCommand { public override string Command => "list"; public override string Description => "Prints list of available commands."; protected override string OnHandle(CommandArgs args) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Available commands:"); foreach (IRconCommand value in Instance._commands.Values) { stringBuilder.AppendLine(value.Command + " - " + value.Description); } return stringBuilder.ToString().Trim(); } } private RconCommandReceiver _receiver; private Dictionary<string, IRconCommand> _commands = new Dictionary<string, IRconCommand>(); private IRconConnectionManager _connectionManager; public static RconProxy Instance { get; private set; } internal event CompletedCommandDelegate OnCommandCompleted; private void Awake() { Instance = this; _connectionManager = new AsynchronousSocketListener(IPAddress.Any, Plugin.Port.Value); _receiver = new RconCommandReceiver(_connectionManager, Plugin.Password.Value, HandleCommandAsync); } internal void Startup() { _connectionManager.StartListening(); } internal void ShutDown() { _receiver.Dispose(); _connectionManager.Dispose(); } private void Update() { _receiver.Update(); } public void RegisterCommand<T>() where T : IRconCommand { RegisterCommand(typeof(T)); } public void RegisterCommand(Type type) { if (Activator.CreateInstance(type) is IRconCommand rconCommand && !string.IsNullOrEmpty(rconCommand.Command)) { RegisterCommand(rconCommand); } } public void RegisterCommand(IRconCommand command) { if (_commands.TryGetValue(command.Command, out var _)) { Log.Error("Duplicated commands " + command.Command + "\n" + command.GetType().Name + "\n" + command.GetType().Name); } _commands[command.Command] = command; Log.Info("Registered command " + command.Command + " -> " + command.GetType().Name); } public void RegisterCommand(string command, string description, Func<CommandArgs, CommandResult> commandFunc) { RegisterCommand(new ActionCommand(command, description, commandFunc)); } private async Task<string> HandleCommandAsync(IRconPeer peer, string command, IReadOnlyList<string> args) { TaskCompletionSource<CommandResult> completionSource = new TaskCompletionSource<CommandResult>(); ThreadingUtil.RunInMainThread(delegate { RunCommand(command, args, completionSource); }); CommandResult result = await completionSource.Task; Log.Message("Command completed: " + command + "\n" + result.Text); this.OnCommandCompleted?.Invoke(peer, command, args, result); return result.Text; } private async void RunCommand(string commandName, IReadOnlyList<string> args, TaskCompletionSource<CommandResult> resultSource) { try { if (!_commands.TryGetValue(commandName, out var value)) { resultSource.TrySetResult(new CommandResult { Text = "Unknown command " + commandName }); } else { resultSource.TrySetResult(await value.HandleCommandAsync(new CommandArgs(args))); } } catch (Exception ex) { resultSource.TrySetResult(new CommandResult { Text = ex.Message }); } } } public static class ZdoUtils { [Flags] public enum Type { None = 0, ItemDrop = 1, GuardStone = 2, Character = 4, Building = 8, ItemStand = 0x10, Destructible = 0x20, Interactable = 0x40 } [CompilerGenerated] private sealed class <GetPermittedPlayers>d__20 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IDisposable, IEnumerator { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private ZDO zdo; public ZDO <>3__zdo; private int <count>5__2; private int <i>5__3; string IEnumerator<string>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetPermittedPlayers>d__20(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <count>5__2 = zdo.GetInt(ZDOVars.s_permitted, 0); if (<count>5__2 <= 0) { return false; } <i>5__3 = 0; break; case 1: <>1__state = -1; <i>5__3++; break; } if (<i>5__3 < <count>5__2) { long @long = zdo.GetLong($"pu_id{<i>5__3}", 0L); string @string = zdo.GetString($"pu_name{<i>5__3}", ""); <>2__current = $"{@long}({@string})"; <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<string> IEnumerable<string>.GetEnumerator() { <GetPermittedPlayers>d__20 <GetPermittedPlayers>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <GetPermittedPlayers>d__ = this; } else { <GetPermittedPlayers>d__ = new <GetPermittedPlayers>d__20(0); } <GetPermittedPlayers>d__.zdo = <>3__zdo; return <GetPermittedPlayers>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<string>)this).GetEnumerator(); } } private static readonly Dictionary<int, Type> PrefabTypes = new Dictionary<int, Type>(); private static readonly Dictionary<int, float> MaxHealth = new Dictionary<int, float>(); private static readonly Dictionary<int, float> MaxSupport = new Dictionary<int, float>(); private static readonly int TagZdoHash = StringExtensionMethods.GetStableHashCode("valheim_rcon_object_tag"); public static string GetTag(this ZDO zdo) { return zdo.GetString(TagZdoHash, ""); } public static void SetTag(this ZDO zdo, string tag) { zdo.Set(TagZdoHash, tag); } public static void SetZdoModified(this ZDO zdo) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) zdo.SetOwner(ZDOMan.GetSessionID()); zdo.DataRevision += 100; ZDOMan.instance.ForceSendZDO(zdo.m_uid); } public static void AppendZdoStats(ZDO zdo, StringBuilder stringBuilder) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) stringBuilder.Append($" Id: {((ZDOID)(ref zdo.m_uid)).ID}:{((ZDOID)(ref zdo.m_uid)).UserID}"); stringBuilder.Append($" Position: {zdo.GetPosition().ToDisplayFormat()}({ZoneSystem.GetZone(zdo.GetPosition())})"); stringBuilder.Append(" Rotation: " + zdo.GetRotation().ToDisplayFormat()); string tag = zdo.GetTag(); if (!string.IsNullOrEmpty(tag)) { stringBuilder.Append(" Tag: " + tag); } zdo.GetPrefab(); TryAppendItemDropData(zdo, stringBuilder); TryAppendBuildingData(zdo, stringBuilder); TryAppendCharacterData(zdo, stringBuilder); TryAppendGuardStoneData(zdo, stringBuilder); TryAppendItemStandData(zdo, stringBuilder); if (!zdo.Persistent) { stringBuilder.Append(" [NOT PERSISTENT]"); } } public static string GetPrefabName(int prefabId) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabId); if (!((Object)(object)prefab != (Object)null)) { return "Unknown"; } return ((Object)prefab).name; } public static string GetPrefabName(this ZDO zdo) { return GetPrefabName(zdo.GetPrefab()); } public static void DeleteZDO(ZDO zdo) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (zdo.Persistent) { zdo.SetOwner(ZDOMan.GetSessionID()); ZDOID connectionZDOID = zdo.GetConnectionZDOID((ConnectionType)3); if (connectionZDOID != ZDOID.None && ZDOMan.instance.m_objectsByID.TryGetValue(connectionZDOID, out var value) && value != zdo) { DeleteZDO(value); } ZDOMan.instance.DestroyZDO(zdo); } } public static bool CanModifyZdo(ZDO zdo) { if (!zdo.IsValid()) { return false; } if (ZNet.instance.m_peers.Any((ZNetPeer p) => p.m_characterID == zdo.m_uid)) { return false; } if (GetPrefabName(zdo.GetPrefab()).StartsWith("_")) { return false; } if (GetPrefabTypes(zdo.GetPrefab()) == Type.None) { return false; } return true; } private static void TryAppendItemStandData(ZDO zdo, StringBuilder stringBuilder) { if (!CheckPrefabType(zdo.GetPrefab(), Type.ItemStand)) { return; } string @string = zdo.GetString(ZDOVars.s_item, ""); if (!string.IsNullOrEmpty(@string)) { stringBuilder.Append(" Attached item: " + @string); stringBuilder.Append($" Durability: {zdo.GetFloat(ZDOVars.s_durability, 0f)}"); stringBuilder.Append($" Stack: {zdo.GetInt(ZDOVars.s_stack, 0)}"); stringBuilder.Append($" Quality: {zdo.GetInt(ZDOVars.s_quality, 0)}"); stringBuilder.Append($" Variant: {zdo.GetInt(ZDOVars.s_variant, 0)}"); stringBuilder.Append(string.Format(" Crafter: {0} ({1})", zdo.GetString(ZDOVars.s_crafterName, ""), zdo.GetLong(ZDOVars.s_crafterID, 0L))); int @int = zdo.GetInt(ZDOVars.s_dataCount, 0); if (@int > 0) { stringBuilder.Append(" Data:"); } for (int i = 0; i < @int; i++) { stringBuilder.Append(" '" + zdo.GetString($"data_{i}", "") + "'='" + zdo.GetString($"data__{i}", "") + "'"); } } } private static void TryAppendItemDropData(ZDO zdo, StringBuilder stringBuilder) { if (CheckPrefabType(zdo.GetPrefab(), Type.ItemDrop)) { stringBuilder.Append(" Durability: " + zdo.GetFloat(ZDOVars.s_durability, 0f).ToDisplayFormat()); stringBuilder.Append($" Stack: {zdo.GetInt(ZDOVars.s_stack, 0)}"); stringBuilder.Append($" Quality: {zdo.GetInt(ZDOVars.s_quality, 0)}"); stringBuilder.Append($" Variant: {zdo.GetInt(ZDOVars.s_variant, 0)}"); stringBuilder.Append(string.Format(" Crafter: {0} ({1})", zdo.GetString(ZDOVars.s_crafterName, ""), zdo.GetLong(ZDOVars.s_crafterID, 0L))); stringBuilder.Append($" WorldLevel: {zdo.GetInt(ZDOVars.s_worldLevel, 0)}"); stringBuilder.Append($" PickedUp: {zdo.GetBool(ZDOVars.s_pickedUp, false)}"); int @int = zdo.GetInt(ZDOVars.s_dataCount, 0); if (@int > 0) { stringBuilder.Append(" Data:"); } for (int i = 0; i < @int; i++) { stringBuilder.Append(" '" + zdo.GetString($"data_{i}", "") + "'='" + zdo.GetString($"data__{i}", "") + "'"); } } } private static void TryAppendGuardStoneData(ZDO zdo, StringBuilder stringBuilder) { if (!CheckPrefabType(zdo.GetPrefab(), Type.GuardStone)) { return; } stringBuilder.Append($" Enabled: {zdo.GetBool(ZDOVars.s_enabled, false)}"); stringBuilder.Append(" Owner: " + zdo.GetString(ZDOVars.s_creatorName, "")); stringBuilder.Append(" Permitted:"); foreach (string permittedPlayer in GetPermittedPlayers(zdo)) { stringBuilder.Append(" " + permittedPlayer); } } private static void TryAppendCharacterData(ZDO zdo, StringBuilder stringBuilder) { if (CheckPrefabType(zdo.GetPrefab(), Type.Character)) { stringBuilder.Append($" Level: {zdo.GetInt(ZDOVars.s_level, 0)}"); float @float = zdo.GetFloat(ZDOVars.s_maxHealth, 0f); stringBuilder.Append(" Health: " + zdo.GetFloat(ZDOVars.s_health, @float).ToDisplayFormat() + "/" + @float.ToDisplayFormat()); stringBuilder.Append($" Tamed: {zdo.GetBool(ZDOVars.s_tamed, false)}"); } } private static void TryAppendBuildingData(ZDO zdo, StringBuilder stringBuilder) { if (CheckPrefabType(zdo.GetPrefab(), Type.Building)) { stringBuilder.Append($" Creator: {zdo.GetLong(ZDOVars.s_creator, 0L)}"); float value; float num = (MaxHealth.TryGetValue(zdo.GetPrefab(), out value) ? value : 0f); float value2; float num2 = (MaxSupport.TryGetValue(zdo.GetPrefab(), out value2) ? value2 : 0f); stringBuilder.Append(" Health: " + zdo.GetFloat(ZDOVars.s_health, num).ToDisplayFormat()); stringBuilder.Append(" Support: " + zdo.GetFloat(ZDOVars.s_support, num2).ToDisplayFormat()); } } public static bool CheckPrefabType(int prefabId, Type type) { if (!ZNetScene.instance.HasPrefab(prefabId)) { return false; } return (GetPrefabTypes(prefabId) & type) != 0; } private static Type GetPrefabTypes(int prefabId) { if (!ZNetScene.instance.HasPrefab(prefabId)) { return Type.None; } if (!PrefabTypes.TryGetValue(prefabId, out var value)) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabId); ItemDrop val = default(ItemDrop); if (prefab.TryGetComponent<ItemDrop>(ref val)) { value |= Type.ItemDrop; } Character val2 = default(Character); if (prefab.TryGetComponent<Character>(ref val2)) { value |= Type.Character; } WearNTear val3 = default(WearNTear); if (prefab.TryGetComponent<WearNTear>(ref val3)) { value |= Type.Building; MaxHealth[prefabId] = val3.m_health; MaxSupport[prefabId] = val3.GetMaxSupport(); } PrivateArea val4 = default(PrivateArea); if (prefab.TryGetComponent<PrivateArea>(ref val4)) { value |= Type.GuardStone; } ItemStand val5 = default(ItemStand); if (prefab.TryGetComponent<ItemStand>(ref val5)) { value |= Type.ItemStand; } IDestructible val6 = default(IDestructible); if (prefab.TryGetComponent<IDestructible>(ref val6)) { value |= Type.Destructible; } Interactable val7 = default(Interactable); if (prefab.TryGetComponent<Interactable>(ref val7)) { value |= Type.Interactable; } PrefabTypes[prefabId] = value; } return value; } [IteratorStateMachine(typeof(<GetPermittedPlayers>d__20))] private static IEnumerable<string> GetPermittedPlayers(ZDO zdo) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <GetPermittedPlayers>d__20(-2) { <>3__zdo = zdo }; } } } namespace ValheimRcon.Core { public sealed class AsynchronousSocketListener : IRconConnectionManager, IDisposable { private static readonly TimeSpan UnauthorizedClientLifetime = TimeSpan.FromSeconds(30.0); private readonly IPAddress _address; private readonly int _port; private readonly Socket _listener; private readonly HashSet<IRconPeer> _clients = new HashSet<IRconPeer>(); private readonly HashSet<IRconPeer> _waitingForDisconnect = new HashSet<IRconPeer>(); private readonly List<IRconPeer> _clientsSnapshot = new List<IRconPeer>(); private readonly object _clientsLock = new object(); private readonly object _disconnectLock = new object(); private IDisposable _acceptThread; public event Action<IRconPeer, RconPacket> OnMessage; public AsynchronousSocketListener(IPAddress ipAddress, int port) { if (ipAddress == null) { throw new ArgumentNullException("ipAddress"); } if (port < 1 || port > 65535) { throw new ArgumentOutOfRangeException("port", "Port must be between 1 and 65535"); } _address = ipAddress; _port = port; _listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); } public void StartListening() { Log.Message("Start listening rcon commands"); try { IPEndPoint localEP = new IPEndPoint(_address, _port); _listener.Bind(localEP); _listener.Listen(100); _acceptThread = ThreadingUtil.RunPeriodicalInSingleThread(TryAcceptClientThread, 100); } catch (Exception msg) { Log.Error(msg); } } public void Update() { lock (_clientsLock) { _clientsSnapshot.Clear(); _clientsSnapshot.AddRange(_clients); } foreach (IRconPeer item in _clientsSnapshot) { RconPacket packet; if (!item.IsConnected()) { Disconnect(item); } else if (IsUnauthorizedTimeout(item)) { Log.Warning("Unauthorized timeout [" + item.Endpoint + "]"); Disconnect(item); } else if (item.TryReceive(out packet)) { this.OnMessage?.Invoke(item, packet); } } lock (_disconnectLock) { foreach (IRconPeer item2 in _waitingForDisconnect) { lock (_clientsLock) { _clients.Remove(item2); } DisconnectPeer(item2); } _waitingForDisconnect.Clear(); } } public void Dispose() { _listener.Close(); _acceptThread?.Dispose(); lock (_clientsLock) { foreach (IRconPeer client in _clients) { client.Dispose(); } _clients.Clear(); } lock (_disconnectLock) { _waitingForDisconnect.Clear(); } } public void Disconnect(IRconPeer peer) { lock (_disconnectLock) { _waitingForDisconnect.Add(peer); } } private void TryAcceptClientThread() { try { if (_listener.Poll(0, SelectMode.SelectRead)) { Socket socket = _listener.Accept(); OnClientConnected(socket); } } catch (Exception msg) { Log.Error(msg); } } private void OnClientConnected(Socket socket) { RconPeer state = new RconPeer(socket); Log.Debug("Client connected [" + state.Endpoint + "]"); ThreadingUtil.RunInMainThread(delegate { lock (_clientsLock) { _clients.Add(state); } }); } private void DisconnectPeer(IRconPeer peer) { Log.Debug("Client disconnected [" + peer.Endpoint + "]"); try { peer.Dispose(); } catch { Log.Debug("Warning: Could not dispose peer connection"); } } private static bool IsUnauthorizedTimeout(IRconPeer peer) { if (!peer.Authentificated) { return DateTime.Now - peer.Created > UnauthorizedClientLifetime; } return false; } } public interface IRconConnectionManager : IDisposable { event Action<IRconPeer, RconPacket> OnMessage; void StartListening(); void Update(); void Disconnect(IRconPeer peer); } public interface IRconPeer : IDisposable { bool Authentificated { get; } string Endpoint { get; } DateTime Created { get; } void SetAuthentificated(bool authentificated); Task SendAsync(RconPacket packet); bool IsConnected(); bool TryReceive(out RconPacket packet); } public enum PacketType { Error = 0, Command = 2, Login = 3 } public delegate Task<string> RconCommandHandler(IRconPeer peer, string command, IReadOnlyList<string> data); public class RconCommandReceiver : IDisposable { private static readonly Regex MatchRegex = new Regex("(?<=[ ][\\\"]|^[\\\"])[^\\\"]+(?=[\\\"][ ]|[\\\"]$)|(?<=[ ]|^)[^\\\" ]+(?=[ ]|$)", RegexOptions.Compiled | RegexOptions.CultureInvariant); private readonly IRconConnectionManager _manager; private readonly string _password; private readonly RconCommandHandler _commandHandler; public RconCommandReceiver(IRconConnectionManager connectionManager, string password, RconCommandHandler commandHandler) { if (string.IsNullOrEmpty(password)) { throw new ArgumentException("Password cannot be null or empty", "password"); } if (commandHandler == null) { throw new ArgumentNullException("commandHandler"); } _password = password; _manager = connectionManager; _manager.OnMessage += SocketListener_OnMessage; _commandHandler = commandHandler; } public void Update() { _manager.Update(); } public void Dispose() { _manager.OnMessage -= SocketListener_OnMessage; } private async void SocketListener_OnMessage(IRconPeer peer, RconPacket packet) { switch (packet.type) { case PacketType.Login: { if (peer.Authentificated) { Log.Error("Already authorized [" + peer.Endpoint + "]"); await peer.SendAsync(new RconPacket(packet.requestId, PacketType.Command, "Already authorized")); _manager.Disconnect(peer); break; } bool success = string.Equals(packet.payload?.Trim() ?? string.Empty, _password); RconPacket rconPacket2; if (success) { peer.SetAuthentificated(authentificated: true); rconPacket2 = new RconPacket(packet.requestId, PacketType.Command, "Logic success"); } else { rconPacket2 = new RconPacket(-1, PacketType.Command, "Login failed"); } Log.Debug($"Login result {rconPacket2}"); await peer.SendAsync(rconPacket2); if (!success) { _manager.Disconnect(peer); } break; } case PacketType.Command: { if (!peer.Authentificated) { Log.Warning("Not authorized [" + peer.Endpoint + "]"); await peer.SendAsync(new RconPacket(packet.requestId, packet.type, "Unauthorized")); _manager.Disconnect(peer); break; } string input = packet.payload?.TrimStart(new char[1] { '/' }) ?? string.Empty; List<string> list = (from Match m in MatchRegex.Matches(input) select m.Value).ToList(); if (list.Count == 0) { Log.Warning("Empty command from [" + peer.Endpoint + "]"); await peer.SendAsync(new RconPacket(packet.requestId, packet.type, "Empty command")); break; } string command = list[0]; list.RemoveAt(0); string payload = ValidatePayloadLength(await _commandHandler(peer, command, list)); RconPacket rconPacket = new RconPacket(packet.requestId, packet.type, payload); Log.Debug($"Command result {command} - {rconPacket}"); await peer.SendAsync(rconPacket); break; } default: Log.Error($"Unknown packet type: {packet} [{peer.Endpoint}]"); await peer.SendAsync(new RconPacket(packet.requestId, PacketType.Error, "Cannot handle command")); _manager.Disconnect(peer); break; } } private string ValidatePayloadLength(string payload) { if (RconPacket.GetPayloadSize(payload) > 4050) { int maxBytes = 4050 - RconPacket.GetPayloadSize("\n--- message truncated ---"); return RconCommandsUtil.TruncateMessageByBytes(payload, maxBytes) + "\n--- message truncated ---"; } return payload; } } public readonly struct RconPacket { public const int MaxPayloadSize = 4050; private const int MaxPacketLength = 4096; public readonly int requestId; public readonly PacketType type; public readonly string payload; public RconPacket(byte[] bytes) { if (bytes == null) { throw new ArgumentNullException("bytes"); } if (bytes.Length < 14) { throw new ArgumentException($"Packet too small - {bytes.Length} bytes", "bytes"); } if (bytes.Length > 65536) { throw new ArgumentException($"Packet too large - {bytes.Length} bytes", "bytes"); } using MemoryStream input = new MemoryStream(bytes); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num < 0) { throw new ArgumentException($"Invalid packet length - {num}", "bytes"); } if (num < 10) { throw new ArgumentException($"Packet data too small - {num}", "bytes"); } if (num > 2147483643 || num + 4 > bytes.Length) { throw new ArgumentException($"Packet length exceeds buffer size - {num}", "bytes"); } requestId = binaryReader.ReadInt32(); type = (PacketType)binaryReader.ReadInt32(); if (!Enum.IsDefined(typeof(PacketType), type)) { throw new ArgumentException($"Invalid packet type {type}", "bytes"); } int num2 = num - 10; if (num2 < 0) { throw new ArgumentException($"Invalid payload size - {num2} bytes", "bytes"); } if (num2 > 4050) { throw new ArgumentException($"Payload too large - {num2} bytes", "bytes"); } byte[] bytes2 = binaryReader.ReadBytes(num2); payload = Encoding.UTF8.GetString(bytes2); } public RconPacket(int requestId, PacketType type, string payload) { payload = payload ?? string.Empty; int payloadSize = GetPayloadSize(payload); if (payloadSize > 4050) { throw new ArgumentException($"Payload too large - {payloadSize} bytes", "payload"); } this.requestId = requestId; this.type = type; this.payload = payload; } public byte[] Serialize() { byte[] bytes = Encoding.UTF8.GetBytes(payload ?? string.Empty); if (bytes.Length > 4096) { throw new InvalidOperationException("Payload too large for serialization"); } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); long position = memoryStream.Position; binaryWriter.Write(0); binaryWriter.Write(requestId); binaryWriter.Write((int)type); binaryWriter.Write(bytes); binaryWriter.Write((byte)0); binaryWriter.Write((byte)0); long num = memoryStream.Position - position - 4; if (num > 4096) { throw new InvalidOperationException($"Packet too large for serialization - {num}"); } int value = (int)num; memoryStream.Position = position; binaryWriter.Write(value); return memoryStream.ToArray(); } public static int GetPayloadSize(string payload) { if (payload == null) { return 0; } return Encoding.UTF8.GetByteCount(payload); } public override string ToString() { if (type == PacketType.Login) { return $"[{requestId} t:{type} ****]"; } return $"[{requestId} t:{type} {payload}]"; } } public class RconPeer : IRconPeer, IDisposable { public const int BufferSize = 4096; private bool _disposed; private readonly byte[] _buffer = new byte[4096]; private readonly Socket _socket; public DateTime Created { get; } public bool Authentificated { get; private set; } public string Endpoint { get { if (_disposed) { return "unknown"; } try { return _socket?.RemoteEndPoint?.ToString() ?? string.Empty; } catch { return "unknown"; } } } public async Task SendAsync(RconPacket packet) { if (_disposed) { Log.Debug("Warning: Attempted to send to disposed peer"); return; } if (_socket == null || !_socket.Connected) { Log.Debug("Warning: Socket is null or not connected"); return; } byte[] array = packet.Serialize(); Log.Debug($"Sent {await SocketTaskExtensions.SendAsync(_socket, new ArraySegment<byte>(array), SocketFlags.None)} bytes to client [{Endpoint}]"); } public bool IsConnected() { if (_disposed) { return false; } if (_socket.Connected) { if (_socket.Poll(0, SelectMode.SelectRead)) { return _socket.Available != 0; } return true; } return false; } public bool TryReceive(out RconPacket packet) { packet = default(RconPacket); if (_disposed) { return false; } if (_socket.Poll(0, SelectMode.SelectRead) && _socket.Available > 0) { int available = _socket.Available; if (available > _buffer.Length) { Log.Warning($"Available data exceeds buffer size: {available} > {_buffer.Length} [{Endpoint}]"); return false; } if (_socket.Receive(_buffer, 0, Math.Min(available, _buffer.Length), SocketFlags.None) == 0) { return false; } try { packet = new RconPacket(_buffer); Log.Debug($"Received package {packet} from [{Endpoint}]"); return true; } catch (Exception ex) { Log.Warning("Failed to parse packet from [" + Endpoint + "]: " + ex.Message); return false; } finally { Array.Clear(_buffer, 0, _buffer.Length); } } return false; } public RconPeer(Socket workSocket) { if (workSocket == null) { throw new ArgumentNullException("workSocket"); } _socket = workSocket; Created = DateTime.Now; } public void SetAuthentificated(bool authentificated) { Authentificated = authentificated; } public void Dispose() { if (!_disposed) { _disposed = true; try { _socket?.Shutdown(SocketShutdown.Both); } catch { } try { _socket?.Close(); } catch { } _socket.Dispose(); } } } } namespace ValheimRcon.Commands { internal class ActionCommand : IRconCommand { private readonly Func<CommandArgs, CommandResult> _execute; public string Command { get; } public string Description { get; } public ActionCommand(string command, string description, Func<CommandArgs, CommandResult> execute) { Command = command; _execute = execute; Description = description; } public Task<CommandResult> HandleCommandAsync(CommandArgs args) { return Task.FromResult(_execute(args)); } } internal class AddAdmin : RconCommand { public override string Command => "addAdmin"; public override string Description => "Adds a player to the admin list. Usage: addAdmin <steamId>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.m_adminList.Add(@string); return @string + " is admin now"; } } internal class AddPermitted : RconCommand { public override string Command => "addPermitted"; public override string Description => "Adds a player to the permitted list. Usage: addPermitted <steamId>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.m_permittedList.Add(@string); return @string + " added to permitted"; } } internal class Ban : RconCommand { public override string Command => "ban"; public override string Description => "Ban a user from the server. Usage: ban <playername or steamid>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.Ban(@string); return "Banned " + @string; } } internal class BanSteamId : RconCommand { public override string Command => "banSteamId"; public override string Description => "Ban a player by their Steam ID. Usage: banSteamId <steamId>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.m_bannedList.Add(@string); return @string + " banned"; } } public class CommandArgs { [CompilerGenerated] private sealed class <GetOptionalArguments>d__17 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable, IEnumerator { private int <>1__state; private int <>2__current; private int <>l__initialThreadId; public CommandArgs <>4__this; private int <i>5__2; int IEnumerator<int>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetOptionalArguments>d__17(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; CommandArgs commandArgs = <>4__this; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_0060; } <>1__state = -1; <i>5__2 = 0; goto IL_0070; IL_0060: <i>5__2++; goto IL_0070; IL_0070: if (<i>5__2 < commandArgs.Arguments.Count) { if (OptionalArgumentRegex.IsMatch(commandArgs.Arguments[<i>5__2])) { <>2__current = <i>5__2; <>1__state = 1; return true; } goto IL_0060; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<int> IEnumerable<int>.GetEnumerator() { <GetOptionalArguments>d__17 result; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; result = this; } else { result = new <GetOptionalArguments>d__17(0) { <>4__this = <>4__this }; } return result; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<int>)this).GetEnumerator(); } } private static readonly Regex OptionalArgumentRegex = new Regex("^-[A-Za-z]+$"); public IReadOnlyList<string> Arguments { get; } public CommandArgs(IReadOnlyList<string> args) { Arguments = args; } public int GetInt(int index) { ValidateIndex(index); if (!int.TryParse(Arguments[index], out var result)) { throw new ArgumentException($"Argument at {index} is invalid"); } return result; } public int TryGetInt(int index, int defaultValue = 0) { if (!HasArgument(index)) { return defaultValue; } return GetInt(index); } public long GetLong(int index) { ValidateIndex(index); if (!long.TryParse(Arguments[index], out var result)) { throw new ArgumentException($"Argument at {index} is invalid"); } return result; } public long TryGetLong(int index, long defaultValue) { if (!HasArgument(index)) { return defaultValue; } return GetLong(index); } public float GetFloat(int index) { ValidateIndex(index); if (!float.TryParse(Arguments[index], out var result)) { throw new ArgumentException($"Argument at {index} is invalid"); } return result; } public float TryGetFloat(int index, float defaultValue) { if (!HasArgument(index)) { return defaultValue; } return GetFloat(index); } public string GetString(int index) { ValidateIndex(index); return Arguments[index]; } public string TryGetString(int index, string defaultValue = "") { if (!HasArgument(index)) { return defaultValue; } return GetString(index); } public uint GetUInt(int index) { ValidateIndex(index); if (!uint.TryParse(Arguments[index], out var result)) { throw new ArgumentException($"Argument at {index} is invalid"); } return result; } public uint TryGetUInt(int index, uint defaultValue = 0u) { if (!HasArgument(index)) { return defaultValue; } return GetUInt(index); } private void ValidateIndex(int index) { if (HasArgument(index)) { return; } throw new ArgumentException($"Cannot get argument at {index}"); } private bool HasArgument(int index) { if (index >= 0) { return index < Arguments.Count; } return false; } [IteratorStateMachine(typeof(<GetOptionalArguments>d__17))] public IEnumerable<int> GetOptionalArguments() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <GetOptionalArguments>d__17(-2) { <>4__this = this }; } public override string ToString() { return string.Join(" ", Arguments); } } public static class CommandArgsExtensions { public static Vector3 GetVector3(this CommandArgs args, int index) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) return new Vector3(args.GetFloat(index), args.GetFloat(index + 1), args.GetFloat(index + 2)); } public static ObjectId GetObjectId(this CommandArgs args, int index) { string @string = args.GetString(index); string[] array = @string.Split(new char[1] { ':' }); if (array.Length == 2 && uint.TryParse(array[0], out var result) && long.TryParse(array[1], out var result2)) { return new ObjectId(result, result2); } throw new ArgumentException("Cannot parse " + @string + " as object id (expected format is ID:User)"); } } public struct CommandResult { public string Text; public string AttachedFilePath; public static CommandResult WithText(string text) { CommandResult result = default(CommandResult); result.Text = text; return result; } } internal class DeleteObjects : RconCommand { private readonly List<ISearchCriteria> _criterias = new List<ISearchCriteria>(); public override string Command => "deleteObjects"; public override string Description => "Delete objects matching all search criteria. Usage (with optional arguments): deleteObjects -creator <creator id> -id <id:userid> -tag <tag> -force (bypass security checks)"; protected override string OnHandle(CommandArgs args) { IEnumerable<int> optionalArguments = args.GetOptionalArguments(); bool flag = false; _criterias.Clear(); foreach (int item in optionalArguments) { string @string = args.GetString(item); switch (@string.ToLower()) { case "-creator": _criterias.Add(new CreatorCriteria(args.GetLong(item + 1))); break; case "-id": _criterias.Add(new IdCriteria(args.GetObjectId(item + 1))); break; case "-tag": _criterias.Add(new TagCriteria(args.GetString(item + 1))); break; case "-force": flag = true; break; default: return "Unknown argument: " + @string; } } if (!_criterias.Any()) { return "At least one criteria must be provided."; } ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => _criterias.All((ISearchCriteria c) => c.IsMatch(zdo))).ToArray(); if (array.Length == 0) { return "No objects found matching the provided criteria."; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Deleting {array.Length} objects:"); ZDO[] array2 = array; foreach (ZDO val in array2) { string prefabName = ZdoUtils.GetPrefabName(val.GetPrefab()); stringBuilder.Append("- Prefab: " + prefabName); ZdoUtils.AppendZdoStats(val, stringBuilder); if (!val.Persistent) { stringBuilder.AppendLine(" [NOT ALLOWED TO DELETE]"); } else if (flag || ZdoUtils.CanModifyZdo(val)) { ZdoUtils.DeleteZDO(val); stringBuilder.AppendLine(" [DELETED]"); } else { stringBuilder.AppendLine(" [NOT ALLOWED TO DELETE]"); } stringBuilder.AppendLine(); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } } internal class ExcludeAttribute : Attribute { } internal class FindObjects : RconCommand { private readonly List<ISearchCriteria> _criterias = new List<ISearchCriteria>(); public override string Command => "findObjects"; public override string Description => "Find objects matching all search criteria. Usage (with optional arguments): findObjects -near <x> <y> <z> <radius> -prefab <prefab> -creator <creator id> -id <id:userid> -tag <tag> -tag-old <tag>"; protected override string OnHandle(CommandArgs args) { //IL_0127: Unknown result type (might be due to invalid IL or missing references) IEnumerable<int> optionalArguments = args.GetOptionalArguments(); _criterias.Clear(); foreach (int item in optionalArguments) { string @string = args.GetString(item); switch (@string.ToLower()) { case "-prefab": _criterias.Add(new PrefabCriteria(args.GetString(item + 1))); break; case "-creator": _criterias.Add(new CreatorCriteria(args.GetLong(item + 1))); break; case "-id": _criterias.Add(new IdCriteria(args.GetObjectId(item + 1))); break; case "-tag": _criterias.Add(new TagCriteria(args.GetString(item + 1))); break; case "-near": _criterias.Add(new NearCriteria(args.GetVector3(item + 1), args.GetFloat(item + 4))); break; case "-tag-old": _criterias.Add(new OldTagCriteria(args.GetString(item + 1))); break; default: return "Unknown argument: " + @string; } } if (!_criterias.Any()) { return "At least one criteria must be provided."; } ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => _criterias.All((ISearchCriteria c) => c.IsMatch(zdo))).ToArray(); if (array.Length == 0) { return "No objects found matching the provided criteria."; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Found {array.Length} objects:"); ZDO[] array2 = array; foreach (ZDO obj in array2) { string prefabName = ZdoUtils.GetPrefabName(obj.GetPrefab()); stringBuilder.Append("- Prefab: " + prefabName); ZdoUtils.AppendZdoStats(obj, stringBuilder); stringBuilder.AppendLine(); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } } internal class ModifyObject : RconCommand { private readonly List<IZdoModification> _modifications = new List<IZdoModification>(); private readonly StringBuilder builder = new StringBuilder(); public override string Command => "modifyObject"; public override string Description => "Modify properties of an object. Usage (with required and optional arguments): modifyObject <id:userid> -position <x> <y> <z> -rotation <x> <y> <z> -health <value> -tag <tag> -force (bypass security checks)"; protected override string OnHandle(CommandArgs args) { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) ObjectId objectId = args.GetObjectId(0); ZDO val = Enumerable.FirstOrDefault(predicate: new IdCriteria(objectId).IsMatch, source: ZDOMan.instance.m_objectsByID.Values); if (val == null) { return "No object found"; } if (!val.Persistent) { return "Object is not persistent and cannot be modified."; } IEnumerable<int> optionalArguments = args.GetOptionalArguments(); bool flag = false; _modifications.Clear(); foreach (int item in optionalArguments) { switch (args.GetString(item)) { case "-position": _modifications.Add(new PositionModification(args.GetVector3(item + 1))); break; case "-rotation": _modifications.Add(new RotationModification(args.GetVector3(item + 1))); break; case "-health": _modifications.Add(new HealthModification(args.GetFloat(item + 1))); break; case "-tag": _modifications.Add(new TagModification(args.GetString(item + 1))); break; case "-force": flag = true; break; default: return "Unknown argument: " + args.GetString(item); } } if (!_modifications.Any()) { return "At least one valid modification argument must be provided."; } if (!flag && !ZdoUtils.CanModifyZdo(val)) { return "Object cannot be modified."; } long owner = val.GetOwner(); ZNetPeer peer = ZNet.instance.GetPeer(owner); if (!flag && peer != null) { return "Object is owned by an online player " + peer.GetPlayerInfo() + " and cannot be modified."; } foreach (IZdoModification modification in _modifications) { modification.Apply(val); } val.SetZdoModified(); builder.Clear(); builder.AppendLine("Object modified successfully"); builder.Append("Prefab: ").Append(val.GetPrefabName()); ZdoUtils.AppendZdoStats(val, builder); return builder.ToString().TrimEnd(Array.Empty<char>()); } } internal class GetServerStats : RconCommand { private StringBuilder builder = new StringBuilder(); public override string Command => "serverStats"; public override string Description => "Prints server statistics including player count, FPS, memory usage, and world information."; protected override string OnHandle(CommandArgs args) { builder.Clear(); _ = ZNet.World?.m_name; int num = ZNet.instance?.m_peers.Count ?? (-1); float num2 = 1f / Time.deltaTime; int valueOrDefault = (ZDOMan.instance?.m_objectsByID?.Count).GetValueOrDefault(-1); EnvMan instance = EnvMan.instance; int num3; if (instance == null) { num3 = -1; } else { ZNet instance2 = ZNet.instance; num3 = instance.GetDay((instance2 != null) ? instance2.GetTimeSeconds() : 0.0); } int num4 = num3; int num5 = ZDOMan.instance?.m_deadZDOs.Count ?? 0; int num6 = ToMegabytes(Profiler.GetMonoUsedSizeLong()); int num7 = ToMegabytes(Profiler.GetMonoHeapSizeLong()); builder.AppendLine($"Stats - Online {num} FPS {num2:0.0}"); builder.AppendLine($"Memory - Mono {num6}MB, Heap {num7}MB"); builder.Append($"World - Day {num4}, Objects {valueOrDefault}, Dead objects {num5}"); return builder.ToString(); } private int ToMegabytes(long bytes) { return Mathf.FloorToInt((float)bytes / 1048576f); } } internal class InvokeConsoleCommand : RconCommand { public override string Command => "consoleCommand"; public override string Description => "Executes a console command on the server. Usage: consoleCommand <command>"; protected override string OnHandle(CommandArgs args) { string text = string.Join(" ", args.Arguments); if (string.IsNullOrWhiteSpace(text)) { return "No command provided."; } ((Terminal)Console.instance).TryRunCommand(text, false, true); return "Command '" + text + "' executed."; } } internal class Kick : RconCommand { public override string Command => "kick"; public override string Description => "Kicks a player from the server. Usage: kick <playername or steamid>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.Kick(@string); return "Kicked " + @string; } } public readonly struct ObjectId { public readonly uint Id; public readonly long UserId; public ObjectId(uint id, long userId) { Id = id; UserId = userId; } } internal class PrintAdminList : RconCommand { private StringBuilder stringBuilder = new StringBuilder(); public override string Command => "adminlist"; public override string Description => "Prints the list of admins on the server."; protected override string OnHandle(CommandArgs args) { stringBuilder.Clear(); foreach (string item in ZNet.instance.m_adminList.GetList()) { stringBuilder.AppendLine(item); } return stringBuilder.ToString(); } } internal class PrintBanlist : RconCommand { private StringBuilder stringBuilder = new StringBuilder(); public override string Command => "banlist"; public override string Description => "Prints the list of banned players"; protected override string OnHandle(CommandArgs args) { stringBuilder.Clear(); foreach (string item in ZNet.instance.m_bannedList.GetList()) { stringBuilder.AppendLine(item); } return stringBuilder.ToString(); } } internal class PrintPermitlist : RconCommand { private StringBuilder stringBuilder = new StringBuilder(); public override string Command => "permitted"; public override string Description => "Prints the list of permitted players on the server."; protected override string OnHandle(CommandArgs args) { stringBuilder.Clear(); foreach (string item in ZNet.instance.m_permittedList.GetList()) { stringBuilder.AppendLine(item); } return stringBuilder.ToString(); } } public abstract class RconCommand : IRconCommand { public abstract string Command { get; } public abstract string Description { get; } public Task<CommandResult> HandleCommandAsync(CommandArgs args) { CommandResult result = default(CommandResult); result.Text = OnHandle(args).Trim(); return Task.FromResult(result); } protected abstract string OnHandle(CommandArgs args); } internal class RemoveAdmin : RconCommand { public override string Command => "removeAdmin"; public override string Description => "Removes a player from the admin list. Usage: removeAdmin <steamId>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.m_adminList.Remove(@string); return @string + " removed from admins"; } } internal class RemovePermitted : RconCommand { public override string Command => "removePermitted"; public override string Description => "Removes a player from the permitted list. Usage: removePermitted <steamId>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.m_permittedList.Remove(@string); return @string + " removed from permitted"; } } internal class SayChat : RconCommand { public override string Command => "say"; public override string Description => "Sends a message to the chat as a shout. Usage: say <message>"; protected override string OnHandle(CommandArgs args) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) string text = args.ToString(); Vector3 val = default(Vector3); if (!ZoneSystem.instance.GetLocationIcon(Game.instance.m_StartLocation, ref val)) { ((Vector3)(ref val))..ctor(0f, 30f, 0f); } ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4] { val, 2, Plugin.CommandsUserInfo, text }); return "Sent to chat - " + text; } } internal class SayPing : RconCommand { public override string Command => "ping"; public override string Description => "Sends a ping message to all players at the specified coordinates. Usage: ping <x> <y> <z>"; protected override string OnHandle(CommandArgs args) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) Vector3 vector = args.GetVector3(0); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4] { vector, 3, Plugin.CommandsUserInfo, "" }); return "Ping sent to " + vector.ToDisplayFormat(); } } internal class ServerLogs : IRconCommand { private const int MaxLinesToDisplay = 5; private readonly StringBuilder _builder = new StringBuilder(); public string Command => "logs"; public string Description => "Get the server logs"; public Task<CommandResult> HandleCommandAsync(CommandArgs args) { string text = Path.Combine(Paths.BepInExRootPath, "LogOutput.log"); if (!File.Exists(text)) { return Task.FromResult(CommandResult.WithText("No logs")); } string text2 = Path.Combine(Paths.CachePath, "LogOutput.log"); File.Copy(text, text2, overwrite: true); string[] array = File.ReadAllLines(text2); int num = Math.Max(array.Length - 5 - 1, 0); _builder.Clear(); for (int i = num; i < array.Length; i++) { _builder.AppendLine(array[i]); } CommandResult result = default(CommandResult); result.Text = _builder.ToString().Trim(new char[1] { '\n' }); result.AttachedFilePath = text2; return Task.FromResult(result); } } internal class ShowMessage : RconCommand { public override string Command => "showMessage"; public override string Description => "Displays a message in the center of the screen for all players. Usage: showMessage <message>"; protected override string OnHandle(CommandArgs args) { string text = args.ToString(); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ShowMessage", new object[2] { 2, text }); return "Message sent - " + text; } } internal class SpawnObject : RconCommand { public override string Command => "spawn"; public override string Description => "Creates the specified number of objects at the given position. Usage (with optional arguments): spawn <prefabName> <x> <y> <z> -count(-c) <count> -radius(-rad) <radius> -level(-l) <level> -rotation(-rot) <x> <y> <z> -tag(-t) <tag> -tamed "; protected override string OnHandle(CommandArgs args) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) string @string = args.GetString(0); Vector3 vector = args.GetVector3(1); int num = 1; int num2 = 0; string text = string.Empty; Quaternion val = Quaternion.identity; float num3 = 0f; bool flag = false; foreach (int optionalArgument in args.GetOptionalArguments()) { string string2 = args.GetString(optionalArgument); switch (string2.ToLower()) { case "-level": case "-l": num2 = args.GetInt(optionalArgument + 1); break; case "-count": case "-c": num = args.GetInt(optionalArgument + 1); break; case "-t": case "-tag": text = args.GetString(optionalArgument + 1); break; case "-rot": case "-rotation": val = Quaternion.Euler(args.GetVector3(optionalArgument + 1)); break; case "-rad": case "-radius": num3 = args.GetFloat(optionalArgument + 1); break; case "-tamed": flag = true; break; default: return "Unknown argument: " + string2; } } GameObject prefab = ZNetScene.instance.GetPrefab(@string); if ((Object)(object)prefab == (Object)null) { return "Prefab " + @string + " not found"; } if (num <= 0) { return "Nothing to spawn"; } List<ZDO> list = new List<ZDO>(num); Character val4 = default(Character); ItemDrop val5 = default(ItemDrop); for (int i = 0; i < num; i++) { ZNetView.StartGhostInit(); Vector3 val2 = Random.onUnitSphere * num3; val2.y = 0f; Vector3 val3 = vector + val2; GameObject obj = Object.Instantiate<GameObject>(prefab, val3, val); if (obj.TryGetComponent<Character>(ref val4)) { val4.SetLevel(num2); } if (obj.TryGetComponent<ItemDrop>(ref val5)) { val5.SetQuality(num2); } ZDO zDO = obj.GetComponent<ZNetView>().GetZDO(); list.Add(zDO); if (!string.IsNullOrEmpty(text)) { zDO.SetTag(text); } if (flag) { zDO.Set(ZDOVars.s_tamed, true); } ZNetView.FinishGhostInit(); Object.Destroy((Object)(object)obj); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"Spawned {num} objects:"); foreach (ZDO item in list) { stringBuilder.Append("- Prefab: " + ZdoUtils.GetPrefabName(item.GetPrefab())); ZdoUtils.AppendZdoStats(item, stringBuilder); stringBuilder.AppendLine(); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } } internal class ServerTimeCommand : RconCommand { public override string Command => "time"; public override string Description => "Get the current server time and day."; protected override string OnHandle(CommandArgs args) { double timeSeconds = ZNet.instance.GetTimeSeconds(); int currentDay = EnvMan.instance.GetCurrentDay(); return $"Current server time: {timeSeconds} sec. Day: {currentDay}"; } } internal class Unban : RconCommand { public override string Command => "unban"; public override string Description => "Unban a user from the server. Usage: unban <playername or steamid>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNet.instance.Unban(@string); return @string + " unbanned"; } } internal class WorldSave : RconCommand { public override string Command => "save"; public override string Description => "Save the current world state."; protected override string OnHandle(CommandArgs args) { ZNet.instance.Save(false, false, false); return "World save started"; } } } namespace ValheimRcon.Commands.Container { public abstract class ContainerRconCommand : RconCommand { protected override string OnHandle(CommandArgs args) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown ObjectId objectId = args.GetObjectId(0); ZDO val = Enumerable.FirstOrDefault(predicate: new IdCriteria(objectId).IsMatch, source: ZDOMan.instance.m_objectsByID.Values); if (val == null) { return "No object found with the specified ID."; } int prefab = val.GetPrefab(); string prefabName = ZdoUtils.GetPrefabName(prefab); GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab); if ((Object)(object)prefab2 == (Object)null) { return "Failed to load prefab: " + prefabName; } Container componentInChildren = prefab2.GetComponentInChildren<Container>(); if ((Object)(object)componentInChildren == (Object)null) { return "Object " + prefabName + " is not a container."; } string @string = val.GetString(ZDOVars.s_items, ""); Inventory val2 = new Inventory(componentInChildren.m_name, componentInChildren.m_bkg, componentInChildren.m_width, componentInChildren.m_height); if (!string.IsNullOrEmpty(@string)) { ZPackage val3 = new ZPackage(@string); val2.Load(val3); } return HandleInventory(args, val, val2, prefabName); } protected abstract string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName); protected void SaveInventory(ZDO zdo, Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); inventory.Save(val); zdo.Set(ZDOVars.s_items, val.GetBase64()); zdo.SetZdoModified(); } } internal class ShowContainerInventory : ContainerRconCommand { public override string Command => "showContainer"; public override string Description => "Shows inventory contents of a container by object ID. Usage: showContainer <id:userid>"; protected override string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName) { if (inventory.NrOfItems() == 0) { return $"Container {prefabName} (ID: {((ZDOID)(ref zdo.m_uid)).ID}:{((ZDOID)(ref zdo.m_uid)).UserID}) is empty."; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("Prefab: {0}", prefabName); ZdoUtils.AppendZdoStats(zdo, stringBuilder); stringBuilder.AppendLine(); stringBuilder.AppendFormat("Items ({0}):", inventory.NrOfItems()); List<ItemData> allItems = inventory.GetAllItems(); int num = 0; foreach (ItemData item in allItems) { stringBuilder.AppendLine(); GameObject dropPrefab = item.m_dropPrefab; string text = ((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? item.m_shared.m_name; stringBuilder.AppendFormat("[{0}] {1} x{2} quality:{3}", num, text, item.m_stack, item.m_quality); if (item.m_variant != 0) { stringBuilder.AppendFormat(" variant:{0}", item.m_variant); } if (item.m_crafterID != 0L) { stringBuilder.AppendFormat(" crafter:{0}({1})", item.m_crafterName, item.m_crafterID); } if (item.m_customData.Count > 0) { stringBuilder.AppendFormat(" data:"); bool flag = false; foreach (KeyValuePair<string, string> customDatum in item.m_customData) { if (flag) { stringBuilder.Append(','); } stringBuilder.AppendFormat("[{0}]:[{1}]", customDatum.Key, customDatum.Value); flag = true; } } num++; } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } } internal class AddItemToContainer : ContainerRconCommand { public override string Command => "addItemToContainer"; public override string Description => "Adds an item to a container's inventory. Usage: addItemToContainer <id:userid> <item_name> -count <count> -quality <quality> -variant <variant> -durability <durability> -data <key> <value> -nocrafter -force"; protected override string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName) { string @string = args.GetString(1); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(@string); if ((Object)(object)itemPrefab == (Object)null) { return "Cannot find item prefab: " + @string; } ItemData itemData = itemPrefab.GetComponent<ItemDrop>().m_itemData; SharedData shared = itemData.m_shared; int num = 1; int num2 = 1; int num3 = 0; bool flag = true; string crafterName = Plugin.ServerChatName.Value; long crafterID = -1L; Dictionary<string, string> dictionary = new Dictionary<string, string>(); bool force = false; float? num4 = null; foreach (int optionalArgument in args.GetOptionalArguments()) { string string2 = args.GetString(optionalArgument); switch (string2) { case "-count": num = args.GetInt(optionalArgument + 1); if (num < 1) { return "Count must be at least 1"; } break; case "-quality": num2 = args.GetInt(optionalArgument + 1); if (num2 < 0) { return "Quality must be at least 0"; } break; case "-variant": num3 = args.GetInt(optionalArgument + 1); if (num3 < 0) { return "Variant must be at least 0"; } if (num3 > 0 && shared.m_variants == 0) { return "Item " + @string + " does not have variants"; } if (num3 > shared.m_variants - 1) { return $"Item {@string} has only {shared.m_variants} variants"; } break; case "-nocrafter": flag = false; crafterID = 0L; crafterName = string.Empty; break; case "-data": { string string3 = args.GetString(optionalArgument + 1); string value = args.TryGetString(optionalArgument + 2); dictionary[string3] = value; break; } case "-durability": num4 = args.GetFloat(optionalArgument + 1); if (num4.Value < 0f) { return "Durability must be at least 0"; } break; case "-force": force = true; break; default: return "Unknown argument: " + string2; } } if (!ContainerUtils.ValidateContainerModification(zdo, prefabName, force, out var error)) { return error; } ItemData val = itemData.Clone(); val.m_quality = num2; val.m_variant = num3; val.m_stack = num; if (flag) { val.m_crafterID = crafterID; val.m_crafterName = crafterName; } else { val.m_crafterID = 0L; val.m_crafterName = string.Empty; } val.m_customData = dictionary; if (shared.m_useDurability) { val.m_durability = (num4.HasValue ? num4.Value : val.GetMaxDurability()); } if (inventory.AddItem(val)) { SaveInventory(zdo, inventory); return $"Item {@string} x{num} added to {prefabName}"; } return "Failed to add item to " + prefabName; } } internal class RemoveItemFromContainer : ContainerRconCommand { public override string Command => "removeItemFromContainer"; public override string Description => "Removes an item from a container's inventory. Usage: removeItemFromContainer <id:userid> -index <index> -item <name> -count <count> -force"; protected override string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName) { IEnumerable<int> optionalArguments = args.GetOptionalArguments(); bool force = false; int num = 1; int? num2 = null; string text = null; foreach (int item in optionalArguments) { switch (args.GetString(item)) { case "-index": num2 = args.GetInt(item + 1); if (num2.Value < 0) { return "Index must be at least 0"; } break; case "-item": text = args.GetString(item + 1); break; case "-count": num = args.GetInt(item + 1); if (num < 1) { return "Count must be at least 1"; } break; case "-force": force = true; break; } } if (!ContainerUtils.ValidateContainerModification(zdo, prefabName, force, out var error)) { return error; } if (!num2.HasValue && string.IsNullOrEmpty(text)) { return "Either -index or -item must be specified."; } if (num2.HasValue && !string.IsNullOrEmpty(text)) { return "Cannot specify both -index and -item. Use only one."; } if (num2.HasValue) { List<ItemData> allItems = inventory.GetAllItems(); if (num2.Value >= allItems.Count) { return $"Index {num2.Value} is out of range. Container has {allItems.Count} items (indices 0-{allItems.Count - 1})."; } ItemData val = allItems[num2.Value]; GameObject dropPrefab = val.m_dropPrefab; string text2 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? val.m_shared.m_name; int num3 = Math.Min(num, val.m_stack); inventory.RemoveItem(val, num3); SaveInventory(zdo, inventory); return $"Removed {text2} x{num3} from {prefabName} (index {num2.Value})"; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(text); if ((Object)(object)itemPrefab == (Object)null) { return "Cannot find item prefab: " + text; } string name = itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_name; if (!inventory.ContainsItemByName(name)) { return "Item " + text + " not found in " + prefabName; } inventory.RemoveItem(name, num, -1, true); SaveInventory(zdo, inventory); return $"Removed {name} x{num} from {prefabName}"; } } internal class ClearContainerInventory : ContainerRconCommand { public override string Command => "clearContainer"; public override string Description => "Clears all items from a container's inventory. Usage: clearContainer <id:userid> -force"; protected override string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName) { IEnumerable<int> optionalArguments = args.GetOptionalArguments(); bool force = false; foreach (int item in optionalArguments) { if (args.GetString(item) == "-force") { force = true; break; } } if (!ContainerUtils.ValidateContainerModification(zdo, prefabName, force, out var error)) { return error; } inventory.RemoveAll(); SaveInventory(zdo, inventory); return "Cleared inventory of " + prefabName; } } public static class ContainerUtils { public static bool ValidateContainerModification(ZDO zdo, string prefabName, bool force, out string error) { if (!zdo.Persistent) { error = "Object is not persistent and cannot be modified."; return false; } if (!ZdoUtils.CanModifyZdo(zdo)) { error = "Object cannot be modified."; return false; } if (zdo.GetInt(ZDOVars.s_inUse, 0) == 1) { error = "Container " + prefabName + " is currently in use and cannot be modified."; return false; } long owner = zdo.GetOwner(); ZNetPeer peer = ZNet.instance.GetPeer(owner); if (peer != null && !force) { error = "Container " + prefabName + " is owned by an online player " + peer.GetPlayerInfo() + " and cannot be modified. Use -force to override."; return false; } error = string.Empty; return true; } } } namespace ValheimRcon.Commands.Search { public interface ISearchCriteria { bool IsMatch(ZDO zdo); } public struct PrefabCriteria : ISearchCriteria { private readonly int _prefabHash; public PrefabCriteria(string prefab) { _prefabHash = StringExtensionMethods.GetStableHashCode(prefab); } public bool IsMatch(ZDO zdo) { return zdo.GetPrefab() == _prefabHash; } } public struct CreatorCriteria : ISearchCriteria { private readonly long _creatorId; public CreatorCriteria(long creatorId) { _creatorId = creatorId; } public bool IsMatch(ZDO zdo) { return zdo.GetLong(ZDOVars.s_creator, 0L) == _creatorId; } } public struct IdCriteria : ISearchCriteria { private readonly uint _id; private readonly long _userId; public IdCriteria(ObjectId objectId) { _id = objectId.Id; _userId = objectId.UserId; } public bool IsMatch(ZDO zdo) { if (((ZDOID)(ref zdo.m_uid)).ID == _id) { return ((ZDOID)(ref zdo.m_uid)).UserID == _userId; } return false; } } public struct TagCriteria : ISearchCriteria { private readonly string _tag; public TagCriteria(string tag) { _tag = tag; } public bool IsMatch(ZDO zdo) { return zdo.GetTag() == _tag; } } public struct NearCriteria : ISearchCriteria { private readonly Vector3 _center; private readonly float _radius; public NearCriteria(Vector3 center, float radius) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _center = center; _radius = radius; } public bool IsMatch(ZDO zdo) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) Vector3 position = zdo.GetPosition(); if (position.x < _center.x + _radius && position.x > _center.x - _radius && position.y < _center.y + _radius && position.y > _center.y - _radius && position.z < _center.z + _radius) { return position.z > _center.z - _radius; } return false; } } public struct OldTagCriteria : ISearchCriteria { private static readonly int OldTagZdoHash = StringExtensionMethods.GetStableHashCode("tag"); private readonly string _tag; public OldTagCriteria(string tag) { _tag = tag; } public bool IsMatch(ZDO zdo) { return zdo.GetString(OldTagZdoHash, "") == _tag; } } } namespace ValheimRcon.Commands.Modification { public struct HealthModification : IZdoModification { private readonly float _health; public HealthModification(float health) { _health = health; } public void Apply(ZDO zdo) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) zdo.Set(ZDOVars.s_health, _health); if (ZdoUtils.CheckPrefabType(zdo.GetPrefab(), ZdoUtils.Type.Building)) { ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, zdo.m_uid, "RPC_HealthChanged", new object[1] { _health }); } } } public interface IZdoModification { void Apply(ZDO zdo); } public struct PositionModification : IZdoModification { private readonly Vector3 _position; public PositionModification(Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _position = position; } public void Apply(ZDO zdo) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) zdo.SetPosition(_position); } } public struct RotationModification : IZdoModification { private readonly Vector3 _rotation; public RotationModification(Vector3 rotation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _rotation = rotation; } public void Apply(ZDO zdo) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) zdo.SetRotation(Quaternion.Euler(_rotation)); } } public struct TagModification : IZdoModification { private readonly string _tag; public TagModification(string tag) { _tag = tag; } public void Apply(ZDO zdo) { zdo.SetTag(_tag); } } } namespace ValheimRcon.Commands.RandomEvents { internal class CurrentEvent : RconCommand { public override string Command => "currentEvent"; public override string Description => "Displays the currently active random event"; protected override string OnHandle(CommandArgs args) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) RandomEvent currentRandomEvent = RandEventSystem.instance.GetCurrentRandomEvent(); if (currentRandomEvent == null) { return "No active random event."; } return "Current event: " + currentRandomEvent.m_name + " Position: " + currentRandomEvent.m_pos.ToDisplayFormat(); } } internal class EventsList : RconCommand { private readonly StringBuilder _builder = new StringBuilder(); public override string Command => "eventsList"; public override string Description => "Prints all available random events"; protected override string OnHandle(CommandArgs args) { _builder.Clear(); _builder.AppendLine("Available random events:"); bool flag = true; foreach (RandomEvent @event in RandEventSystem.instance.m_events) { if (!flag) { _builder.Append(", "); } _builder.Append(@event.m_name); flag = false; } return _builder.ToString(); } } internal class StartEvent : RconCommand { public override string Command => "startEvent"; public override string Description => "Starts a random event. Usage: startEvent <event_name> <x> <y> <z>"; protected override string OnHandle(CommandArgs args) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) string @string = args.GetString(0); Vector3 vector = args.GetVector3(1); RandomEvent @event = RandEventSystem.instance.GetEvent(@string); if (@event == null) { return "Event '" + @string + "' not found."; } RandEventSystem.instance.SetRandomEvent(@event, vector); return "Event '" + @string + "' started at " + vector.ToDisplayFormat(); } } internal class StopEvent : RconCommand { public override string Command => "stopEvent"; public override string Description => "Stops the currently active random event."; protected override string OnHandle(CommandArgs args) { RandEventSystem.instance.ResetRandomEvent(); return "Current random event stopped."; } } } namespace ValheimRcon.Commands.Players { internal class Damage : PlayerRconCommand { public override string Command => "damage"; public override string Description => "Damage a player by a specified amount. Usage: damage <steamid> <amount>"; protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown HitData val = new HitData { m_blockable = false, m_dodgeable = false, m_ignorePVP = true, m_hitType = (HitType)0, m_damage = new DamageTypes { m_damage = args.GetInt(1) } }; peer.InvokeRoutedRpcToZdo("RPC_Damage", val); return peer.GetPlayerInfo() + " damaged to " + val.m_damage.m_damage.ToDisplayFormat() + "hp"; } } internal class FindPlayer : RconCommand { public override string Command => "findPlayer"; public override string Description => "Find a player and show their details. Usage: findPlayer <playername/steamid/player id>"; protected override string OnHandle(CommandArgs args) { string @string = args.GetString(0); ZNetPeer val = ZNet.instance.GetPeerByPlayerName(@string) ?? ZNet.instance.GetPeerByHostName(@string); if (val == null && long.TryParse(@string, out var playerId)) { val = ZNet.instance.m_peers.Find((ZNetPeer p) => p.GetPlayerId() == playerId); } if (val == null) { return "Player " + @string + " not found"; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Player " + @string + " found:"); val.WritePlayerInfo(stringBuilder); return stringBuilder.ToString().Trim(); } } internal class GiveItem : PlayerRconCommand { public override string Command => "give"; public override string Description => "Spawns an item on the player. Usage (with optional arguments): give <steamid> <item_name> -count <count> -quality <quality> -variant <variant> -durability <durability> -data <key> <value> -nocrafter"; protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args) { //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) string @string = args.GetString(1); int num = 1; int num2 = 1; int num3 = 0; string crafterName = Plugin.ServerChatName.Value; long crafterID = -1L; Dictionary<string, string> dictionary = new Dictionary<string, string>(); float? num4 = null; GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(@string);