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 LukeSkywalker.IPNetwork;
using Splatform;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.Profiling;
using ValheimRcon.Commands;
using ValheimRcon.Commands.Modification;
using ValheimRcon.Commands.Search;
using ValheimRcon.Core;
using ValheimRcon.ZDOInfo;
[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.5.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.5.1.0")]
[module: UnverifiableCode]
internal class Helper
{
public static void WatchConfigFileChanges(ConfigFile config, Action onChanged = null)
{
WatchFileChanges(config.ConfigFilePath, (Action)config.Reload);
config.SettingChanged += delegate
{
onChanged?.Invoke();
};
}
public static void WatchFileChanges(string path, Action onChanged)
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher();
string directoryName = Path.GetDirectoryName(path);
string fileName = Path.GetFileName(path);
fileSystemWatcher.Path = directoryName;
fileSystemWatcher.Filter = fileName;
fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
fileSystemWatcher.Changed += delegate
{
onChanged?.Invoke();
};
fileSystemWatcher.Deleted += delegate
{
onChanged?.Invoke();
};
fileSystemWatcher.Created += delegate
{
onChanged?.Invoke();
};
fileSystemWatcher.Renamed += delegate
{
onChanged?.Invoke();
};
fileSystemWatcher.EnableRaisingEvents = true;
}
}
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 url;
public string text;
public string filePath;
}
private const string Name = "RCON";
private readonly IDisposable _thread;
private readonly ConcurrentQueue<Message> _queue = new ConcurrentQueue<Message>();
public DiscordService()
{
_thread = ThreadingUtil.RunPeriodicalInSingleThread(SendQueuedMessage, 333);
}
public void SendResult(string url, string text, string filePath)
{
if (!string.IsNullOrEmpty(url))
{
_queue.Enqueue(new Message
{
url = url,
filePath = filePath,
text = text
});
}
}
private void SendQueuedMessage()
{
if (!_queue.TryDequeue(out var result) || string.IsNullOrEmpty(result.url))
{
return;
}
try
{
string filePath = result.filePath;
if (string.IsNullOrEmpty(filePath))
{
Discord.Send(result.text, "RCON", result.url);
}
else
{
string fileName = Path.GetFileName(filePath);
string extension = Path.GetExtension(filePath);
Discord.SendFile(result.text, fileName, extension, filePath, "RCON", result.url);
}
Log.Debug($"Sent to discord (symbols:{result.text.Length})");
}
catch (Exception arg)
{
Log.Error($"Cannot send to discord (symbols:{result.text.Length})\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 string ToDisplayFormat(this Vector2i 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)
return $"({vector.x} {vector.y})";
}
}
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_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: 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}", peer.GetRefPos().ToDisplayFormat());
sb.AppendFormat(" Zone: {0}", ZoneSystem.GetZone(peer.GetRefPos()).ToDisplayFormat());
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());
}
sb.AppendFormat(" Public position: {0}", peer.m_publicRefPos);
if (peer.m_serverSyncedPlayerData.TryGetValue("platformDisplayName", out var value))
{
sb.AppendFormat(" Platform name: {0}", value);
}
}
}
[BepInProcess("valheim_server.exe")]
[BepInPlugin("org.tristan.rcon", "Valheim Rcon", "1.5.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);
}
}
}
private const long MaxDiscordPayloadSize = 10485760L;
public const string Guid = "org.tristan.rcon";
public const string Name = "Valheim Rcon";
public const string Version = "1.5.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 static ConfigEntry<string> WhiteListConfig;
private static ConfigEntry<string> BlackListConfig;
private static ConfigEntry<string> DiscordSecurityUrl;
private static ConfigEntry<string> DiscordSecurityReportPrefix;
private static ConfigEntry<Incident> LogIncidents;
private DiscordService _discordService;
private StringBuilder _builder = new StringBuilder();
private string _cacheFilesFolder;
public static readonly UserInfo CommandsUserInfo = new UserInfo
{
Name = string.Empty,
UserId = new PlatformUserID("Bot", 0uL, false)
};
public static IpAddressFilter IpFilter = new IpAddressFilter();
private void Awake()
{
//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
//IL_01ca: 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. [Server restart required for update]");
Password = ((BaseUnityPlugin)this).Config.Bind<string>("1. Rcon", "Password", "", "Password for RCON packages validation. Empty password means plugin will not work! [Server restart required for update]");
WhiteListConfig = ((BaseUnityPlugin)this).Config.Bind<string>("1. Rcon", "Whitelist IP mask", "", "Comma-separated list of IP addresses or masks (e.g., 192.168.1.0/24, 10.0.0.1)");
BlackListConfig = ((BaseUnityPlugin)this).Config.Bind<string>("1. Rcon", "Blacklist IP mask", "", "Comma-separated list of IP addresses or masks (e.g., 192.168.1.0/24, 10.0.0.1)");
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");
DiscordSecurityReportPrefix = ((BaseUnityPlugin)this).Config.Bind<string>("4. Security", "Message prefix", "@here Security alert", "Prefix attached to every security report");
DiscordSecurityUrl = ((BaseUnityPlugin)this).Config.Bind<string>("4. Security", "Webhook url", "", "Discord webhook for sending security reports");
LogIncidents = ((BaseUnityPlugin)this).Config.Bind<Incident>("4. Security", "Incidents", Incident.UnauthorizedAccess | Incident.UnexpectedBehaviour, "Incident types will be reported to discord");
CommandsUserInfo.Name = ServerChatName.Value;
_cacheFilesFolder = Path.Combine(Paths.CachePath, "Valheim Rcon");
ClearCacheDirectory(_cacheFilesFolder);
_discordService = new DiscordService();
RefreshIpFilter();
Helper.WatchConfigFileChanges(((BaseUnityPlugin)this).Config, RefreshIpFilter);
Object.DontDestroyOnLoad((Object)new GameObject("RconProxy", new Type[1] { typeof(RconProxy) }));
RconProxy.Instance.OnCommandCompleted += SendResultToDiscord;
RconProxy.Instance.OnSecurityReport += SendReportToDiscord;
Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
if (string.IsNullOrWhiteSpace(Password.Value))
{
Log.Error("Password is empty. Plugin will not work. Please configure a secure password and restart the server.");
}
else
{
RconCommandsUtil.RegisterAllCommands(Assembly.GetExecutingAssembly());
}
}
private void OnDestroy()
{
_discordService.Dispose();
}
private void SendResultToDiscord(IRconPeer peer, string command, IReadOnlyList<string> args, CommandResult result)
{
string value = DiscordUrl.Value;
if (string.IsNullOrEmpty(value))
{
return;
}
string arg = command + " " + string.Join(" ", args);
_builder.Clear();
_builder.AppendLine($"> {peer.Address} -> {arg}");
if (_builder.Length > 1900)
{
string value2 = RconCommandsUtil.TruncateMessage(_builder.ToString(), 200);
_builder.Clear();
_builder.Append(value2);
_builder.AppendLine("...");
}
int num = 1900 - _builder.Length;
if (result.Text.Length > num)
{
string value3 = RconCommandsUtil.TruncateMessage(result.Text, 200);
_builder.AppendLine(value3);
_builder.Append("*--- message truncated ---*");
_discordService.SendResult(value, _builder.ToString(), result.AttachedFilePath);
string text = Path.Combine(_cacheFilesFolder, $"{DateTime.UtcNow.Ticks}.txt");
FileHelpers.EnsureDirectoryExists(text);
File.WriteAllText(text, result.Text);
FileInfo fileInfo = new FileInfo(text);
Log.Debug($"Saved full result {text}. Size {(float)fileInfo.Length / 1024f}kb");
if (fileInfo.Length > 10485760)
{
string text2 = Path.Combine(Utils.GetSaveDataPath((FileSource)1), "Valheim Rcon", $"{command}_{DateTime.UtcNow:yyyy_MM_dd_HH_mm_ss}.txt");
FileHelpers.EnsureDirectoryExists(text2);
File.Copy(text, text2, overwrite: true);
string text3 = "The result is too long to send to Discord. It has been saved on the server: `" + text2 + "`";
_discordService.SendResult(value, text3, "");
Log.Message(text3);
}
else
{
_discordService.SendResult(value, "**Full message**", text);
}
}
else
{
_builder.Append(result.Text);
_discordService.SendResult(value, _builder.ToString(), result.AttachedFilePath);
}
}
private void SendReportToDiscord(object endPoint, Incident incident, string reason)
{
if (!LogIncidents.Value.HasFlag(incident))
{
return;
}
string value = DiscordSecurityUrl.Value;
if (!string.IsNullOrEmpty(value))
{
_builder.Clear();
if (!string.IsNullOrEmpty(DiscordSecurityReportPrefix.Value))
{
_builder.AppendLine(DiscordSecurityReportPrefix.Value);
}
_builder.AppendFormat("[`{0}`] Disconnected for security reasons", endPoint);
_builder.AppendLine();
_builder.AppendFormat("**Reason**: {0}", reason);
_discordService.SendResult(value, _builder.ToString(), null);
}
}
private void RefreshIpFilter()
{
IEnumerable<string> blackList = ParseList(BlackListConfig.Value);
IEnumerable<string> whiteList = ParseList(WhiteListConfig.Value);
IpFilter.RefreshFilter(whiteList, blackList);
Log.Debug($"IP filter updated {IpFilter}");
}
private static IEnumerable<string> ParseList(string config)
{
return config.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
private static void ClearCacheDirectory(string cacheDirectory)
{
if (Directory.Exists(cacheDirectory))
{
DirectoryInfo directoryInfo = new DirectoryInfo(cacheDirectory);
Log.Info($"Clearing cache. Files {directoryInfo.GetFiles().Length}, directories {directoryInfo.GetDirectories().Length}");
Directory.Delete(cacheDirectory, recursive: true);
}
}
}
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;
internal event SecurityReportHandler OnSecurityReport;
private void Awake()
{
Instance = this;
_connectionManager = new AsynchronousSocketListener(IPAddress.Any, Plugin.Port.Value, HandleSecurityReport, Plugin.IpFilter);
_receiver = new RconCommandReceiver(_connectionManager, Plugin.Password.Value, HandleCommandAsync, HandleSecurityReport);
}
internal void Startup()
{
_connectionManager.StartListening();
}
internal void ShutDown()
{
_receiver.Dispose();
_connectionManager.Dispose();
}
private void Update()
{
_receiver.Update();
}
private void HandleSecurityReport(object endPoint, Incident incident, string reason)
{
this.OnSecurityReport?.Invoke(endPoint, incident, reason);
}
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
{
private static readonly int TagZdoHash = StringExtensionMethods.GetStableHashCode("valheim_rcon_object_tag");
public static string GetTag(this ZDO zdo)
{
return zdo.GetString(TagZdoHash, "");
}
public static void SetTag(this ZDO zdo, string tag)
{
zdo.Set(TagZdoHash, tag);
}
public static void SetZdoModified(this ZDO zdo)
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
zdo.SetOwner(ZDOMan.GetSessionID());
zdo.DataRevision += 100;
ZDOMan.instance.ForceSendZDO(zdo.m_uid);
}
public static string GetPrefabName(int prefabId)
{
GameObject prefab = ZNetScene.instance.GetPrefab(prefabId);
if (!((Object)(object)prefab != (Object)null))
{
return $"Unknown ({prefabId})";
}
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 (zdo.GetPrefabName().StartsWith("_"))
{
return false;
}
return true;
}
}
}
namespace ValheimRcon.ZDOInfo
{
internal class CommonZDOInfoProvider : IZDOInfoProvider
{
public void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
//IL_0045: 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_0064: Unknown result type (might be due to invalid IL or missing references)
//IL_007b: Unknown result type (might be due to invalid IL or missing references)
stringBuilder.AppendFormat("Prefab: {0}", zdo.GetPrefabName());
stringBuilder.AppendFormat(" Id: {0}:{1}", ((ZDOID)(ref zdo.m_uid)).ID, ((ZDOID)(ref zdo.m_uid)).UserID);
stringBuilder.AppendFormat(" Position: {0}", zdo.GetPosition().ToDisplayFormat());
if (detailed)
{
stringBuilder.AppendFormat(" Zone: {0}", ZoneSystem.GetZone(zdo.GetPosition()).ToDisplayFormat());
stringBuilder.AppendFormat(" Rotation: {0}", zdo.GetRotation().ToDisplayFormat());
}
string tag = zdo.GetTag();
if (!string.IsNullOrEmpty(tag))
{
stringBuilder.Append(" Tag: " + tag);
}
}
public bool IsAvailableTo(ZDO zdo)
{
return true;
}
}
internal class BedZDOInfoProvider : ZDOInfoProviderBase<Bed>
{
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
string @string = zdo.GetString(ZDOVars.s_ownerName, "");
long @long = zdo.GetLong(ZDOVars.s_owner, 0L);
stringBuilder.Append("Owner: ");
if (string.IsNullOrEmpty(@string))
{
stringBuilder.Append("<not claimed>");
}
else
{
stringBuilder.AppendFormat("{0}({1})", @string, @long);
}
}
}
internal class BuildingZDOInfoProvider : IZDOInfoProvider
{
private readonly Dictionary<int, bool> _prefabs = new Dictionary<int, bool>();
private readonly Dictionary<int, float> _maxHealth = new Dictionary<int, float>();
private readonly Dictionary<int, float> _maxSupport = new Dictionary<int, float>();
public void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
stringBuilder.Append($"Creator: {zdo.GetLong(ZDOVars.s_creator, 0L)}");
float value;
float num = (_maxHealth.TryGetValue(zdo.GetPrefab(), out value) ? value : 0f);
stringBuilder.Append(" Health: " + zdo.GetFloat(ZDOVars.s_health, num).ToDisplayFormat());
if (detailed)
{
float value2;
float num2 = (_maxSupport.TryGetValue(zdo.GetPrefab(), out value2) ? value2 : 0f);
stringBuilder.Append(" Support: " + zdo.GetFloat(ZDOVars.s_support, num2).ToDisplayFormat());
}
}
public bool IsAvailableTo(ZDO zdo)
{
int prefab = zdo.GetPrefab();
if (_prefabs.TryGetValue(prefab, out var value))
{
return value;
}
GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
WearNTear val = (((Object)(object)prefab2 != (Object)null) ? prefab2.GetComponentInChildren<WearNTear>() : null);
value = (Object)(object)val != (Object)null;
_prefabs[prefab] = value;
if (!value)
{
return false;
}
_maxHealth[prefab] = val.m_health;
_maxSupport[prefab] = val.m_support;
return true;
}
}
internal class CharacterZDOInfoProvider : ZDOInfoProviderBase<Character>
{
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
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)}");
string @string = zdo.GetString(ZDOVars.s_tamedName, "");
string string2 = zdo.GetString(ZDOVars.s_tamedNameAuthor, "");
if (!string.IsNullOrEmpty(@string))
{
stringBuilder.AppendFormat(" Name: {0} (author: {1})", @string, string2);
}
}
}
internal class ContainerZDOInfoProvider : ZDOInfoProviderBase<Container>
{
private static readonly Inventory TempInventory = new Inventory("", (Sprite)null, 8, 5);
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Expected O, but got Unknown
TempInventory.RemoveAll();
string @string = zdo.GetString(ZDOVars.s_items, "");
if (!string.IsNullOrEmpty(@string))
{
ZPackage val = new ZPackage(@string);
TempInventory.Load(val);
}
List<ItemData> inventory = TempInventory.m_inventory;
stringBuilder.Append("Container: ");
int count = inventory.Count;
if (count == 0)
{
stringBuilder.Append("<empty>");
}
else
{
stringBuilder.AppendFormat("{0} items", count);
}
}
}
internal class CustomZDOInfoProvider : IZDOInfoProvider
{
private readonly HashSet<IZDOInfoProvider> _providers = new HashSet<IZDOInfoProvider>();
public CustomZDOInfoProvider(params IZDOInfoProvider[] providers)
{
_providers = providers.ToHashSet();
}
public void AddProvider(IZDOInfoProvider provider)
{
_providers.Add(provider);
}
public void RemoveProvider(IZDOInfoProvider provider)
{
_providers.Remove(provider);
}
public void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
bool flag = false;
foreach (IZDOInfoProvider provider in _providers)
{
if (provider.IsAvailableTo(zdo))
{
if (flag)
{
stringBuilder.Append(' ');
}
provider.AppendInfo(zdo, stringBuilder, detailed);
flag = true;
}
}
}
public bool IsAvailableTo(ZDO zdo)
{
if (_providers.Count == 0)
{
return false;
}
foreach (IZDOInfoProvider provider in _providers)
{
if (provider.IsAvailableTo(zdo))
{
return true;
}
}
return false;
}
}
internal class GuardStoneZDOInfoProvider : ZDOInfoProviderBase<PrivateArea>
{
[CompilerGenerated]
private sealed class <GetPermittedPlayers>d__1 : IEnumerable<(long Id, string Name)>, IEnumerable, IEnumerator<(long Id, string Name)>, IDisposable, IEnumerator
{
private int <>1__state;
private (long Id, string Name) <>2__current;
private int <>l__initialThreadId;
private ZDO zdo;
public ZDO <>3__zdo;
private int <count>5__2;
private int <i>5__3;
(long, string) IEnumerator<(long, string)>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <GetPermittedPlayers>d__1(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<(long Id, string Name)> IEnumerable<(long, string)>.GetEnumerator()
{
<GetPermittedPlayers>d__1 <GetPermittedPlayers>d__;
if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
{
<>1__state = 0;
<GetPermittedPlayers>d__ = this;
}
else
{
<GetPermittedPlayers>d__ = new <GetPermittedPlayers>d__1(0);
}
<GetPermittedPlayers>d__.zdo = <>3__zdo;
return <GetPermittedPlayers>d__;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<(long, string)>)this).GetEnumerator();
}
}
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
stringBuilder.Append($"Enabled: {zdo.GetBool(ZDOVars.s_enabled, false)}");
stringBuilder.Append(" Owner: " + zdo.GetString(ZDOVars.s_creatorName, ""));
stringBuilder.Append(" Permitted:");
IEnumerable<(long, string)> permittedPlayers = GetPermittedPlayers(zdo);
if (!permittedPlayers.Any())
{
stringBuilder.Append(" <empty>");
return;
}
foreach (var item in permittedPlayers)
{
stringBuilder.Append($" {item}");
}
}
[IteratorStateMachine(typeof(<GetPermittedPlayers>d__1))]
private static IEnumerable<(long Id, string Name)> GetPermittedPlayers(ZDO zdo)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <GetPermittedPlayers>d__1(-2)
{
<>3__zdo = zdo
};
}
}
internal class ItemDropZDOInfoProvider : ZDOInfoProviderBase<ItemDrop>
{
private readonly ItemData _tempData = new ItemData();
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
ItemDrop.LoadFromZDO(_tempData, zdo);
ZDOInfoUtil.AppendItemInfo(_tempData, stringBuilder);
}
}
internal class ArmorStandZDOInfoProvider : IZDOInfoProvider
{
private readonly Dictionary<int, int> _itemStandSlots = new Dictionary<int, int>();
private readonly ItemData _tempData = new ItemData();
public void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed = true)
{
stringBuilder.AppendFormat("Pose: {0}", zdo.GetInt(ZDOVars.s_pose, 0));
stringBuilder.AppendFormat(" Attached items: ");
int num = _itemStandSlots[zdo.GetPrefab()];
bool flag = false;
for (int i = 0; i < num; i++)
{
string @string = zdo.GetString($"{i}_item", "");
if (!string.IsNullOrEmpty(@string))
{
if (flag)
{
stringBuilder.Append(',');
}
if (detailed)
{
ItemDrop.LoadFromZDO(i, _tempData, zdo);
stringBuilder.AppendFormat("( {0} ", @string);
ZDOInfoUtil.AppendItemInfo(_tempData, stringBuilder);
stringBuilder.Append(')');
}
else
{
stringBuilder.Append(@string);
}
flag = true;
}
}
if (!flag)
{
stringBuilder.Append("<empty>");
}
}
public bool IsAvailableTo(ZDO zdo)
{
int prefab = zdo.GetPrefab();
if (_itemStandSlots.TryGetValue(prefab, out var value))
{
return value > 0;
}
GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
ArmorStand val = default(ArmorStand);
value = (((Object)(object)prefab2 != (Object)null && prefab2.TryGetComponent<ArmorStand>(ref val)) ? val.m_slots.Count : 0);
_itemStandSlots[prefab] = value;
return value > 0;
}
}
internal class ItemStandZDOInfoProvider : ZDOInfoProviderBase<ItemStand>
{
private readonly ItemData _tempData = new ItemData();
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
string @string = zdo.GetString(ZDOVars.s_item, "");
stringBuilder.AppendFormat(" Attached item: ");
if (string.IsNullOrEmpty(@string))
{
stringBuilder.Append("<empty>");
return;
}
stringBuilder.Append(@string);
if (detailed)
{
stringBuilder.Append(' ');
ItemDrop.LoadFromZDO(_tempData, zdo);
ZDOInfoUtil.AppendItemInfo(_tempData, stringBuilder);
}
}
}
public interface IZDOInfoProvider
{
bool IsAvailableTo(ZDO zdo);
void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed);
}
internal class PortalZDOInfoProvider : ZDOInfoProviderBase<TeleportWorld>
{
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
string @string = zdo.GetString(ZDOVars.s_tag, "");
string string2 = zdo.GetString(ZDOVars.s_tagauthor, "");
stringBuilder.AppendFormat("Portal tag: {0} (author {1})", @string, string2);
}
}
internal class SignZDOInfoProvider : ZDOInfoProviderBase<Sign>
{
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
string @string = zdo.GetString(ZDOVars.s_text, "");
string string2 = zdo.GetString(ZDOVars.s_author, "");
if (!string.IsNullOrEmpty(@string))
{
stringBuilder.AppendFormat("Text: {0} (author: {1})", @string, string2);
}
}
}
internal class TombStoneZDOInfoProvider : ZDOInfoProviderBase<TombStone>
{
public override void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed)
{
string @string = zdo.GetString(ZDOVars.s_ownerName, "");
long @long = zdo.GetLong(ZDOVars.s_owner, 0L);
stringBuilder.AppendFormat("Tombstone: {0}({1})", @string, @long);
}
}
public abstract class ZDOInfoProviderBase<T> : IZDOInfoProvider where T : Component
{
private readonly Dictionary<int, bool> _prefabs = new Dictionary<int, bool>();
public virtual bool IsAvailableTo(ZDO zdo)
{
int prefab = zdo.GetPrefab();
if (_prefabs.TryGetValue(prefab, out var value))
{
return value;
}
GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
value = (Object)(object)prefab2 != (Object)null && (Object)(object)prefab2.GetComponentInChildren<T>() != (Object)null;
_prefabs[prefab] = value;
return value;
}
public abstract void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed = true);
}
public static class ZDOInfoUtil
{
private static readonly CustomZDOInfoProvider _externalProvider = new CustomZDOInfoProvider();
private static readonly CustomZDOInfoProvider _globalProvider = new CustomZDOInfoProvider(new CommonZDOInfoProvider(), new BuildingZDOInfoProvider(), new ItemDropZDOInfoProvider(), new CharacterZDOInfoProvider(), new GuardStoneZDOInfoProvider(), new ArmorStandZDOInfoProvider(), new ItemStandZDOInfoProvider(), new ContainerZDOInfoProvider(), new BedZDOInfoProvider(), new TombStoneZDOInfoProvider(), new SignZDOInfoProvider(), new PortalZDOInfoProvider(), _externalProvider);
public static void AppendInfo(ZDO zdo, StringBuilder stringBuilder, bool detailed = true)
{
_globalProvider.AppendInfo(zdo, stringBuilder, detailed);
if (!zdo.Persistent)
{
stringBuilder.Append(" [NOT PERSISTENT]");
}
}
public static void AppendItemInfo(ItemData item, StringBuilder stringBuilder)
{
stringBuilder.AppendFormat("Stack: {0}", item.m_stack);
stringBuilder.AppendFormat(" Quality: {0}", item.m_quality);
if (item.m_variant != 0)
{
stringBuilder.AppendFormat(" Variant: {0}", item.m_variant);
}
if (item.m_crafterID != 0L)
{
stringBuilder.AppendFormat(" Crafter: {0} ({1})", item.m_crafterName, item.m_crafterID);
}
if (item.m_customData.Count <= 0)
{
return;
}
stringBuilder.Append(" Data:");
foreach (KeyValuePair<string, string> customDatum in item.m_customData)
{
stringBuilder.Append(" '" + customDatum.Key + "'='" + customDatum.Value + "'");
}
}
public static void RegisterInfoProvider(IZDOInfoProvider provider)
{
_externalProvider.AddProvider(provider);
}
}
}
namespace ValheimRcon.Core
{
internal 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 SecurityReportHandler _securityReportHandler;
private readonly IpAddressFilter _filter;
private bool _checkNewConnections;
public event Action<IRconPeer, RconPacket> OnMessage;
public AsynchronousSocketListener(IPAddress ipAddress, int port, SecurityReportHandler securityReportHandler, IpAddressFilter filter)
{
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;
_filter = filter;
_listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_securityReportHandler = securityReportHandler;
}
public void StartListening()
{
Log.Message("Start listening rcon commands");
try
{
IPEndPoint localEP = new IPEndPoint(_address, _port);
_listener.Bind(localEP);
_listener.Listen(100);
_checkNewConnections = true;
}
catch (Exception msg)
{
Log.Error(msg);
}
}
public void Update()
{
TryAcceptNewClients();
DateTime now = DateTime.Now;
foreach (IRconPeer client in _clients)
{
RconPacket packet;
string error;
if (!client.IsConnected())
{
Disconnect(client);
}
else if (!client.Authentificated && now > client.Created + UnauthorizedClientLifetime)
{
Log.Warning($"Unauthorized timeout [{client.Address}]");
_securityReportHandler?.Invoke(client.Address, Incident.UnexpectedBehaviour, "Unauthorized timeout.");
Disconnect(client);
}
else if (!_filter.IsAllowed(client.Address))
{
Log.Warning($"Disconnected by IP filter [{client.Address}]");
_securityReportHandler?.Invoke(client.Address, Incident.IpFilter, "Disconnected by IP filter.");
Disconnect(client);
}
else if (client.TryReceive(out packet, out error))
{
this.OnMessage?.Invoke(client, packet);
}
else if (!string.IsNullOrEmpty(error))
{
_securityReportHandler?.Invoke(client.Address, Incident.UnexpectedBehaviour, error);
Disconnect(client);
}
}
foreach (IRconPeer item in _waitingForDisconnect)
{
_clients.Remove(item);
DisconnectPeer(item);
}
_waitingForDisconnect.Clear();
}
public void Dispose()
{
_checkNewConnections = false;
_listener.Close();
foreach (IRconPeer client in _clients)
{
client.Dispose();
}
_clients.Clear();
_waitingForDisconnect.Clear();
}
public void Disconnect(IRconPeer peer)
{
_waitingForDisconnect.Add(peer);
}
private void TryAcceptNewClients()
{
if (!_checkNewConnections)
{
return;
}
try
{
if (_listener.Poll(0, SelectMode.SelectRead))
{
Socket socket = _listener.Accept();
OnClientConnected(socket);
}
}
catch (Exception msg)
{
Log.Error(msg);
}
}
private void OnClientConnected(Socket socket)
{
if (!(socket.RemoteEndPoint is IPEndPoint iPEndPoint))
{
Log.Warning("Client connected with invalid endpoint");
_securityReportHandler?.Invoke(socket.RemoteEndPoint, Incident.IpFilter, "Rejected connection. Unknown endpoint.");
socket.Close();
return;
}
IPAddress address = iPEndPoint.Address;
if (!_filter.IsAllowed(address))
{
Log.Warning($"Client connection rejected from [{iPEndPoint}] - IP not allowed");
_securityReportHandler?.Invoke(address, Incident.IpFilter, "Rejected connection by IP filter.");
socket.Close();
}
else
{
RconPeer rconPeer = new RconPeer(socket);
Log.Debug($"Client connected [{rconPeer.Address}]");
_clients.Add(rconPeer);
}
}
private void DisconnectPeer(IRconPeer peer)
{
Log.Debug($"Client disconnected [{peer.Address}]");
try
{
peer.Dispose();
}
catch
{
Log.Debug("Warning: Could not dispose peer connection");
}
}
}
public interface IRconConnectionManager : IDisposable
{
event Action<IRconPeer, RconPacket> OnMessage;
void StartListening();
void Update();
void Disconnect(IRconPeer peer);
}
public interface IRconPeer : IDisposable
{
bool Authentificated { get; }
IPAddress Address { get; }
DateTime Created { get; }
void SetAuthentificated(bool authentificated);
Task SendAsync(RconPacket packet);
bool IsConnected();
bool TryReceive(out RconPacket packet, out string error);
}
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;
private readonly SecurityReportHandler _securityReportHandler;
public RconCommandReceiver(IRconConnectionManager connectionManager, string password, RconCommandHandler commandHandler, SecurityReportHandler securityReportHandler)
{
if (password == null)
{
throw new ArgumentException("Password cannot be null", "password");
}
if (commandHandler == null)
{
throw new ArgumentNullException("commandHandler");
}
_password = password;
_manager = connectionManager;
_manager.OnMessage += SocketListener_OnMessage;
_commandHandler = commandHandler;
_securityReportHandler = securityReportHandler;
}
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.Warning($"Already authorized [{peer.Address}]");
_securityReportHandler?.Invoke(peer.Address, Incident.UnexpectedBehaviour, "Already authorized.");
await peer.SendAsync(new RconPacket(packet.requestId, PacketType.Command, "Already authorized"));
_manager.Disconnect(peer);
break;
}
bool success = !string.IsNullOrWhiteSpace(_password) && string.Equals(packet.payload ?? string.Empty, _password);
RconPacket rconPacket;
if (success)
{
peer.SetAuthentificated(authentificated: true);
rconPacket = new RconPacket(packet.requestId, PacketType.Command, "Login success");
}
else
{
rconPacket = new RconPacket(-1, PacketType.Command, "Login failed");
}
Log.Debug($"Login result {rconPacket}");
await peer.SendAsync(rconPacket);
if (!success)
{
_securityReportHandler?.Invoke(peer.Address, Incident.UnauthorizedAccess, "Login failed.");
_manager.Disconnect(peer);
}
break;
}
case PacketType.Command:
{
if (!peer.Authentificated)
{
Log.Warning($"Unauthorized access attempt [{peer.Address}]");
_securityReportHandler?.Invoke(peer.Address, Incident.UnauthorizedAccess, "Unauthorized access attempt.");
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.Address}]");
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 rconPacket2 = new RconPacket(packet.requestId, packet.type, payload);
Log.Debug($"Command result {command} - {rconPacket2}");
await peer.SendAsync(rconPacket2);
break;
}
default:
Log.Warning($"Unknown packet type: {packet} [{peer.Address}]");
_securityReportHandler?.Invoke(peer.Address, Incident.UnexpectedBehaviour, $"Unknown packet type {packet}.");
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;
private readonly IPAddress _address;
public DateTime Created { get; }
public bool Authentificated { get; private set; }
public IPAddress Address
{
get
{
if (_disposed)
{
return null;
}
return _address;
}
}
public RconPeer(Socket workSocket)
{
if (workSocket == null)
{
throw new ArgumentNullException("workSocket");
}
_socket = workSocket;
_address = (_socket.RemoteEndPoint as IPEndPoint).Address;
Created = DateTime.Now;
}
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 [{Address}]");
}
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, out string error)
{
packet = default(RconPacket);
error = null;
if (_disposed)
{
return false;
}
if (_socket.Poll(0, SelectMode.SelectRead) && _socket.Available > 0)
{
int available = _socket.Available;
if (available > _buffer.Length)
{
error = $"Available data exceeds buffer size: {available} > {_buffer.Length}";
Log.Warning($"{error} [{Address}]");
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 [{Address}]");
return true;
}
catch (Exception ex)
{
Log.Warning($"Failed to parse packet from [{Address}]: {ex.Message}");
error = ex.Message;
return false;
}
finally
{
Array.Clear(_buffer, 0, _buffer.Length);
}
}
return false;
}
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();
}
}
}
public delegate void SecurityReportHandler(object endPoint, Incident incident, string reason);
public class IpAddressFilter
{
private List<IPNetwork> _whiteList = new List<IPNetwork>();
private List<IPNetwork> _blackList = new List<IPNetwork>();
public void RefreshFilter(IEnumerable<string> whiteList, IEnumerable<string> blackList)
{
_whiteList.Clear();
_blackList.Clear();
if (ParseConfigs(whiteList, out var validNetworks))
{
_whiteList.AddRange(validNetworks);
}
else
{
_whiteList.Add(IPNetwork.Parse("127.0.0.1"));
}
ParseConfigs(blackList, out var validNetworks2);
_blackList.AddRange(validNetworks2);
}
public bool IsAllowed(IPAddress address)
{
if (address == null)
{
return false;
}
if (_blackList.Count > 0 && IsInList(address, _blackList))
{
return false;
}
if (_whiteList.Count != 0)
{
return IsInList(address, _whiteList);
}
return true;
}
private static bool IsInList(IPAddress address, IReadOnlyCollection<IPNetwork> list)
{
foreach (IPNetwork item in list)
{
if (IPNetwork.Contains(item, address))
{
return true;
}
}
return false;
}
private static bool ParseConfigs(IEnumerable<string> configs, out IReadOnlyCollection<IPNetwork> validNetworks)
{
List<IPNetwork> list = (List<IPNetwork>)(validNetworks = new List<IPNetwork>());
if (configs == null)
{
return true;
}
bool result = true;
foreach (string config in configs)
{
try
{
IPNetwork item = Parse(config.Trim());
list.Add(item);
}
catch (Exception arg)
{
Log.Error($"Cannot parse config {config} - {arg}");
}
}
return result;
}
private static IPNetwork Parse(string config)
{
string[] array = config.Split(new char[1] { '/' });
if (array.Length == 2)
{
string ipaddress = array[0];
byte cidr = byte.Parse(array[1]);
return IPNetwork.Parse(ipaddress, cidr);
}
if (array.Length == 1)
{
return IPNetwork.Parse(config, 32);
}
throw new ArgumentException("Invalid network " + config);
}
public override string ToString()
{
return "IpAddressFilter - whitelist: " + string.Join(",", _whiteList) + " blacklist: " + string.Join(",", _blackList);
}
}
[Flags]
public enum Incident
{
IpFilter = 2,
UnauthorizedAccess = 4,
UnexpectedBehaviour = 8
}
}
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;
}
}
public class CommandArgs
{
[CompilerGenerated]
private sealed class <GetOptionalArguments>d__17 : IEnumerable<(int Index, string Argument)>, IEnumerable, IEnumerator<(int Index, string Argument)>, IDisposable, IEnumerator
{
private int <>1__state;
private (int Index, string Argument) <>2__current;
private int <>l__initialThreadId;
public CommandArgs <>4__this;
private int <i>5__2;
(int, string) IEnumerator<(int, string)>.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_0076;
}
<>1__state = -1;
<i>5__2 = 0;
goto IL_0086;
IL_0076:
<i>5__2++;
goto IL_0086;
IL_0086:
if (<i>5__2 < commandArgs.Arguments.Count)
{
if (OptionalArgumentRegex.IsMatch(commandArgs.Arguments[<i>5__2]))
{
<>2__current = (<i>5__2, commandArgs.Arguments[<i>5__2]);
<>1__state = 1;
return true;
}
goto IL_0076;
}
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 Index, string Argument)> IEnumerable<(int, string)>.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, string)>)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 Index, string Argument)> 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 Vector2i GetVector2i(this CommandArgs args, int index)
{
//IL_0010: Unknown result type (might be due to invalid IL or missing references)
return new Vector2i(args.GetInt(index), args.GetInt(index + 1));
}
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 -near <x> <y> <z> <radius> -zone <x> <y> -prefab <prefab> -creator <creator id> -id <id:userid> -tag <tag> -force (bypass security checks)";
protected override string OnHandle(CommandArgs args)
{
//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
bool flag = false;
_criterias.Clear();
bool flag2 = false;
string text = null;
foreach (var (num, text2) in args.GetOptionalArguments())
{
switch (text2.ToLower())
{
case "-creator":
_criterias.Add(new CreatorCriteria(args.GetLong(num + 1)));
break;
case "-id":
_criterias.Add(new IdCriteria(args.GetObjectId(num + 1)));
break;
case "-prefab":
text = args.GetString(num + 1);
_criterias.Add(new PrefabCriteria(text));
break;
case "-near":
_criterias.Add(new NearCriteria(args.GetVector3(num + 1), args.GetFloat(num + 4)));
flag2 = true;
break;
case "-zone":
_criterias.Add(new ZoneCriteria(args.GetVector2i(num + 1)));
flag2 = true;
break;
case "-tag":
_criterias.Add(new TagCriteria(args.GetString(num + 1)));
break;
case "-force":
flag = true;
break;
default:
return "Unknown argument: " + text2;
}
}
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.";
}
if (flag2 && _criterias.Count == 1)
{
return "Must provide at least 1 more criteria if use -near or -zone";
}
if (!string.IsNullOrEmpty(text) && _criterias.Count == 1 && (Object)(object)ZNetScene.instance.GetPrefab(text) != (Object)null && !flag)
{
return $"You're about to delete all existing objects of prefab {text} ({array.Length}) in the world. Use -force if you really want to delete them.";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Deleting {array.Length} objects:");
int num2 = 0;
ZDO[] array2 = array;
foreach (ZDO val in array2)
{
stringBuilder.Append('-');
ZDOInfoUtil.AppendInfo(val, stringBuilder);
if (!val.Persistent)
{
stringBuilder.AppendLine(" [NOT ALLOWED TO DELETE]");
}
else if (flag || ZdoUtils.CanModifyZdo(val))
{
ZdoUtils.DeleteZDO(val);
stringBuilder.AppendLine(" [DELETED]");
num2++;
}
else
{
stringBuilder.AppendLine(" [NOT ALLOWED TO DELETE]");
}
stringBuilder.AppendLine();
}
stringBuilder.AppendFormat("Deleted {0}/{1} objects", num2, array.Length);
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> -zone <x> <y> -prefab <prefab> -creator <creator id> -id <id:userid> -tag <tag> -tag-old <tag> -detailed";
protected override string OnHandle(CommandArgs args)
{
//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
//IL_0225: Unknown result type (might be due to invalid IL or missing references)
_criterias.Clear();
bool detailed = false;
foreach (var (num, text) in args.GetOptionalArguments())
{
switch (text.ToLower())
{
case "-prefab":
_criterias.Add(new PrefabCriteria(args.GetString(num + 1)));
break;
case "-creator":
_criterias.Add(new CreatorCriteria(args.GetLong(num + 1)));
break;
case "-id":
_criterias.Add(new IdCriteria(args.GetObjectId(num + 1)));
break;
case "-tag":
_criterias.Add(new TagCriteria(args.GetString(num + 1)));
break;
case "-near":
_criterias.Add(new NearCriteria(args.GetVector3(num + 1), args.GetFloat(num + 4)));
break;
case "-zone":
_criterias.Add(new ZoneCriteria(args.GetVector2i(num + 1)));
break;
case "-tag-old":
_criterias.Add(new OldTagCriteria(args.GetString(num + 1)));
break;
case "-detailed":
detailed = true;
break;
default:
return "Unknown argument: " + text;
}
}
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 zdo2 in array2)
{
stringBuilder.Append('-');
ZDOInfoUtil.AppendInfo(zdo2, stringBuilder, detailed);
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> -prefab <prefab name> -force (bypass security checks)";
protected override string OnHandle(CommandArgs args)
{
//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: 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 Index, string Argument)> optionalArguments = args.GetOptionalArguments();
bool flag = false;
string text = string.Empty;
_modifications.Clear();
foreach (var item in optionalArguments)
{
var (num, _) = item;
switch (item.Argument)
{
case "-position":
_modifications.Add(new PositionModification(args.GetVector3(num + 1)));
break;
case "-rotation":
_modifications.Add(new RotationModification(args.GetVector3(num + 1)));
break;
case "-health":
_modifications.Add(new HealthModification(args.GetFloat(num + 1)));
break;
case "-tag":
_modifications.Add(new TagModification(args.GetString(num + 1)));
break;
case "-prefab":
text = args.GetString(num + 1);
_modifications.Add(new PrefabModification(text));
break;
case "-force":
flag = true;
break;
default:
return "Unknown argument: " + args.GetString(num);
}
}
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.";
}
if (!flag && !string.IsNullOrEmpty(text) && !ZNetScene.instance.HasPrefab(StringExtensionMethods.GetStableHashCode(text)))
{
return "Cannot find prefab with name " + text + ". If you know what you are doing, use -force option.";
}
foreach (IZdoModification modification in _modifications)
{
modification.Apply(val);
}
val.SetZdoModified();
builder.Clear();
builder.AppendLine("Object modified successfully");
builder.AppendFormat("{0} ", val.GetPrefabName());
ZDOInfoUtil.AppendInfo(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_0202: Unknown result type (might be due to invalid IL or missing references)
//IL_0207: Unknown result type (might be due to invalid IL or missing references)
//IL_020c: Unknown result type (might be due to invalid IL or missing references)
//IL_0297: Unknown result type (might be due to invalid IL or missing references)
//IL_029e: Unknown result type (might be due to invalid IL or missing references)
//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
//IL_02b9: Unknown result type (might be due to invalid IL or missing references)
//IL_02bd: 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)
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 (var (num4, text2) in args.GetOptionalArguments())
{
switch (text2)
{
case "-level":
case "-l":
num2 = args.GetInt(num4 + 1);
break;
case "-count":
case "-c":
num = args.GetInt(num4 + 1);
break;
case "-t":
case "-tag":
text = args.GetString(num4 + 1);
break;
case "-rot":
case "-rotation":
val = Quaternion.Euler(args.GetVector3(num4 + 1));
break;
case "-rad":
case "-radius":
num3 = args.GetFloat(num4 + 1);
break;
case "-tamed":
flag = true;
break;
default:
return "Unknown argument: " + text2;
}
}
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('-');
ZDOInfoUtil.AppendInfo(item, stringBuilder);
stringBuilder.AppendLine();
}
return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
}
}
internal class ServerTimeCommand : RconCommand
{
public override string Command => "time";
public override string Description => "Get the current server time and day.";
protected override string OnHandle(CommandArgs args)
{
double timeSeconds = ZNet.instance.GetTimeSeconds();
int currentDay = EnvMan.instance.GetCurrentDay();
return $"Current server time: {timeSeconds} sec. Day: {currentDay}";
}
}
internal class Unban : RconCommand
{
public override string Command => "unban";
public override string Description => "Unban a user from the server. Usage: unban <playername or steamid>";
protected override string OnHandle(CommandArgs args)
{
string @string = args.GetString(0);
ZNet.instance.Unban(@string);
return @string + " unbanned";
}
}
internal class WorldSave : RconCommand
{
public override string Command => "save";
public override string Description => "Save the current world state.";
protected override string OnHandle(CommandArgs args)
{
ZNet.instance.Save(false, false, false);
return "World save started";
}
}
}
namespace ValheimRcon.Commands.Container
{
public abstract class ContainerRconCommand : RconCommand
{
protected override string OnHandle(CommandArgs args)
{
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Expected O, but got Unknown
//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
//IL_00df: Expected O, but got Unknown
ObjectId objectId = args.GetObjectId(0);
ZDO val = Enumerable.FirstOrDefault(predicate: new IdCriteria(objectId).IsMatch, source: ZDOMan.instance.m_objectsByID.Values);
if (val == null)
{
return "No object found with the specified ID.";
}
int prefab = val.GetPrefab();
string prefabName = val.GetPrefabName();
GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab);
if ((Object)(object)prefab2 == (Object)null)
{
return "Failed to load prefab: " + prefabName;
}
Container componentInChildren = prefab2.GetComponentInChildren<Container>();
if ((Object)(object)componentInChildren == (Object)null)
{
return "Object " + prefabName + " is not a container.";
}
string @string = val.GetString(ZDOVars.s_items, "");
Inventory val2 = new Inventory(componentInChildren.m_name, componentInChildren.m_bkg, componentInChildren.m_width, componentInChildren.m_height);
if (!string.IsNullOrEmpty(@string))
{
ZPackage val3 = new ZPackage(@string);
val2.Load(val3);
}
return HandleInventory(args, val, val2, prefabName);
}
protected abstract string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName);
protected void SaveInventory(ZDO zdo, Inventory inventory)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Expected O, but got Unknown
ZPackage val = new ZPackage();
inventory.Save(val);
zdo.Set(ZDOVars.s_items, val.GetBase64());
zdo.SetZdoModified();
}
}
internal class ShowContainerInventory : ContainerRconCommand
{
public override string Command => "showContainer";
public override string Description => "Shows inventory contents of a container by object ID. Usage: showContainer <id:userid>";
protected override string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName)
{
if (inventory.NrOfItems() == 0)
{
return "Container is empty.";
}
StringBuilder stringBuilder = new StringBuilder();
ZDOInfoUtil.AppendInfo(zdo, stringBuilder);
stringBuilder.AppendLine();
stringBuilder.AppendFormat("Items ({0}):", inventory.NrOfItems());
List<ItemData> allItems = inventory.GetAllItems();
int num = 0;
foreach (ItemData item in allItems)
{
stringBuilder.AppendLine();
GameObject dropPrefab = item.m_dropPrefab;
string arg = ((dropPrefab != null) ? ((Object)dropPrefab).name : null) ?? item.m_shared.m_name;
stringBuilder.AppendFormat("[{0}] {1} ", num, arg);
ZDOInfoUtil.AppendItemInfo(item, stringBuilder);
num++;
}
return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
}
}
internal class AddItemToContainer : ContainerRconCommand
{
public override string Command => "addItemToContainer";
public override string Description => "Adds an item to a container's inventory. Usage: addItemToContainer <id:userid> <item_name> -count <count> -quality <quality> -variant <variant> -durability <durability> -data <key> <value> -nocrafter -force";
protected override string HandleInventory(CommandArgs args, ZDO zdo, Inventory inventory, string prefabName)
{
string @string = args.GetString(1);
GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(@string);
if ((Object)(object)itemPrefab == (Object)null)
{
return "Cannot find item prefab: " + @string;
}
ItemData itemData = itemPrefab.GetComponent<ItemDrop>().m_itemData;
SharedData shared = itemData.m_shared;
int num = 1;
int num2 = 1;
int num3 = 0;
bool flag = true;
string crafterName = Plugin.ServerChatName.Value;
long crafterID = -1L;
Dictionary<string, string> dictionary = new Dictionary<string, string>();
bool force = false;
float? num4 = null;
foreach (var (num5, text) in args.GetOptionalArguments())
{
switch (text)
{
case "-count":
num = args.GetInt(num5 + 1);
if (num < 1)
{
return "Count must be at least 1";
}
break;
case "-quality":
num2 = args.GetInt(num5 + 1);
if (num2 < 0)
{
return "Quality must be at least 0";
}
break;
case "-variant":
num3 = args.GetInt(num5 + 1);
if (num3 < 0)
{
return "Variant must be at least 0";
}
if (num3 > 0 && shared.m_variants == 0)
{
return "Item " + @string + " does not have variants";
}
if (num3 > shared.m_variants - 1)
{
return $"Item {@string} has only {shared.m_variants} variants";
}
break;
case "-nocrafter":
flag = false;
crafterID = 0L;
crafterName = string.Empty;
break;
case "-data":
{
string string2 = args.GetString(num5 + 1);
string value = args.TryGetString(num5 + 2);
dictionary[string2] = value;
break;
}
case "-durability":
num4 = args.GetFloat(num5 + 1);
if (num4.Value < 0f)
{
return "Durability must be at least 0";
}
break;
case "-force":
force = true;
break;
default:
return "Unknown argument: " + text;
}
}
if (!ContainerUtils.ValidateContainerModification(zdo, prefabName, force, out var error))
{
return error;
}
ItemData val = itemData.Clone();
val.m_quality = num2;
val.m_variant = num3;
val.m_stack = num;
if (flag)
{
val.m_crafterID = crafterID;
val.m_crafterName = crafterName;
}
else
{
val.m_crafterID = 0L;
val.m_crafterName = string.Empty;
}
val.m_customData = dictionary;
if (shared.m_useDurability)