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 ServerGateway v1.0.0
Landoria.ServerGateway.dll
Decompiled 5 days agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Landoria.SharedLib; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Landoria.ServerGateway")] [assembly: AssemblyDescription("Local authenticated server command gateway for Valheim")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Landoria.ServerGateway")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: ComVisible(false)] [assembly: Guid("1E1B57B7-5D29-4492-B6DB-E72C68CC6612")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.16332")] namespace Landoria.ServerGateway { internal sealed class LocalGatewayServer : IDisposable { private const int MaximumRequestLineLength = 2048; private const int MaximumHeaderCount = 50; private readonly int _port; private readonly string _token; private readonly Func<bool> _queueSave; private readonly ModLog _log; private TcpListener _listener; private Thread _thread; private volatile bool _running; private string _response = "{\"ready\":false}"; internal LocalGatewayServer(int port, string token, Func<bool> queueSave, ModLog log) { _port = port; _token = token ?? string.Empty; _queueSave = queueSave; _log = log; } internal void Start() { try { _listener = new TcpListener(IPAddress.Loopback, _port); _listener.Start(); _running = true; _thread = new Thread(Listen) { IsBackground = true, Name = "ServerGateway HTTP" }; _thread.Start(); _log.LogInfo($"Server gateway is listening on http://127.0.0.1:{_port}."); } catch (Exception arg) { _log.LogError($"Could not start the server gateway: {arg}"); Dispose(); } } internal void SetResponse(string response) { Interlocked.Exchange(ref _response, response); } private void Listen() { while (_running) { try { using TcpClient client = _listener.AcceptTcpClient(); Handle(client); } catch (SocketException) when (!_running) { break; } catch (Exception ex2) { _log.LogWarning("Server gateway request failed: " + ex2.Message); } } } private void Handle(TcpClient client) { client.ReceiveTimeout = 2000; client.SendTimeout = 2000; using NetworkStream stream = client.GetStream(); using StreamReader streamReader = new StreamReader(stream, Encoding.ASCII, detectEncodingFromByteOrderMarks: false, 1024, leaveOpen: true); string text = streamReader.ReadLine(); if (text == null || text.Length > 2048) { WriteResponse(stream, "400 Bad Request", "{\"error\":\"bad_request\"}"); return; } string authorization = null; for (int i = 0; i < 50; i++) { string text2 = streamReader.ReadLine(); if (text2 == null || text2.Length > 2048) { WriteResponse(stream, "400 Bad Request", "{\"error\":\"bad_request\"}"); return; } if (text2.Length == 0) { HandleRequest(stream, text, authorization); return; } if (text2.StartsWith("Authorization:", StringComparison.OrdinalIgnoreCase)) { authorization = text2.Substring("Authorization:".Length).Trim(); } } WriteResponse(stream, "431 Request Header Fields Too Large", "{\"error\":\"too_many_headers\"}"); } private void HandleRequest(Stream stream, string requestLine, string authorization) { string[] array = requestLine.Split(new char[1] { ' ' }); if (array.Length != 3) { WriteResponse(stream, "400 Bad Request", "{\"error\":\"bad_request\"}"); return; } string text = array[1].Split(new char[1] { '?' })[0]; if (Authenticate(stream, authorization)) { if (array[0] == "GET" && text == "/status") { WriteResponse(stream, "200 OK", Interlocked.CompareExchange(ref _response, null, null)); } else if (array[0] == "POST" && text == "/commands/save") { HandleSave(stream); } else { WriteResponse(stream, "404 Not Found", "{\"error\":\"not_found\"}"); } } } private bool Authenticate(Stream stream, string authorization) { if (_token.Length == 0) { WriteResponse(stream, "503 Service Unavailable", "{\"error\":\"gateway_not_configured\"}"); return false; } string supplied = ((authorization != null && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) ? authorization.Substring("Bearer ".Length) : string.Empty); if (!FixedTimeEquals(_token, supplied)) { WriteResponse(stream, "401 Unauthorized", "{\"error\":\"unauthorized\"}"); return false; } return true; } private void HandleSave(Stream stream) { if (!_queueSave()) { WriteResponse(stream, "409 Conflict", "{\"error\":\"save_already_queued\"}"); } else { WriteResponse(stream, "202 Accepted", "{\"accepted\":true,\"command\":\"save\"}"); } } private static bool FixedTimeEquals(string expected, string supplied) { int num = expected.Length ^ supplied.Length; int num2 = Math.Max(expected.Length, supplied.Length); for (int i = 0; i < num2; i++) { char c = ((i < expected.Length) ? expected[i] : '\0'); char c2 = ((i < supplied.Length) ? supplied[i] : '\0'); num |= c ^ c2; } return num == 0; } private static void WriteResponse(Stream stream, string status, string json) { byte[] bytes = Encoding.UTF8.GetBytes(json); string s = "HTTP/1.1 " + status + "\r\nContent-Type: application/json; charset=utf-8\r\n" + $"Content-Length: {bytes.Length}\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n"; byte[] bytes2 = Encoding.ASCII.GetBytes(s); stream.Write(bytes2, 0, bytes2.Length); stream.Write(bytes, 0, bytes.Length); } public void Dispose() { _running = false; _listener?.Stop(); _listener = null; if (_thread != null && _thread != Thread.CurrentThread) { _thread.Join(2000); } _thread = null; } } [BepInPlugin("Landoria.ServerGateway", "Landoria.ServerGateway", "1.0.0")] public sealed class ServerGatewayPlugin : LandoriaPlugin { private const string PluginGuid = "Landoria.ServerGateway"; private const string PluginName = "Landoria.ServerGateway"; private const string PluginVersion = "1.0.0"; private const int DefaultPort = 8765; private ConfigEntry<int> _port; private ConfigEntry<string> _token; private LocalGatewayServer _server; private float _nextSnapshotTime; private int _saveRequested; internal static ModLog Log { get; private set; } private void Awake() { Log = InitializePlugin("Landoria.ServerGateway"); _port = ((BaseUnityPlugin)this).Config.Bind<int>("RPC", "Port", 8765, "Local HTTP port used by the server gateway."); _token = ((BaseUnityPlugin)this).Config.Bind<string>("Gateway", "Token", string.Empty, "Bearer token required by every gateway endpoint."); Log.LogInfo("Landoria.ServerGateway 1.0.0 is loaded."); } private void Update() { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { EnsureServerStarted(); ExecuteQueuedCommands(); if (!(Time.unscaledTime < _nextSnapshotTime)) { _nextSnapshotTime = Time.unscaledTime + 1f; _server.SetResponse(StatusSnapshot.CreateJson()); } } } private void EnsureServerStarted() { if (_server == null) { int num = _port.Value; if (num < 1 || num > 65535) { Log.LogWarning($"Invalid RPC port {num}; using {8765}."); num = 8765; } string token = Environment.GetEnvironmentVariable("LANDORIA_SERVER_GATEWAY_TOKEN") ?? _token.Value; _server = new LocalGatewayServer(num, token, QueueSave, Log); _server.Start(); } } private bool QueueSave() { return Interlocked.CompareExchange(ref _saveRequested, 1, 0) == 0; } private void ExecuteQueuedCommands() { if (Interlocked.Exchange(ref _saveRequested, 0) == 1) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { Log.LogWarning("The queued save command was ignored because the server is not ready."); return; } ZNet.instance.SaveWorldAndPlayerProfiles(); Log.LogInfo("The queued save command was executed."); } } private void OnDestroy() { _server?.Dispose(); _server = null; ShutdownPlugin(); Log = null; } } internal static class StatusSnapshot { internal const string NotReadyJson = "{\"ready\":false}"; private const int VanillaMaxPlayers = 10; private const int ExpandedServerDefaultMaxPlayers = 20; internal static string CreateJson() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer() || ZNet.World == null) { return "{\"ready\":false}"; } List<PlayerInfo> playerList = instance.GetPlayerList(); StringBuilder stringBuilder = new StringBuilder(256); stringBuilder.Append("{\"ready\":true,\"serverName\":").Append(JsonString(ReadArgument("-name"))); stringBuilder.Append(",\"worldName\":").Append(JsonString(instance.GetWorldName())); stringBuilder.Append(",\"playerCount\":").Append(playerList.Count); stringBuilder.Append(",\"maxPlayers\":").Append(GetMaximumPlayers()); stringBuilder.Append(",\"day\":").Append(GetDay(instance)); stringBuilder.Append(",\"worldCreatedAt\":").Append(JsonString(Environment.GetEnvironmentVariable("WORLD_CREATED_AT"))); stringBuilder.Append(",\"players\":["); AppendPlayers(stringBuilder, playerList); stringBuilder.Append("],\"playerDetails\":["); AppendPlayerDetails(stringBuilder, playerList); return stringBuilder.Append("]}").ToString(); } private static void AppendPlayers(StringBuilder json, List<PlayerInfo> players) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < players.Count; i++) { if (i > 0) { json.Append(','); } json.Append(JsonString(players[i].m_name)); } } private unsafe static void AppendPlayerDetails(StringBuilder json, List<PlayerInfo> players) { //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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < players.Count; i++) { if (i > 0) { json.Append(','); } PlayerInfo val = players[i]; string text = ((object)(*(PlatformUserID*)(&val.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString(); json.Append("{\"name\":").Append(JsonString(players[i].m_name)); json.Append(",\"platformUserId\":").Append(JsonString(text)); json.Append(",\"steamId\":").Append(JsonString(GetSteamId(text))); json.Append(",\"isAdmin\":").Append(ZNet.instance.PlayerIsAdmin(players[i].m_userInfo.m_id) ? "true" : "false").Append('}'); } } private static string GetSteamId(string platformUserId) { if (platformUserId == null || !platformUserId.StartsWith("Steam_", StringComparison.Ordinal)) { return null; } return platformUserId.Substring("Steam_".Length); } private static int GetDay(ZNet network) { if (!((Object)(object)EnvMan.instance == (Object)null)) { return EnvMan.instance.GetDay(network.GetTimeSeconds()); } return 0; } private static int GetMaximumPlayers() { if (int.TryParse(ReadArgument("--maxplayer"), out var result) && result > 0) { return Math.Min(result, 100); } if (!Chainloader.PluginInfos.ContainsKey("Landoria.ExpandedServer")) { return 10; } return 20; } private static string ReadArgument(string name) { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i + 1 < commandLineArgs.Length; i++) { if (string.Equals(commandLineArgs[i], name, StringComparison.OrdinalIgnoreCase)) { return commandLineArgs[i + 1]; } } return null; } private static string JsonString(string value) { if (value == null) { return "null"; } StringBuilder stringBuilder = new StringBuilder(value.Length + 2).Append('"'); foreach (char character in value) { AppendEscaped(stringBuilder, character); } return stringBuilder.Append('"').ToString(); } private static void AppendEscaped(StringBuilder escaped, char character) { switch (character) { case '"': escaped.Append("\\\""); break; case '\\': escaped.Append("\\\\"); break; case '\b': escaped.Append("\\b"); break; case '\f': escaped.Append("\\f"); break; case '\n': escaped.Append("\\n"); break; case '\r': escaped.Append("\\r"); break; case '\t': escaped.Append("\\t"); break; default: escaped.Append((character < ' ') ? $"\\u{(int)character:x4}" : character.ToString()); break; } } } } namespace Landoria.SharedLib { public abstract class LandoriaPlugin : BaseUnityPlugin { private Harmony _harmony; private bool _patchesApplied; protected ModLog InitializePlugin(string pluginGuid) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown ModLog modLog = new ModLog(((BaseUnityPlugin)this).Logger); Version version = ((object)this).GetType().Assembly.GetName().Version; modLog.LogInfo($"AssemblyVersion: {version}."); _harmony = new Harmony(pluginGuid); PatchOwnNamespace(modLog); return modLog; } protected void PatchOwnNamespace(ModLog log) { if (_patchesApplied) { log.LogDebug("Harmony patches are already active; skipping registration."); return; } string text = ((object)this).GetType().Namespace; Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (type.Namespace == text) { _harmony.CreateClassProcessor(type).Patch(); } } _patchesApplied = true; log.LogDebug("Harmony patches were applied for the plugin namespace."); } protected void ShutdownPlugin() { if (_patchesApplied) { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _patchesApplied = false; } } } public sealed class ModLog { private readonly ManualLogSource _logger; public ModLog(ManualLogSource logger) { _logger = logger; } public void LogFatal(object message) { Write((LogLevel)1, message); } public void LogError(object message) { Write((LogLevel)2, message); } public void LogWarning(object message) { Write((LogLevel)4, message); } public void LogMessage(object message) { Write((LogLevel)8, message); } public void LogInfo(object message) { Write((LogLevel)16, message); } public void LogDebug(object message) { Write((LogLevel)32, message); } public void Log(LogLevel level, object message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Write(level, message); } private void Write(LogLevel level, object message) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) string arg = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"); _logger.Log(level, (object)$"[{arg}] {message}"); } } }