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.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.0.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.0")]
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.0";
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__19 : 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__19(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__19 <GetPermittedPlayers>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<GetPermittedPlayers>d__ = this;
}
else
{
<GetPermittedPlayers>d__ = new <GetPermittedPlayers>d__19(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 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__19))]
private static IEnumerable<string> GetPermittedPlayers(ZDO zdo)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <GetPermittedPlayers>d__19(-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)
//IL_0239: 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.";
}
val.SetOwner(ZDOMan.GetSessionID());
foreach (IZdoModification modification in _modifications)
{
modification.Apply(val);
}
val.DataRevision += 100;
ZDOMan.instance.ForceSendZDO(val.m_uid);
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.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> -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_01f9: Unknown result type (might be due to invalid IL or missing references)
//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
//IL_0223: Unknown result type (might be due to invalid IL or missing references)
//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
//IL_02ac: 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();
Vector3 position = zdo.GetPosition();
stringBuilder.AppendLine("Spawning items on player " + peer.GetPlayerInfo() + " " + position.ToDisplayFormat() + ":");
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, position, 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)
{
float @float = args.GetFloat(1);
peer.InvokeRoutedRpcToZdo("RPC_Heal", @float, true);
return peer.GetPlayerInfo() + " healed to " + @float.ToDisplayFormat() + "hp";
}
}
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 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)
{
_builder.Clear();
int count = ZNet.instance.GetPeers().Count;
_builder.AppendFormat("Online {0}\n", count);
foreach (ZNetPeer peer in ZNet.instance.GetPeers())
{
peer.WritePlayerInfo(_builder);
_builder.AppendLine();
}
return _builder.ToString();
}
}
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_0020: 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(1);
peer.InvokeRoutedRpcToZdo("RPC_TeleportTo", vector, zdo.GetRotation(), true);
return "Player " + peer.GetPlayerInfo() + " teleported to " + vector.ToDisplayFormat();
}
}
}
namespace ValheimRcon.Commands.GlobalKeys
{
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 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 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.";
}
}
}
namespace ValheimRcon.Commands.Admin
{
internal class DisconnectAll : RconCommand
{
public override string Command => "disconnectAll";
public override string Description => "Disconnects all players from the server.";
protected override string OnHandle(CommandArgs args)
{
ZNet.instance.SendDisconnect();
return "All players have been disconnected from the server.";
}
}
}