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.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.2.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.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();
}
}
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();
}
}
[BepInProcess("valheim_server.exe")]
[BepInPlugin("org.tristan.rcon", "Valheim Rcon", "1.2.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.2.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(RconPeer 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 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(RconPeer peer, string command, IReadOnlyList<string> args, CommandResult result);
[HarmonyPatch]
private class Patches
{
[HarmonyFinalizer]
[HarmonyPatch(typeof(ZNet), "LoadWorld")]
private static void ZNet_LoadWorld()
{
Instance._receiver.StartListening();
}
[HarmonyPrefix]
[HarmonyPatch(typeof(Game), "Shutdown")]
private static void Game_Shutdown()
{
Instance._receiver.Dispose();
}
}
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>();
public static RconProxy Instance { get; private set; }
internal event CompletedCommandDelegate OnCommandCompleted;
private void Awake()
{
Instance = this;
_receiver = new RconCommandReceiver(Plugin.Port.Value, Plugin.Password.Value, HandleCommandAsync);
}
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(RconPeer 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]
private enum Type
{
None = 0,
ItemDrop = 1,
GuardStone = 2,
Character = 4,
Building = 8,
ItemStand = 0x10,
Destructible = 0x20,
Interactable = 0x40
}
[CompilerGenerated]
private sealed class <GetPermittedPlayers>d__18 : 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__18(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__18 <GetPermittedPlayers>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<GetPermittedPlayers>d__ = this;
}
else
{
<GetPermittedPlayers>d__ = new <GetPermittedPlayers>d__18(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>();
public static string GetTag(this ZDO zdo)
{
return zdo.GetString("tag", "");
}
public static void SetTag(this ZDO zdo, string tag)
{
zdo.Set("tag", tag);
}
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)
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
stringBuilder.Append($" Id: {((ZDOID)(ref zdo.m_uid)).ID} UserId: {((ZDOID)(ref zdo.m_uid)).UserID}");
stringBuilder.Append($" Position: {zdo.GetPosition()}({ZoneSystem.GetZone(zdo.GetPosition())})");
Quaternion rotation = zdo.GetRotation();
stringBuilder.Append($" Rotation: {((Quaternion)(ref rotation)).eulerAngles}");
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);
}
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 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 (CanDeleteZdo(zdo))
{
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 CanDeleteZdo(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;
}
public static bool MatchesCriteria(ZDO zdo, long? creatorId, ObjectId? id, string tag)
{
if (creatorId.HasValue && zdo.GetLong(ZDOVars.s_creator, 0L) != creatorId.Value)
{
return false;
}
if (id.HasValue && (((ZDOID)(ref zdo.m_uid)).ID != id.Value.Id || ((ZDOID)(ref zdo.m_uid)).UserID != id.Value.UserId))
{
return false;
}
if (!string.IsNullOrEmpty(tag) && zdo.GetTag() != tag)
{
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)}");
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)}/{@float}");
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)}");
stringBuilder.Append($" Support: {zdo.GetFloat(ZDOVars.s_support, num2)}");
}
}
private 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__18))]
private static IEnumerable<string> GetPermittedPlayers(ZDO zdo)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <GetPermittedPlayers>d__18(-2)
{
<>3__zdo = zdo
};
}
}
}
namespace ValheimRcon.Core
{
public class AsynchronousSocketListener
{
internal delegate void MessageReceived(RconPeer peer, RconPacket package);
private static readonly TimeSpan UnauthorizedClientLifetime = TimeSpan.FromSeconds(30.0);
private readonly IPAddress _address;
private readonly int _port;
private readonly Socket _listener;
private readonly HashSet<RconPeer> _clients = new HashSet<RconPeer>();
private readonly HashSet<RconPeer> _waitingForDisconnect = new HashSet<RconPeer>();
private readonly List<RconPeer> _clientsSnapshot = new List<RconPeer>();
private readonly object _clientsLock = new object();
private readonly object _disconnectLock = new object();
private IDisposable _acceptThread;
internal event MessageReceived 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 async Task SendAsync(RconPeer peer, RconPacket packet)
{
if (peer.IsDisposed)
{
Log.Debug("Tried to send to a disposed peer.");
return;
}
string endpointString = "unknown";
try
{
Socket socket = peer.socket;
if (socket == null || !socket.Connected)
{
Log.Debug("Warning: Socket is null or not connected");
return;
}
byte[] array = packet.Serialize();
int num = await SocketTaskExtensions.SendAsync(socket, new ArraySegment<byte>(array), SocketFlags.None);
endpointString = socket.RemoteEndPoint.ToString();
Log.Debug($"Sent {num} bytes to client [{endpointString}]");
}
catch (ObjectDisposedException)
{
Log.Debug("Attempted to send to a disposed socket (" + endpointString + ").");
}
catch (Exception msg)
{
Log.Error(msg);
}
}
public void Update()
{
lock (_clientsLock)
{
_clientsSnapshot.Clear();
_clientsSnapshot.AddRange(_clients);
}
foreach (RconPeer item in _clientsSnapshot)
{
if (!IsConnected(item))
{
Disconnect(item);
}
else if (IsUnauthorizedTimeout(item))
{
Log.Warning("Unauthorized timeout [" + item.Endpoint + "]");
Disconnect(item);
}
else
{
TryReceive(item);
}
}
lock (_disconnectLock)
{
foreach (RconPeer item2 in _waitingForDisconnect)
{
lock (_clientsLock)
{
_clients.Remove(item2);
}
DisconnectPeer(item2);
}
_waitingForDisconnect.Clear();
}
}
public void Close()
{
_listener.Close();
_acceptThread?.Dispose();
lock (_clientsLock)
{
foreach (RconPeer client in _clients)
{
client.Dispose();
}
_clients.Clear();
}
lock (_disconnectLock)
{
_waitingForDisconnect.Clear();
}
}
public void Disconnect(RconPeer 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 TryReceive(RconPeer peer)
{
Socket socket = peer.socket;
if (!socket.Poll(0, SelectMode.SelectRead) || socket.Available <= 0)
{
return;
}
int available = socket.Available;
if (available > peer.Buffer.Length)
{
Log.Warning($"Available data exceeds buffer size: {available} > {peer.Buffer.Length} [{peer.Endpoint}]");
Disconnect(peer);
return;
}
int num = socket.Receive(peer.Buffer, 0, Math.Min(available, peer.Buffer.Length), SocketFlags.None);
if (num != 0)
{
OnPackageReceived(peer, num);
}
}
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 OnPackageReceived(RconPeer peer, int readCount)
{
_ = peer.socket;
Log.Debug($"Got package from client, {readCount} bytes [{peer.Endpoint}]");
try
{
RconPacket rconPacket = new RconPacket(peer.Buffer);
Log.Debug($"Received package {rconPacket}");
this.OnMessage?.Invoke(peer, rconPacket);
}
catch (Exception ex)
{
Log.Warning("Failed to parse packet from [" + peer.Endpoint + "]: " + ex.Message);
Disconnect(peer);
}
finally
{
Array.Clear(peer.Buffer, 0, peer.Buffer.Length);
}
}
private void DisconnectPeer(RconPeer peer)
{
_ = peer.socket;
Log.Debug("Client disconnected [" + peer.Endpoint + "]");
try
{
peer.Dispose();
}
catch
{
Log.Debug("Warning: Could not dispose peer connection");
}
}
private static bool IsConnected(RconPeer peer)
{
Socket socket = peer.socket;
if (socket.Connected)
{
if (socket.Poll(0, SelectMode.SelectRead))
{
return socket.Available != 0;
}
return true;
}
return false;
}
private static bool IsUnauthorizedTimeout(RconPeer peer)
{
if (!peer.Authentificated)
{
return DateTime.Now - peer.created > UnauthorizedClientLifetime;
}
return false;
}
}
public enum PacketType
{
Error = 0,
Command = 2,
Login = 3
}
public delegate Task<string> RconCommandHandler(RconPeer peer, string command, IReadOnlyList<string> data);
public class RconCommandReceiver : IDisposable
{
private const int MaxPayloadSize = 4080;
private static readonly Regex MatchRegex = new Regex("(?<=[ ][\\\"]|^[\\\"])[^\\\"]+(?=[\\\"][ ]|[\\\"]$)|(?<=[ ]|^)[^\\\" ]+(?=[ ]|$)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
private readonly AsynchronousSocketListener _socketListener;
private readonly string _password;
private RconCommandHandler _commandHandler;
public RconCommandReceiver(int port, 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;
_socketListener = new AsynchronousSocketListener(IPAddress.Any, port);
_socketListener.OnMessage += SocketListener_OnMessage;
_commandHandler = commandHandler;
}
public void StartListening()
{
_socketListener.StartListening();
}
public void Update()
{
_socketListener.Update();
}
public void Dispose()
{
_socketListener.Close();
}
private async void SocketListener_OnMessage(RconPeer peer, RconPacket packet)
{
_ = peer.socket;
switch (packet.type)
{
case PacketType.Login:
{
if (peer.Authentificated)
{
Log.Error("Already authorized [" + peer.Endpoint + "]");
await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, PacketType.Command, "Already authorized"));
_socketListener.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 _socketListener.SendAsync(peer, rconPacket2);
if (!success)
{
_socketListener.Disconnect(peer);
}
break;
}
case PacketType.Command:
{
if (!peer.Authentificated)
{
Log.Warning("Not authorized [" + peer.Endpoint + "]");
await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, packet.type, "Unauthorized"));
_socketListener.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 _socketListener.SendAsync(peer, new RconPacket(packet.requestId, packet.type, "Empty command"));
break;
}
string command = list[0];
list.RemoveAt(0);
string text = await _commandHandler(peer, command, list);
if (RconPacket.GetPayloadSize(text) > 4080)
{
int maxLength = text.Length / 2;
text = RconCommandsUtil.TruncateMessage(text, maxLength) + "\n--- message truncated ---";
}
RconPacket rconPacket = new RconPacket(packet.requestId, packet.type, text);
Log.Debug($"Command result {command} - {rconPacket}");
await _socketListener.SendAsync(peer, rconPacket);
break;
}
default:
Log.Error($"Unknown packet type: {packet} [{peer.Endpoint}]");
await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, PacketType.Error, "Cannot handle command"));
_socketListener.Disconnect(peer);
break;
}
}
}
public readonly struct RconPacket
{
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");
}
if (bytes.Length > 65536)
{
throw new ArgumentException("Packet too large", "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", "bytes");
}
if (num < 10)
{
throw new ArgumentException("Packet data too small", "bytes");
}
if (num > 2147483643 || num + 4 > bytes.Length)
{
throw new ArgumentException("Packet length exceeds buffer size", "bytes");
}
requestId = binaryReader.ReadInt32();
type = (PacketType)binaryReader.ReadInt32();
if (!Enum.IsDefined(typeof(PacketType), type))
{
throw new ArgumentException("Invalid packet type", "bytes");
}
int num2 = num - 10;
if (num2 < 0)
{
throw new ArgumentException("Invalid payload size", "bytes");
}
if (num2 > 4096)
{
throw new ArgumentException("Payload too large", "bytes");
}
byte[] bytes2 = binaryReader.ReadBytes(num2);
payload = Encoding.UTF8.GetString(bytes2);
}
public RconPacket(int requestId, PacketType type, string payload)
{
if (payload != null && GetPayloadSize(payload) > 4096)
{
throw new ArgumentException("Payload too large", "payload");
}
this.requestId = requestId;
this.type = type;
this.payload = payload ?? string.Empty;
}
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 > int.MaxValue)
{
throw new InvalidOperationException("Packet too large for serialization");
}
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 : IDisposable
{
public const int BufferSize = 4096;
private bool disposed;
public readonly byte[] Buffer = new byte[4096];
public readonly Socket socket;
public readonly DateTime created;
public bool IsDisposed => disposed;
public bool Authentificated { get; private set; }
public string Endpoint
{
get
{
try
{
return socket?.RemoteEndPoint?.ToString() ?? string.Empty;
}
catch
{
return "unknown";
}
}
}
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 AddGlobalKeyCommand : RconCommand
{
public override string Command => "addGlobalKey";
public override string Description => "Adds a global key to the server. Usage: addGlobalKey <key>";
protected override string OnHandle(CommandArgs args)
{
string @string = args.GetString(0);
ZoneSystem.instance.GlobalKeyAdd(@string, true);
return "Added global key: " + @string;
}
}
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)
{
return new ObjectId(args.GetUInt(index), args.GetLong(index + 1));
}
}
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 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}hp";
}
}
internal class DeleteObjects : RconCommand
{
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>";
protected override string OnHandle(CommandArgs args)
{
IEnumerable<int> optionalArguments = args.GetOptionalArguments();
if (!optionalArguments.Any())
{
return "At least one criteria must be provided.";
}
long? creatorId = null;
ObjectId? id = null;
string tag = string.Empty;
foreach (int item in optionalArguments)
{
string @string = args.GetString(item);
switch (@string.ToLower())
{
case "-creator":
creatorId = args.GetLong(item + 1);
break;
case "-id":
id = args.GetObjectId(item + 1);
break;
case "-tag":
tag = args.GetString(item + 1);
break;
default:
return "Unknown argument: " + @string;
}
}
ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => ZdoUtils.MatchesCriteria(zdo, creatorId, id, tag)).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 (ZdoUtils.CanDeleteZdo(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
{
public override string Command => "findObjects";
public override string Description => "Find objects matching all search criteria. Usage (with optional arguments): findObjects -prefab <prefab> -creator <creator id> -id <id> <userid> -tag <tag>";
protected override string OnHandle(CommandArgs args)
{
IEnumerable<int> optionalArguments = args.GetOptionalArguments();
if (!optionalArguments.Any())
{
return "At least one search criteria must be provided.";
}
string prefab = string.Empty;
long? creatorId = null;
ObjectId? id = null;
string tag = string.Empty;
foreach (int item in optionalArguments)
{
string @string = args.GetString(item);
switch (@string.ToLower())
{
case "-prefab":
prefab = args.GetString(item + 1);
break;
case "-creator":
creatorId = args.GetLong(item + 1);
break;
case "-id":
id = args.GetObjectId(item + 1);
break;
case "-tag":
tag = args.GetString(item + 1);
break;
default:
return "Unknown argument: " + @string;
}
}
int prefabHash = StringExtensionMethods.GetStableHashCode(prefab);
ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => (string.IsNullOrEmpty(prefab) || zdo.GetPrefab() == prefabHash) && ZdoUtils.MatchesCriteria(zdo, creatorId, id, tag)).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 FindObjectsNear : RconCommand
{
public override string Command => "findObjectsNear";
public override string Description => "Find objects near a location. Usage (with optional arguments): findObjectsNear <x> <z> <y> <radius> -prefab <prefab> -creator <creator id> -id <id> <userid> -tag <tag>";
protected override string OnHandle(CommandArgs args)
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_0168: Unknown result type (might be due to invalid IL or missing references)
//IL_016f: Unknown result type (might be due to invalid IL or missing references)
Vector3 position = args.GetVector3(0);
float radius = args.GetFloat(3);
long? creatorId = null;
ObjectId? id = null;
string tag = string.Empty;
foreach (int optionalArgument in args.GetOptionalArguments())
{
string @string = args.GetString(optionalArgument);
switch (@string.ToLower())
{
case "-creator":
creatorId = args.GetLong(optionalArgument + 1);
break;
case "-id":
id = args.GetObjectId(optionalArgument + 1);
break;
case "-tag":
tag = args.GetString(optionalArgument + 1);
break;
default:
return "Unknown argument: " + @string;
}
}
ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => IsInRange(zdo.GetPosition(), position, radius) && ZdoUtils.MatchesCriteria(zdo, creatorId, id, tag)).ToArray();
if (array.Length == 0)
{
return "No objects found";
}
StringBuilder stringBuilder = new StringBuilder();
ZDO[] array2 = array;
foreach (ZDO val in array2)
{
stringBuilder.Append("- Prefab: " + ZdoUtils.GetPrefabName(val.GetPrefab()));
float num = Vector3.Distance(position, val.GetPosition());
stringBuilder.Append($" Distance: {num}");
ZdoUtils.AppendZdoStats(val, stringBuilder);
stringBuilder.AppendLine();
}
return stringBuilder.ToString().Trim();
}
private static bool IsInRange(Vector3 zdoPosition, Vector3 position, float radius)
{
//IL_0000: 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_0010: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: 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_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: 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_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
if (zdoPosition.x < position.x + radius && zdoPosition.x > position.x - radius && zdoPosition.y < position.y + radius && zdoPosition.y > position.y - radius && zdoPosition.z < position.z + radius)
{
return zdoPosition.z > position.z - radius;
}
return false;
}
}
[Exclude]
internal class MoveObjectById : RconCommand
{
public override string Command => "moveObjectById";
public override string Description => "[WIP: Currently requires scene reload] Move an object by its user and object ids (which together make up a ZDO.m_uid). Usage: moveObjectById <userId> <objectId> <x> <y> <x> <rx> <ry> <rz>";
protected override string OnHandle(CommandArgs args)
{
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_0083: Unknown result type (might be due to invalid IL or missing references)
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
long objectId = args.GetLong(0);
ZDO val = ((IEnumerable<ZDO>)ZDOMan.instance.m_objectsByID.Values).FirstOrDefault((Func<ZDO, bool>)((ZDO obj) => ((ZDOID)(ref obj.m_uid)).ID == objectId));
if (val == null)
{
return $"No objects found for id {objectId}";
}
Vector3 position = default(Vector3);
((Vector3)(ref position))..ctor(args.GetFloat(2), args.GetFloat(3), args.GetFloat(4));
Quaternion rotation = Quaternion.Euler(args.GetFloat(5), args.GetFloat(6), args.GetFloat(7));
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Moving object:");
stringBuilder.Append("- Prefab: " + ZdoUtils.GetPrefabName(val.GetPrefab()));
ZdoUtils.AppendZdoStats(val, stringBuilder);
stringBuilder.AppendLine();
val.SetPosition(position);
val.SetRotation(rotation);
val.IncreaseDataRevision();
val.DataRevision += 120;
ZDOMan.instance.ClientChanged(val.m_uid);
ZDOMan.instance.ForceSendZDO(val.m_uid);
stringBuilder.Append("- To new location: ");
ZdoUtils.AppendZdoStats(val, stringBuilder);
stringBuilder.AppendLine();
return stringBuilder.ToString().Trim();
}
}
internal class FindPlayer : RconCommand
{
public override string Command => "findPlayer";
public override string Description => "Find a player and show their details. Usage: findPlayer <playername or steamid>";
protected override string OnHandle(CommandArgs args)
{
//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
string @string = args.GetString(0);
ZNetPeer val = ZNet.instance.GetPeerByPlayerName(@string) ?? ZNet.instance.GetPeerByHostName(@string);
if (val == null)
{
return "Player " + @string + " not found";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Player " + @string + " found:");
stringBuilder.AppendLine("Steam ID: " + val.GetSteamId());
stringBuilder.AppendLine("Name: " + val.m_playerName);
ZDO zDO = val.GetZDO();
bool flag = zDO == null;
stringBuilder.AppendLine($"Is Dead: {flag}");
if (!flag)
{
stringBuilder.AppendLine($"Position: {val.GetRefPos()} ({ZoneSystem.GetZone(val.GetRefPos())})");
stringBuilder.AppendLine($"Health: {zDO.GetFloat(ZDOVars.s_health, 0f)}/{zDO.GetFloat(ZDOVars.s_maxHealth, 0f)}");
}
return stringBuilder.ToString().Trim();
}
}
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 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> -data <key> <value> -nocrafter";
protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
{
//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
//IL_0206: Unknown result type (might be due to invalid IL or missing references)
//IL_0288: Unknown result type (might be due to invalid IL or missing references)
//IL_028d: 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>();
GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(@string);
if ((Object)(object)itemPrefab == (Object)null)
{
return "Cannot find prefab " + @string;
}
ItemData itemData = itemPrefab.GetComponent<ItemDrop>().m_itemData;
SharedData shared = itemData.m_shared;
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":
crafterID = 0L;
crafterName = string.Empty;
break;
case "-data":
{
string string3 = args.GetString(optionalArgument + 1);
string value = args.TryGetString(optionalArgument + 2);
dictionary[string3] = value;
break;
}
default:
return "Unknown argument: " + string2;
}
}
List<ItemDrop> list = default(List<ItemDrop>);
IDisposable disposable = (IDisposable)(object)CollectionPool<List<ItemDrop>, ItemDrop>.Get(ref list);
ZNetView.StartGhostInit();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Spawning items on player {peer.GetPlayerInfo()} {peer.GetRefPos()}:");
while (num > 0)
{
ItemData val = itemData.Clone();
int num4 = Math.Min(shared.m_maxStackSize, num);
val.m_dropPrefab = itemPrefab;
val.m_quality = num2;
val.m_variant = num3;
val.m_crafterID = crafterID;
val.m_crafterName = crafterName;
val.m_customData = dictionary;
if (shared.m_useDurability)
{
val.m_durability = val.GetMaxDurability();
}
ItemDrop val2 = ItemDrop.DropItem(val, num4, peer.GetRefPos(), Quaternion.identity);
list.Add(val2);
num -= num4;
stringBuilder.Append('-');
ZdoUtils.AppendZdoStats(val2.m_nview.GetZDO(), stringBuilder);
stringBuilder.AppendLine();
}
ZNetView.FinishGhostInit();
foreach (ItemDrop item in list)
{
Object.Destroy((Object)(object)((Component)item).gameObject);
}
disposable.Dispose();
return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
}
}
internal class Heal : PlayerRconCommand
{
public override string Command => "heal";
public override string Description => "Heals the player's to a specified value. Usage: heal <steamid> <value>";
protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
{
int @int = args.GetInt(1);
peer.InvokeRoutedRpcToZdo("RPC_Heal", (float)@int, true);
return $"{peer.GetPlayerInfo()} healed to {@int}hp";
}
}
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;
}
}
public abstract class PlayerRconCommand : RconCommand
{
protected override string OnHandle(CommandArgs args)
{
string @string = args.GetString(0);
ZNetPeer val = ZNet.instance.GetPeerByHostName(@string);
if (val == null)
{
val = ZNet.instance.GetPeerByPlayerName(@string);
}
if (val == null)
{
return "Cannot find user " + @string;
}
ZDO zDO = val.GetZDO();
if (zDO == null)
{
return "Cannot handle command for player " + val.GetPlayerInfo() + ". ZDO not found";
}
return OnHandle(val, zDO, args);
}
protected abstract string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args);
}
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 PrintGlobalKeysCommand : RconCommand
{
public override string Command => "globalKeys";
public override string Description => "Prints all global keys and their values.";
protected override string OnHandle(CommandArgs args)
{
List<string> globalKeys = ZoneSystem.instance.GetGlobalKeys();
Dictionary<string, string> globalKeysValues = ZoneSystem.instance.m_globalKeysValues;
StringBuilder stringBuilder = new StringBuilder("Global Keys:\n");
foreach (string item in globalKeys)
{
stringBuilder.Append(item);
if (globalKeysValues.TryGetValue(item, out var value))
{
stringBuilder.Append(" : " + value);
}
stringBuilder.AppendLine();
}
return stringBuilder.ToString().Trim();
}
}
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 RemoveGlobalKeyCommand : RconCommand
{
public override string Command => "removeGlobalKey";
public override string Description => "Removes a global key from the server. Usage: removeGlobalKey <key>";
protected override string OnHandle(CommandArgs args)
{
string @string = args.GetString(0);
ZoneSystem.instance.GlobalKeyRemove(@string, true);
return "Global key '" + @string + "' removed successfully.";
}
}
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_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
Vector3 val = default(Vector3);
val.x = args.GetInt(0);
val.y = args.GetInt(1);
val.z = args.GetInt(2);
ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4]
{
val,
3,
Plugin.CommandsUserInfo,
""
});
return $"Ping sent to {val}";
}
}
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 ShowPlayers : RconCommand
{
private StringBuilder _builder = new StringBuilder();
public override string Command => "players";
public override string Description => "Show all online players with their positions and zones";
protected override string OnHandle(CommandArgs args)
{
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
_builder.Clear();
int count = ZNet.instance.GetPeers().Count;
_builder.AppendFormat("Online {0}\n", count);
foreach (ZNetPeer peer in ZNet.instance.GetPeers())
{
_builder.AppendFormat("{0}:{1} - {2}({3})", peer.GetSteamId(), peer.m_playerName, peer.GetRefPos(), ZoneSystem.GetZone(peer.GetRefPos()));
_builder.AppendLine();
}
return _builder.ToString();
}
}
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 TeleportPlayer : PlayerRconCommand
{
public override string Command => "teleport";
public override string Description => "Teleports the player to a specified position. Usage: teleport <x> <y> <z>";
protected override string OnHandle(ZNetPeer peer, ZDO zdo, 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_0016: 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_0043: Unknown result type (might be due to invalid IL or missing references)
Vector3 vector = args.GetVector3(1);
peer.InvokeRoutedRpcToZdo("RPC_TeleportTo", vector, Quaternion.identity, true);
return $"Player {peer.GetPlayerInfo()} teleported to {vector}";
}
}
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";
}
}
}