Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Rise v0.1.0
plugins/Rise.dll
Decompiled an hour agousing System; 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.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Rise")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("Rise")] [assembly: AssemblyTitle("Rise")] [assembly: AssemblyVersion("0.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Rise { internal static class Commands { private static readonly DateTime StartedAt = DateTime.UtcNow; private const string Help = "Rise 0.1.0 commands:\n info world, players, uptime\n players | status | list connected players: name, platform id\n kick <name|id> kick a player\n ban <name|id> ban and kick a player\n unban <id|name> remove from the ban list\n banlist show the ban list\n say <text> message in the center of every player's screen\n save save the world now\n shutdown | quit | stop save the world and stop the server"; public static string Execute(string line) { line = (line ?? "").Trim(); if (line.Length == 0) { return ""; } int num = line.IndexOf(' '); string text = ((num < 0) ? line : line.Substring(0, num)).ToLowerInvariant(); string text2 = ((num < 0) ? "" : line.Substring(num + 1).Trim()); if (text == "help" || text == "?") { return "Rise 0.1.0 commands:\n info world, players, uptime\n players | status | list connected players: name, platform id\n kick <name|id> kick a player\n ban <name|id> ban and kick a player\n unban <id|name> remove from the ban list\n banlist show the ban list\n say <text> message in the center of every player's screen\n save save the world now\n shutdown | quit | stop save the world and stop the server"; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return "error: the world is not loaded yet"; } switch (text) { case "info": return Info(instance); case "list": case "players": case "status": return Players(instance); case "kick": return Kick(instance, text2); case "ban": return Ban(instance, text2); case "unban": if (text2.Length == 0) { return "usage: unban <id|name>"; } if (!instance.Banned.Contains(text2)) { return "not in the ban list: " + text2; } instance.Unban(text2); Plugin.Log.LogInfo((object)("RCON: unbanned " + text2)); return "unbanned " + text2; case "banlist": { List<string> banned = instance.Banned; if (banned.Count != 0) { return string.Join("\n", banned); } return "ban list is empty"; } case "say": case "broadcast": if (text2.Length == 0) { return "usage: say <text>"; } ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text2 }); Plugin.Log.LogInfo((object)("RCON: broadcast: " + text2)); return "sent"; case "save": instance.Save(true, false, false); Plugin.Log.LogInfo((object)"RCON: world saved"); return "world saved"; case "stop": case "quit": case "shutdown": Plugin.Log.LogInfo((object)"RCON: shutting down"); Application.Quit(); return "saving the world and shutting down"; default: return "unknown command: " + text + ". Type help"; } } private static string Info(ZNet net) { TimeSpan timeSpan = DateTime.UtcNow - StartedAt; return "world: " + net.GetWorldName() + "\n" + $"players: {Online(net).Count()}\n" + $"uptime: {(int)timeSpan.TotalHours}h {timeSpan.Minutes}m\n" + "rise: 0.1.0"; } private static string Players(ZNet net) { List<ZNetPeer> list = Online(net).ToList(); StringBuilder stringBuilder = new StringBuilder($"players: {list.Count}"); foreach (ZNetPeer item in list) { stringBuilder.Append('\n').Append(item.m_playerName).Append('\t') .Append(item.m_socket.GetHostName()); } return stringBuilder.ToString(); } private static IEnumerable<ZNetPeer> Online(ZNet net) { return from p in net.GetPeers() where p.IsReady() && !string.IsNullOrEmpty(p.m_playerName) select p; } private static ZNetPeer Find(ZNet net, string who) { return Online(net).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => p.m_socket.GetHostName() == who)) ?? Online(net).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer p) => string.Equals(p.m_playerName, who, StringComparison.OrdinalIgnoreCase))); } private static string Kick(ZNet net, string who) { if (who.Length == 0) { return "usage: kick <name|id>"; } ZNetPeer val = Find(net, who); if (val == null) { return "player not found: " + who; } string hostName = val.m_socket.GetHostName(); net.Kick(hostName); Plugin.Log.LogInfo((object)("RCON: kicked " + val.m_playerName + " (" + hostName + ")")); return "kicked " + val.m_playerName + " (" + hostName + ")"; } private static string Ban(ZNet net, string who) { if (who.Length == 0) { return "usage: ban <name|id>"; } ZNetPeer val = Find(net, who); string text = ((val != null) ? val.m_socket.GetHostName() : who); net.Ban(text); if (val != null) { net.Kick(text); } Plugin.Log.LogInfo((object)("RCON: banned " + text)); if (val == null) { return "banned " + text; } return "banned and kicked " + val.m_playerName + " (" + text + ")"; } } [BepInPlugin("rise.valheim", "Rise", "0.1.0")] public class Plugin : BaseUnityPlugin { private class Job { public string Command; public string Result; public readonly ManualResetEventSlim Done = new ManualResetEventSlim(); } public const string Guid = "rise.valheim"; public const string Name = "Rise"; public const string Version = "0.1.0"; internal static ManualLogSource Log; private ConfigEntry<bool> rconEnabled; private ConfigEntry<string> rconBind; private ConfigEntry<int> rconPort; private ConfigEntry<string> rconPassword; private RconServer rcon; private readonly ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>(); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; rconEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Rcon", "Enabled", true, "Enable Source RCON"); rconBind = ((BaseUnityPlugin)this).Config.Bind<string>("Rcon", "Bind", "0.0.0.0", "Address to listen on. 127.0.0.1 allows local connections only"); rconPort = ((BaseUnityPlugin)this).Config.Bind<int>("Rcon", "Port", 2459, "RCON TCP port. The game uses 2456-2458"); rconPassword = ((BaseUnityPlugin)this).Config.Bind<string>("Rcon", "Password", "", "RCON password. While it is empty, RCON is off"); if (!rconEnabled.Value) { Log.LogInfo((object)"RCON is disabled in the config"); return; } if (string.IsNullOrEmpty(rconPassword.Value)) { Log.LogWarning((object)("RCON is off: set Password in " + ((BaseUnityPlugin)this).Config.ConfigFilePath + " and restart the server")); return; } rcon = new RconServer(rconBind.Value, rconPort.Value, rconPassword.Value, Run); rcon.Start(); } private void OnDestroy() { rcon?.Stop(); } private void Update() { Job result; while (jobs.TryDequeue(out result)) { try { result.Result = Commands.Execute(result.Command); } catch (Exception ex) { result.Result = "error: " + ex.Message; Log.LogError((object)$"Command '{result.Command}' failed: {ex}"); } result.Done.Set(); } } private string Run(string command) { Job job = new Job { Command = command }; jobs.Enqueue(job); if (!job.Done.Wait(TimeSpan.FromSeconds(15.0))) { return "error: server is busy (loading or saving), try again"; } return job.Result; } } internal sealed class RconServer { private const int TypeResponse = 0; private const int TypeExec = 2; private const int TypeAuthResponse = 2; private const int TypeAuth = 3; private const int MaxBody = 4000; private const int MaxClients = 8; private const int MaxFails = 5; private static readonly TimeSpan BlockFor = TimeSpan.FromMinutes(5.0); private readonly string bind; private readonly int port; private readonly byte[] password; private readonly Func<string, string> run; private TcpListener listener; private volatile bool stopping; private int clients; private readonly Dictionary<IPAddress, (int fails, DateTime until)> fails = new Dictionary<IPAddress, (int, DateTime)>(); public RconServer(string bind, int port, string password, Func<string, string> run) { this.bind = bind; this.port = port; this.password = Encoding.UTF8.GetBytes(password); this.run = run; } public void Start() { try { listener = new TcpListener(IPAddress.Parse(bind), port); listener.Start(); } catch (Exception ex) { Plugin.Log.LogError((object)$"RCON failed to start on {bind}:{port}: {ex.Message}"); return; } Thread thread = new Thread(Accept); thread.IsBackground = true; thread.Name = "Rise RCON"; thread.Start(); Plugin.Log.LogInfo((object)$"RCON listening on {bind}:{port}"); } public void Stop() { stopping = true; try { listener?.Stop(); } catch { } } private void Accept() { while (!stopping) { TcpClient client; try { client = listener.AcceptTcpClient(); } catch { if (stopping) { break; } Thread.Sleep(500); continue; } if (Interlocked.Increment(ref clients) > 8) { Interlocked.Decrement(ref clients); client.Close(); continue; } Thread thread = new Thread((ThreadStart)delegate { Serve(client); }); thread.IsBackground = true; thread.Name = "Rise RCON client"; thread.Start(); } } private void Serve(TcpClient client) { IPAddress address = ((IPEndPoint)client.Client.RemoteEndPoint).Address; try { using (client) { using NetworkStream s = client.GetStream(); if (Blocked(address)) { return; } client.ReceiveTimeout = 30000; bool flag = false; int id; int type; byte[] body; while (!stopping && Read(s, out id, out type, out body)) { if (type == 3) { flag = CheckPassword(body); Write(s, id, 0, ""); Write(s, flag ? id : (-1), 2, ""); if (!flag) { Fail(address); Plugin.Log.LogWarning((object)$"RCON: wrong password from {address}"); break; } client.ReceiveTimeout = 0; Plugin.Log.LogInfo((object)$"RCON: connected {address}"); continue; } if (!flag) { break; } switch (type) { case 0: Write(s, id, 0, ""); Write(s, id, 0, new byte[4] { 0, 1, 0, 0 }); break; case 2: { string text = Encoding.UTF8.GetString(body); string text2 = ((text.Length == 0) ? "" : run(text)); if (text.Length > 0) { Plugin.Log.LogInfo((object)$"RCON {address}: {text}"); } WriteChunks(s, id, text2); break; } } } } } catch (IOException) { } catch (Exception ex2) { Plugin.Log.LogWarning((object)$"RCON {address}: {ex2.Message}"); } finally { Interlocked.Decrement(ref clients); } } private bool CheckPassword(byte[] body) { int num = body.Length ^ password.Length; for (int i = 0; i < password.Length; i++) { num |= password[i] ^ ((i < body.Length) ? body[i] : 0); } return num == 0; } private bool Blocked(IPAddress ip) { lock (fails) { (int, DateTime) value; return fails.TryGetValue(ip, out value) && value.Item1 >= 5 && DateTime.UtcNow < value.Item2; } } private void Fail(IPAddress ip) { lock (fails) { fails.TryGetValue(ip, out (int, DateTime) value); if (DateTime.UtcNow >= value.Item2) { value.Item1 = 0; } fails[ip] = (value.Item1 + 1, DateTime.UtcNow + BlockFor); } } private static bool Read(Stream s, out int id, out int type, out byte[] body) { id = (type = 0); body = null; byte[] array = new byte[4]; if (!ReadFull(s, array, 4)) { return false; } int num = BitConverter.ToInt32(array, 0); if (num < 10 || num > 4106) { return false; } byte[] array2 = new byte[num]; if (!ReadFull(s, array2, num)) { return false; } id = BitConverter.ToInt32(array2, 0); type = BitConverter.ToInt32(array2, 4); int num2 = Array.IndexOf(array2, (byte)0, 8) - 8; if (num2 < 0) { num2 = num - 10; } body = new byte[num2]; Buffer.BlockCopy(array2, 8, body, 0, num2); return true; } private static bool ReadFull(Stream s, byte[] buf, int n) { int num; for (int i = 0; i < n; i += num) { num = s.Read(buf, i, n - i); if (num <= 0) { return false; } } return true; } private static void WriteChunks(Stream s, int id, string text) { byte[] bytes = Encoding.UTF8.GetBytes(text ?? ""); if (bytes.Length <= 4000) { Write(s, id, 0, bytes); return; } for (int i = 0; i < bytes.Length; i += 4000) { byte[] array = new byte[Math.Min(4000, bytes.Length - i)]; Buffer.BlockCopy(bytes, i, array, 0, array.Length); Write(s, id, 0, array); } } private static void Write(Stream s, int id, int type, string body) { Write(s, id, type, Encoding.UTF8.GetBytes(body)); } private static void Write(Stream s, int id, int type, byte[] body) { byte[] array = new byte[14 + body.Length]; BitConverter.GetBytes(10 + body.Length).CopyTo(array, 0); BitConverter.GetBytes(id).CopyTo(array, 4); BitConverter.GetBytes(type).CopyTo(array, 8); Buffer.BlockCopy(body, 0, array, 12, body.Length); s.Write(array, 0, array.Length); s.Flush(); } } }