Decompiled source of Bigfrost ServerPortal v1.0.0

BepInEx\plugins\Bigfrost_ServerPortal\Bigfrost_ServerPortal.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
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.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Bifrostheim.Helpers;
using Bifrostheim.Systems.Web;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("TestRunner")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+aaed364b2d1fe22de3733e0597dd5e084c8c9366")]
[assembly: AssemblyProduct("TestRunner")]
[assembly: AssemblyTitle("TestRunner")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 TestRunner
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs resolveArgs) => new AssemblyName(resolveArgs.Name).Name.Equals("netstandard", StringComparison.OrdinalIgnoreCase) ? typeof(object).Assembly : null;
			string text = Path.Combine(Path.GetTempPath(), "test_bepinex_config_" + Guid.NewGuid().ToString("N"));
			Directory.CreateDirectory(text);
			Environment.SetEnvironmentVariable("BEPINEX_CONFIG_PATH", text);
			Console.WriteLine("Testing ConfigSyncManager with test directory: " + text);
			Console.WriteLine("\n[1] Testing Valgrind Save & Load...");
			ConfigSyncManager.SaveValgrindConfig(new ValgrindConfigDto
			{
				calculationMode = "TieredBrackets",
				useTopNSkillsOnly = true,
				topNSkillsCount = 7,
				resetAccumulatorOnDeath = false,
				enableDebugLogging = true,
				earlyGameLossPercent = 10f,
				midGameLossPercent = 6f,
				lateGameLossPercent = 3f,
				endgameLossPercent = 1.5f,
				curveMaxLossPercent = 10f,
				curveMinLossPercent = 1.5f
			});
			string path = Path.Combine(text, "com.bigai.valgrind.cfg");
			if (!File.Exists(path))
			{
				throw new Exception("com.bigai.valgrind.cfg was not created!");
			}
			Console.WriteLine("Config file content:\n" + File.ReadAllText(path));
			ValgrindConfigDto valgrindConfigDto = ConfigSyncManager.LoadValgrindConfig();
			if (valgrindConfigDto.calculationMode != "TieredBrackets" || !valgrindConfigDto.useTopNSkillsOnly || valgrindConfigDto.topNSkillsCount != 7 || Math.Abs(valgrindConfigDto.earlyGameLossPercent - 10f) > 0.01f)
			{
				throw new Exception($"Valgrind mismatch: mode={valgrindConfigDto.calculationMode}, topN={valgrindConfigDto.useTopNSkillsOnly}, count={valgrindConfigDto.topNSkillsCount}, early={valgrindConfigDto.earlyGameLossPercent}");
			}
			Console.WriteLine("✓ Valgrind PASSED");
			Console.WriteLine("\n[2] Testing Dagr & Nott Save & Load...");
			ConfigSyncManager.SaveDagrNottConfig(new DagrNottConfigDto
			{
				dawnMultiplier = 0.9f,
				dayMultiplier = 0.5f,
				duskMultiplier = 0.9f,
				nightMultiplier = 0.3f,
				logPhaseTransitions = true
			});
			string path2 = Path.Combine(text, "com.bigai.dagrnott_customdaycycle.cfg");
			if (!File.Exists(path2))
			{
				throw new Exception("com.bigai.dagrnott_customdaycycle.cfg was not created!");
			}
			Console.WriteLine("Config file content:\n" + File.ReadAllText(path2));
			DagrNottConfigDto dagrNottConfigDto = ConfigSyncManager.LoadDagrNottConfig();
			Console.WriteLine($"Loaded Dagr: Dawn={dagrNottConfigDto.dawnMultiplier}x (~{dagrNottConfigDto.dawnMinutes}m), Day={dagrNottConfigDto.dayMultiplier}x (~{dagrNottConfigDto.dayMinutes}m), Dusk={dagrNottConfigDto.duskMultiplier}x (~{dagrNottConfigDto.duskMinutes}m), Night={dagrNottConfigDto.nightMultiplier}x (~{dagrNottConfigDto.nightMinutes}m) | Total: ~{dagrNottConfigDto.totalMinutes}m");
			if (Math.Abs(dagrNottConfigDto.dawnMultiplier - 0.9f) > 0.01f || Math.Abs(dagrNottConfigDto.dayMultiplier - 0.5f) > 0.01f || Math.Abs(dagrNottConfigDto.nightMultiplier - 0.3f) > 0.01f || dagrNottConfigDto.totalMinutes != 60f)
			{
				throw new Exception($"Dagr & Nott mismatch: dawn={dagrNottConfigDto.dawnMultiplier}, day={dagrNottConfigDto.dayMultiplier}, night={dagrNottConfigDto.nightMultiplier}, total={dagrNottConfigDto.totalMinutes}");
			}
			Console.WriteLine("✓ Dagr & Nott PASSED");
			Console.WriteLine("\n[3] Testing Skald Save & Load...");
			ConfigSyncManager.SaveSkaldConfig(new SkaldConfigDto
			{
				enabled = true,
				enableBosses = true,
				monsterTemplates = "{victim} fell to {killer};A foul {killer} destroyed {victim}",
				bossTemplates = "{victim} was crushed by legendary {killer}",
				overwhelmedMessages = "{victim} was swarmed by enemies",
				genericDeathMessages = "{victim} died in the {biome}"
			});
			string path3 = Path.Combine(text, "com.bigai.skald_vikingkillfeed.cfg");
			if (!File.Exists(path3))
			{
				throw new Exception("com.bigai.skald_vikingkillfeed.cfg was not created!");
			}
			Console.WriteLine("Config file content:\n" + File.ReadAllText(path3));
			SkaldConfigDto skaldConfigDto = ConfigSyncManager.LoadSkaldConfig();
			if (!skaldConfigDto.enabled || skaldConfigDto.overwhelmedMessages != "{victim} was swarmed by enemies" || skaldConfigDto.monsterTemplates != "{victim} fell to {killer};A foul {killer} destroyed {victim}")
			{
				throw new Exception($"Skald mismatch: enabled={skaldConfigDto.enabled}, overwhelmed={skaldConfigDto.overwhelmedMessages}");
			}
			Console.WriteLine("✓ Skald PASSED");
			Console.WriteLine("\n[4] Testing Njörðr Save & Load...");
			ConfigSyncManager.SaveNjororConfig(new NjororConfigDto
			{
				enableFairWinds = true,
				headwindMitigationPercent = 85f,
				stormFrequencyMultiplier = 1.75f,
				alwaysTailwindInOcean = true
			});
			string path4 = Path.Combine(text, "com.bigai.njoror_fairwinds.cfg");
			if (!File.Exists(path4))
			{
				throw new Exception("com.bigai.njoror_fairwinds.cfg was not created!");
			}
			Console.WriteLine("Config file content:\n" + File.ReadAllText(path4));
			NjororConfigDto njororConfigDto = ConfigSyncManager.LoadNjororConfig();
			if (Math.Abs(njororConfigDto.headwindMitigationPercent - 85f) > 0.01f || Math.Abs(njororConfigDto.stormFrequencyMultiplier - 1.75f) > 0.01f || !njororConfigDto.alwaysTailwindInOcean)
			{
				throw new Exception($"Njörðr mismatch: headwindMitigation={njororConfigDto.headwindMitigationPercent}, stormFreq={njororConfigDto.stormFrequencyMultiplier}");
			}
			Console.WriteLine("✓ Njörðr PASSED");
			Console.WriteLine("\n[6] Testing CharactersVault Load, Parse, Unbind & Wipe...");
			string text2 = Path.Combine(text, "CharacterVault");
			Directory.CreateDirectory(text2);
			string path5 = Path.Combine(text2, "bindings.json");
			string contents = "{\n  \"Steam_76561198132796198\": {\n    \"characterName\": \"Ragnar Lothbrok\",\n    \"created\": \"2026-08-10\",\n    \"lastLogin\": \"2026-08-28 20:00\",\n    \"status\": \"Bound\"\n  },\n  \"Steam_456\": \"Lagertha\"\n}";
			File.WriteAllText(path5, contents);
			List<Dictionary<string, object>> list = ConfigSyncManager.LoadCharacterVaultBindings();
			Console.WriteLine($"Loaded {list.Count} bindings.");
			foreach (Dictionary<string, object> item in list)
			{
				Console.WriteLine(string.Format("  SteamId: {0}, Character: {1}, Created: {2}, LastLogin: {3}, Status: {4}", item["steamId"], item["characterName"], item["created"], item["lastLogin"], item["status"]));
			}
			Dictionary<string, object> dictionary = list.Find((Dictionary<string, object> b) => b["steamId"].ToString() == "Steam_76561198132796198");
			if (dictionary == null || dictionary["characterName"].ToString() != "Ragnar Lothbrok")
			{
				throw new Exception(string.Format("CharacterVault object parsing failed! Expected 'Ragnar Lothbrok', got '{0}'", dictionary?["characterName"]));
			}
			if (dictionary["characterName"].ToString().Contains("Dictionary"))
			{
				throw new Exception("CharacterVault characterName contains raw dictionary type string!");
			}
			if (dictionary["created"].ToString() != "2026-08-10" || dictionary["lastLogin"].ToString() != "2026-08-28 20:00")
			{
				throw new Exception(string.Format("CharacterVault timestamps mismatch! Expected 2026-08-10 / 2026-08-28 20:00, got {0} / {1}", dictionary["created"], dictionary["lastLogin"]));
			}
			Dictionary<string, object> dictionary2 = list.Find((Dictionary<string, object> b) => b["steamId"].ToString() == "Steam_456");
			if (dictionary2 == null || dictionary2["characterName"].ToString() != "Lagertha")
			{
				throw new Exception(string.Format("CharacterVault string parsing failed! Expected 'Lagertha', got '{0}'", dictionary2?["characterName"]));
			}
			if (dictionary2["created"].ToString() != "—" || dictionary2["lastLogin"].ToString() != "—")
			{
				throw new Exception(string.Format("CharacterVault missing timestamps should default to '—', got {0} / {1}", dictionary2["created"], dictionary2["lastLogin"]));
			}
			string text3 = Path.Combine(text2, "characters");
			Directory.CreateDirectory(text3);
			string path6 = Path.Combine(text3, "76561198999999999.fch");
			File.WriteAllText(path6, "DUMMY_CHARACTER_DATA");
			DateTime creationTimeUtc = new DateTime(2025, 4, 15, 10, 0, 0, DateTimeKind.Utc);
			DateTime lastWriteTimeUtc = new DateTime(2026, 1, 20, 14, 30, 0, DateTimeKind.Utc);
			File.SetCreationTimeUtc(path6, creationTimeUtc);
			File.SetLastWriteTimeUtc(path6, lastWriteTimeUtc);
			string contents2 = "{\n  \"Steam_76561198999999999\": \"Floki\"\n}";
			File.WriteAllText(path5, contents2);
			Dictionary<string, object> dictionary3 = ConfigSyncManager.LoadCharacterVaultBindings().Find((Dictionary<string, object> b) => b["steamId"].ToString() == "Steam_76561198999999999");
			if (dictionary3 == null || dictionary3["created"].ToString() != "2025-04-15" || dictionary3["lastLogin"].ToString() != "2026-01-20 14:30")
			{
				throw new Exception(string.Format("CharacterVault file timestamp detection failed! Got created={0}, lastLogin={1}", dictionary3?["created"], dictionary3?["lastLogin"]));
			}
			ConfigSyncManager.UnbindCharacter("76561198999999999");
			if (ConfigSyncManager.LoadCharacterVaultBindings().Exists((Dictionary<string, object> b) => b["steamId"].ToString() == "Steam_76561198999999999"))
			{
				throw new Exception("CharacterVault unbind with prefix mismatch failed!");
			}
			ConfigSyncManager.WipeCharacters();
			string text4 = File.ReadAllText(path5);
			Console.WriteLine("After wipe JSON: " + text4);
			if (text4.Trim() != "{}")
			{
				throw new Exception("CharacterVault wipe failed!");
			}
			Console.WriteLine("✓ CharactersVault PASSED");
			Console.WriteLine("\n[7] Testing INI Preservation with Comments...");
			string path7 = Path.Combine(text, "com.bigai.valgrind.cfg");
			string contents3 = "## Settings file was created by plugin Valgrind\n\n[1 - General]\n\n## Custom comment\nCalculationMode = TieredBrackets\nUseTopNSkillsOnly = false\n\n[2 - Tiered Brackets]\nEarlyGameLossPercent = 8.0\n";
			File.WriteAllText(path7, contents3);
			ConfigSyncManager.SaveValgrindConfig(new ValgrindConfigDto
			{
				calculationMode = "ContinuousCurve",
				earlyGameLossPercent = 12f
			});
			string text5 = File.ReadAllText(path7);
			Console.WriteLine("Preserved INI content:\n" + text5);
			if (!text5.Contains("## Custom comment") || !text5.Contains("CalculationMode = ContinuousCurve") || !text5.Contains("EarlyGameLossPercent = 12.0"))
			{
				throw new Exception("INI preservation failed to preserve comments or update keys!");
			}
			Console.WriteLine("✓ INI Preservation PASSED");
			Console.WriteLine("\n========================================================");
			Console.WriteLine(">>> ALL CONFIG PERSISTENCE TESTS PASSED WITH 100% SUCCESS <<<");
			Console.WriteLine("========================================================");
		}
	}
}
namespace Bifrostheim
{
	[BepInPlugin("com.bigai.bigfrost_serverportal", "Bigfrost_ServerPortal", "1.0.0")]
	public class BifrostheimPlugin : BaseUnityPlugin
	{
		public const string PluginGUID = "com.bigai.bigfrost_serverportal";

		public const string PluginName = "Bigfrost_ServerPortal";

		public const string PluginVersion = "1.0.0";

		public static ConfigEntry<bool> EnableWebPortal;

		public static ConfigEntry<int> WebPortalPort;

		public static ConfigEntry<string> WebAdminPassword;

		public static ConfigEntry<bool> VerboseLogging;

		public static ConfigEntry<string> LifecycleRestartMode;

		public static ConfigEntry<string> LifecycleScriptPath;

		public static ConfigEntry<bool> DailyRestartEnabled;

		public static ConfigEntry<string> DailyRestartTime;

		public static BifrostheimPlugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				Log.LogInfo((object)"══════════════════════════════════════════");
				Log.LogInfo((object)"  Bigfrost_ServerPortal v1.0.0 loading...");
				Log.LogInfo((object)"══════════════════════════════════════════");
				EnableWebPortal = ((BaseUnityPlugin)this).Config.Bind<bool>("WebPortal", "EnableWebPortal", true, "Enable the embedded web management portal.");
				WebPortalPort = ((BaseUnityPlugin)this).Config.Bind<int>("WebPortal", "WebPortalPort", 8080, "Port for the embedded web management portal.");
				WebAdminPassword = ((BaseUnityPlugin)this).Config.Bind<string>("WebPortal", "WebAdminPassword", "admin", "Password required for administrative actions in the web portal.");
				VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", false, "Enable verbose logging in BepInEx console.");
				LifecycleRestartMode = ((BaseUnityPlugin)this).Config.Bind<string>("Lifecycle", "RestartMode", "ExitOnly", "Server restart strategy: ExitOnly or SpawnProcess.");
				LifecycleScriptPath = ((BaseUnityPlugin)this).Config.Bind<string>("Lifecycle", "RestartScriptPath", "./start_server.sh", "Path to external restart script when RestartMode is SpawnProcess.");
				DailyRestartEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Lifecycle", "DailyRestartEnabled", false, "Enable automated daily server restart.");
				DailyRestartTime = ((BaseUnityPlugin)this).Config.Bind<string>("Lifecycle", "DailyRestartTime", "04:00", "Daily restart time in 24h format (HH:mm).");
				MainThreadDispatcher.Initialize();
				ConfigSyncManager.OnlinePlayerChecker = ZNetHelper.IsPlayerOnline;
				Logger.Listeners.Add((ILogListener)(object)new BepInExLogListener());
				if (EnableWebPortal.Value)
				{
					WebPortalServer.Start(WebPortalPort.Value, WebAdminPassword.Value);
				}
				Log.LogInfo((object)"[Bigfrost_ServerPortal] Initialized successfully.");
			}
			catch (Exception arg)
			{
				Log.LogError((object)string.Format("[{0}] Failed to initialize: {1}", "Bigfrost_ServerPortal", arg));
			}
		}

		private void Update()
		{
			WebApiRouter.TickLifecycle();
		}

		private void OnDestroy()
		{
			WebPortalServer.Stop();
			Log.LogInfo((object)"[Bigfrost_ServerPortal] Unloaded.");
		}
	}
}
namespace Bifrostheim.Systems.Web
{
	public static class WebApiRouter
	{
		private static readonly DateTime StartTime = DateTime.UtcNow;

		private static readonly List<ConsoleLogEntry> LogsBuffer = new List<ConsoleLogEntry>();

		private static readonly object LogLock = new object();

		private static ScheduledRestartState _scheduledRestart = new ScheduledRestartState();

		private static DailyRestartState _dailyRestart = new DailyRestartState();

		private static LifecycleConfigState _lifecycleConfig = new LifecycleConfigState();

		private static DateTime _lastLifecycleTick = DateTime.MinValue;

		private static readonly HashSet<int> _restartWarningsSent = new HashSet<int>();

		private static string _lastDailyRestartDate = string.Empty;

		private static bool _isExecutingRestart = false;

		private static readonly List<PendingConfigChange> _pendingChanges = new List<PendingConfigChange>();

		private static readonly object _pendingLock = new object();

		private static List<SkaldDeathRecordDto> _skaldChronicle = new List<SkaldDeathRecordDto>();

		public static void RecordPendingChange(string module, string moduleName)
		{
			lock (_pendingLock)
			{
				PendingConfigChange pendingConfigChange = _pendingChanges.FirstOrDefault((PendingConfigChange c) => c.module.Equals(module, StringComparison.OrdinalIgnoreCase));
				if (pendingConfigChange != null)
				{
					pendingConfigChange.timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
					return;
				}
				_pendingChanges.Add(new PendingConfigChange
				{
					module = module,
					moduleName = moduleName,
					timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")
				});
			}
		}

		public static void ClearPendingChanges()
		{
			lock (_pendingLock)
			{
				_pendingChanges.Clear();
			}
		}

		public static void AddLog(string level, string source, string text)
		{
			lock (LogLock)
			{
				LogsBuffer.Add(new ConsoleLogEntry
				{
					time = DateTime.Now.ToString("HH:mm:ss"),
					source = source,
					text = text,
					level = level
				});
				if (LogsBuffer.Count > 300)
				{
					LogsBuffer.RemoveAt(0);
				}
			}
		}

		public static async Task HandleApiRequestAsync(HttpListenerContext context, string path, string clientIp)
		{
			HttpListenerRequest request = context.Request;
			HttpListenerResponse response = context.Response;
			string text = request.HttpMethod.ToUpperInvariant();
			response.ContentType = "application/json; charset=utf-8";
			response.AddHeader("Access-Control-Allow-Origin", "*");
			try
			{
				if (path.Equals("/api/auth/login", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleAuthLogin(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/auth/verify", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleAuthVerify(request, response);
					return;
				}
				if (path.Equals("/api/auth/status", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleAuthStatus(request, response);
					return;
				}
				if (!IsAuthorized(request))
				{
					AddLog("warn", "AUTH", "Unauthorized " + text + " request to " + path + " from " + clientIp + ".");
					await SendJsonAsync(response, 401, new
					{
						success = false,
						error = "Unauthorized: Admin password required."
					});
					return;
				}
				if (path.Equals("/api/modules/installed", StringComparison.OrdinalIgnoreCase))
				{
					await HandleGetInstalledModules(response);
					return;
				}
				if (path.Equals("/api/server/telemetry", StringComparison.OrdinalIgnoreCase))
				{
					await HandleGetTelemetry(response);
					return;
				}
				if (path.Equals("/api/players", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetPlayers(response);
					return;
				}
				if (path.Equals("/api/players/kick", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleKickPlayer(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/players/ban", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleBanPlayer(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/bans", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetBans(response);
					return;
				}
				if (path.Equals("/api/bans/unban", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleUnbanPlayer(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/bans/add", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleAddBan(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/console/logs", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetLogs(response);
					return;
				}
				if (path.Equals("/api/console/exec", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleExecCommand(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/server/save", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleForceSave(response, clientIp);
					return;
				}
				if (path.Equals("/api/server/broadcast", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleBroadcast(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/server/restart-status", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetRestartStatus(response);
					return;
				}
				if (path.Equals("/api/server/schedule-restart", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleScheduleRestart(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/server/cancel-restart", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleCancelRestart(response, clientIp);
					return;
				}
				if (path.Equals("/api/server/daily-restart", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleUpdateDailyRestart(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/server/pending-changes", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetPendingChanges(response);
					return;
				}
				if (path.Equals("/api/server/clear-pending-changes", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleClearPendingChanges(response, clientIp);
					return;
				}
				if (path.Equals("/api/server/lifecycle-config", StringComparison.OrdinalIgnoreCase))
				{
					if (text == "GET")
					{
						await HandleGetLifecycleConfig(response);
					}
					else if (text == "POST")
					{
						await HandleSaveLifecycleConfig(request, response, clientIp);
					}
					return;
				}
				if (path.Equals("/api/modules/charactervault/bindings", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetCharacterBindings(response);
					return;
				}
				if (path.Equals("/api/modules/charactervault/unbind", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleUnbindCharacter(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/modules/charactervault/wipe", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleWipeCharacters(response, clientIp);
					return;
				}
				if (path.Equals("/api/modules/valgrind/config", StringComparison.OrdinalIgnoreCase))
				{
					if (text == "GET")
					{
						await HandleGetValgrindConfig(response);
					}
					else if (text == "POST")
					{
						await HandleSaveValgrindConfig(request, response, clientIp);
					}
					return;
				}
				if (path.Equals("/api/modules/dagrnott/config", StringComparison.OrdinalIgnoreCase))
				{
					if (text == "GET")
					{
						await HandleGetDagrNottConfig(response);
					}
					else if (text == "POST")
					{
						await HandleSaveDagrNottConfig(request, response, clientIp);
					}
					return;
				}
				if (path.Equals("/api/modules/skald/config", StringComparison.OrdinalIgnoreCase))
				{
					if (text == "GET")
					{
						await HandleGetSkaldConfig(response);
					}
					else if (text == "POST")
					{
						await HandleSaveSkaldConfig(request, response, clientIp);
					}
					return;
				}
				if (path.Equals("/api/modules/skald/chronicle", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetSkaldChronicle(response);
					return;
				}
				if (path.Equals("/api/modules/skald/test-death", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleTestDeathAnnouncement(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/modules/njoror/config", StringComparison.OrdinalIgnoreCase))
				{
					if (text == "GET")
					{
						await HandleGetNjororConfig(response);
					}
					else if (text == "POST")
					{
						await HandleSaveNjororConfig(request, response, clientIp);
					}
					return;
				}
				if (path.Equals("/api/other-mods/list", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetOtherModsList(response);
					return;
				}
				if (path.Equals("/api/other-mods/config", StringComparison.OrdinalIgnoreCase) && text == "GET")
				{
					await HandleGetOtherModConfig(request, response);
					return;
				}
				if (path.Equals("/api/other-mods/config/save", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleSaveOtherModConfig(request, response, clientIp);
					return;
				}
				if (path.Equals("/api/other-mods/config/reset-defaults", StringComparison.OrdinalIgnoreCase) && text == "POST")
				{
					await HandleResetOtherModDefaults(request, response, clientIp);
					return;
				}
				await SendJsonAsync(response, 404, new
				{
					error = "API endpoint '" + path + "' not found."
				});
			}
			catch (Exception ex)
			{
				BifrostheimPlugin.Log.LogError((object)$"[WebApiRouter] Error handling '{path}': {ex}");
				await SendJsonAsync(response, 500, new
				{
					error = ex.Message
				});
			}
		}

		private static bool HasPlugin(params string[] candidateGuids)
		{
			Dictionary<string, PluginInfo>.KeyCollection keys = Chainloader.PluginInfos.Keys;
			foreach (string candidate in candidateGuids)
			{
				if (keys.Any((string k) => string.Equals(k, candidate, StringComparison.OrdinalIgnoreCase) || k.IndexOf(candidate, StringComparison.OrdinalIgnoreCase) >= 0))
				{
					return true;
				}
			}
			return false;
		}

		private static string GetConfiguredAdminPassword()
		{
			string text = BifrostheimPlugin.WebAdminPassword?.Value ?? WebPortalServer.AdminPassword;
			if (string.IsNullOrWhiteSpace(text))
			{
				return "admin";
			}
			return text;
		}

		public static bool IsAuthorized(HttpListenerRequest request)
		{
			string configuredAdminPassword = GetConfiguredAdminPassword();
			if (string.Equals(configuredAdminPassword, "none", StringComparison.OrdinalIgnoreCase) || string.Equals(configuredAdminPassword, "open", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			string text = request.Headers["X-Admin-Password"];
			if (!string.IsNullOrEmpty(text) && string.Equals(text, configuredAdminPassword, StringComparison.Ordinal))
			{
				return true;
			}
			string text2 = request.Headers["Authorization"];
			if (!string.IsNullOrEmpty(text2))
			{
				if (text2.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
				{
					if (string.Equals(text2.Substring(7).Trim(), configuredAdminPassword, StringComparison.Ordinal))
					{
						return true;
					}
				}
				else if (string.Equals(text2.Trim(), configuredAdminPassword, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static async Task HandleAuthVerify(HttpListenerRequest request, HttpListenerResponse response)
		{
			if (IsAuthorized(request))
			{
				await SendJsonAsync(response, 200, new
				{
					authenticated = true,
					message = "Session valid."
				});
			}
			else
			{
				await SendJsonAsync(response, 401, new
				{
					authenticated = false,
					message = "Unauthorized. Admin password required."
				});
			}
		}

		private static async Task HandleAuthStatus(HttpListenerRequest request, HttpListenerResponse response)
		{
			string configuredAdminPassword = GetConfiguredAdminPassword();
			bool flag = !string.Equals(configuredAdminPassword, "none", StringComparison.OrdinalIgnoreCase) && !string.Equals(configuredAdminPassword, "open", StringComparison.OrdinalIgnoreCase);
			bool authenticated = !flag || IsAuthorized(request);
			await SendJsonAsync(response, 200, new
			{
				required = flag,
				authenticated = authenticated
			});
		}

		private static async Task HandleAuthLogin(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			string text = SimpleJson.DeserializeObject<AuthRequest>(await ReadBodyAsync(request))?.password ?? string.Empty;
			string configuredAdminPassword = GetConfiguredAdminPassword();
			if (string.Equals(configuredAdminPassword, "none", StringComparison.OrdinalIgnoreCase) || string.Equals(configuredAdminPassword, "open", StringComparison.OrdinalIgnoreCase))
			{
				await SendJsonAsync(response, 200, new
				{
					success = true,
					token = text,
					message = "Authentication successful (open access)."
				});
			}
			else if (string.Equals(text, configuredAdminPassword, StringComparison.Ordinal))
			{
				AddLog("info", "AUTH", "Admin login successful from " + clientIp + ".");
				await SendJsonAsync(response, 200, new
				{
					success = true,
					token = text,
					message = "Authentication successful."
				});
			}
			else
			{
				AddLog("warn", "AUTH", "Failed admin login attempt from " + clientIp + ".");
				await SendJsonAsync(response, 401, new
				{
					success = false,
					message = "Invalid admin password."
				});
			}
		}

		private static async Task HandleGetInstalledModules(HttpListenerResponse response)
		{
			List<string> list = new List<string>();
			if (HasPlugin("com.charactervault.valheim", "com.bigai.charactervault", "com.bigai.charactersvault", "charactervault"))
			{
				list.Add("charvault");
			}
			if (HasPlugin("com.bigai.valgrind", "valgrind"))
			{
				list.Add("valgrind");
			}
			if (HasPlugin("com.bigai.dagrnott_customdaycycle", "com.bigai.dagrandnott", "com.bigai.dagrnott", "dagrnott_customdaycycle", "dagrandnott", "dagrnott"))
			{
				list.Add("dagrnott");
			}
			if (HasPlugin("com.bigai.skald_vikingkillfeed", "com.bigai.skald", "skald_vikingkillfeed", "skald"))
			{
				list.Add("skald");
			}
			if (HasPlugin("com.bigai.njoror_fairwinds", "com.bigai.njoror", "njoror_fairwinds", "njoror"))
			{
				list.Add("njoror");
			}
			await SendJsonAsync(response, 200, new
			{
				installed = list
			});
		}

		private static async Task HandleGetTelemetry(HttpListenerResponse response)
		{
			TimeSpan uptimeSpan = DateTime.UtcNow - StartTime;
			string uptimeStr = $"{(int)uptimeSpan.TotalHours}h {uptimeSpan.Minutes}m {uptimeSpan.Seconds}s";
			int onlineCount = 0;
			int maxPlayers = 10;
			int activeZdos = 0;
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					onlineCount = ZNet.instance.GetNrOfPlayers();
					maxPlayers = ZNetHelper.GetServerPlayerLimit();
				}
				if (ZDOMan.instance != null)
				{
					activeZdos = ZDOMan.instance.NrOfObjects();
				}
			});
			float num = 1f / Mathf.Max(Time.unscaledDeltaTime, 0.0001f);
			long num2 = GC.GetTotalMemory(forceFullCollection: false) / 1048576;
			ServerTelemetryDto data = new ServerTelemetryDto
			{
				uptime = uptimeStr,
				uptimeSeconds = (long)uptimeSpan.TotalSeconds,
				onlineCount = onlineCount,
				maxPlayers = maxPlayers,
				fps = (int)Math.Round(num),
				tickRate = "50 Hz",
				activeZdos = activeZdos,
				memoryMb = (int)num2
			};
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleGetPlayers(HttpListenerResponse response)
		{
			List<object> playerList = new List<object>();
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0053: 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_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_0072: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					foreach (ZNetPeer peer in ZNetHelper.GetPeers())
					{
						if (peer != null)
						{
							string playerId = ZNetHelper.GetPlayerId(peer);
							string name = peer.m_playerName ?? "Unknown";
							int peerPing = ZNetHelper.GetPeerPing(peer);
							Vector3 refPos = peer.m_refPos;
							string pos = $"{refPos.x:F0}, {refPos.y:F0}, {refPos.z:F0}";
							(float, float, bool, string, int) playerData = ZNetHelper.GetPlayerData(peer);
							playerList.Add(new
							{
								id = playerId,
								name = name,
								steamId = playerId,
								ping = $"{peerPing}ms",
								pos = pos,
								zone = playerData.Item4,
								health = (int)Math.Round(playerData.Item1),
								maxHealth = (int)Math.Round(playerData.Item2),
								pvp = playerData.Item3,
								daysSurvived = playerData.Item5
							});
						}
					}
				}
			});
			await SendJsonAsync(response, 200, playerList);
		}

		private static async Task HandleKickPlayer(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			string name = SimpleJson.DeserializeObject<KickRequest>(await ReadBodyAsync(request))?.name?.Trim() ?? string.Empty;
			if (string.IsNullOrWhiteSpace(name))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Player name is required."
				});
				return;
			}
			bool kicked = false;
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					foreach (ZNetPeer peer in ZNetHelper.GetPeers())
					{
						if (peer != null && (peer.m_playerName.Equals(name, StringComparison.OrdinalIgnoreCase) || ZNetHelper.GetPlayerId(peer).Equals(name, StringComparison.OrdinalIgnoreCase)))
						{
							ZNet.instance.Disconnect(peer);
							kicked = true;
							break;
						}
					}
				}
			});
			if (kicked)
			{
				AddLog("warn", "KICK", "Kicked player '" + name + "'");
				await SendJsonAsync(response, 200, new
				{
					success = true,
					message = "Kicked player '" + name + "'"
				});
			}
			else
			{
				await SendJsonAsync(response, 404, new
				{
					success = false,
					message = "Player '" + name + "' not found online."
				});
			}
		}

		private static async Task HandleBanPlayer(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			BanRequest banRequest = SimpleJson.DeserializeObject<BanRequest>(await ReadBodyAsync(request));
			string name = banRequest?.name?.Trim() ?? string.Empty;
			string reason = banRequest?.reason?.Trim() ?? "Banned by administrator";
			if (string.IsNullOrWhiteSpace(name))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Player name or ID is required."
				});
				return;
			}
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					ZNet.instance.Ban(name);
				}
			});
			AddLog("warn", "BAN", "Banned player '" + name + "' (Reason: " + reason + ")");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "Banned player '" + name + "'"
			});
		}

		private static async Task HandleGetBans(HttpListenerResponse response)
		{
			List<object> bansList = new List<object>();
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					foreach (string banned in ZNetHelper.GetBannedList())
					{
						bansList.Add(new
						{
							id = banned,
							name = banned,
							steamId = banned,
							bannedAt = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm"),
							reason = "Server ban",
							bannedBy = "Administrator"
						});
					}
				}
			});
			await SendJsonAsync(response, 200, bansList);
		}

		private static async Task HandleUnbanPlayer(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			string steamId = SimpleJson.DeserializeObject<UnbanRequest>(await ReadBodyAsync(request))?.steamId?.Trim() ?? string.Empty;
			if (string.IsNullOrWhiteSpace(steamId))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Steam ID is required."
				});
				return;
			}
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					ZNet.instance.Unban(steamId);
				}
			});
			AddLog("info", "UNBAN", "Unbanned Steam ID '" + steamId + "'");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "Unbanned player '" + steamId + "'"
			});
		}

		private static async Task HandleAddBan(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			ManualBanRequest req = SimpleJson.DeserializeObject<ManualBanRequest>(await ReadBodyAsync(request));
			string steamId = req?.steamId?.Trim() ?? string.Empty;
			if (string.IsNullOrWhiteSpace(steamId))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Steam ID is required."
				});
				return;
			}
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					ZNet.instance.Ban(steamId);
				}
			});
			AddLog("warn", "BAN", "Added ban for '" + steamId + "' (Reason: " + req?.reason + ")");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "Banned ID '" + steamId + "'"
			});
		}

		private static async Task HandleGetLogs(HttpListenerResponse response)
		{
			List<ConsoleLogEntry> data;
			lock (LogLock)
			{
				data = new List<ConsoleLogEntry>(LogsBuffer);
			}
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleExecCommand(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			string cmd = SimpleJson.DeserializeObject<ExecCommandRequest>(await ReadBodyAsync(request))?.command?.Trim() ?? string.Empty;
			if (string.IsNullOrWhiteSpace(cmd))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					output = "Command is empty."
				});
				return;
			}
			AddLog("cmd", "ADMIN", "> " + cmd);
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)Console.instance != (Object)null)
				{
					((Terminal)Console.instance).TryRunCommand(cmd, false, false);
				}
			});
			await SendJsonAsync(response, 200, new
			{
				success = true,
				output = "Command '" + cmd + "' executed."
			});
		}

		private static async Task HandleForceSave(HttpListenerResponse response, string clientIp)
		{
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				if ((Object)(object)ZNet.instance != (Object)null)
				{
					ZNet.instance.Save(true, false, false);
				}
			});
			AddLog("info", "SAVE", "World save triggered by administrator.");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "World save triggered successfully."
			});
		}

		private static async Task HandleBroadcast(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			string message = SimpleJson.DeserializeObject<BroadcastRequest>(await ReadBodyAsync(request))?.message?.Trim() ?? string.Empty;
			if (string.IsNullOrWhiteSpace(message))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Message is empty."
				});
				return;
			}
			try
			{
				await MainThreadDispatcher.EnqueueAsync(delegate
				{
					ZNetHelper.BroadcastServerMessage(message);
				});
				AddLog("info", "BROADCAST", "Broadcast: '" + message + "'");
				await SendJsonAsync(response, 200, new
				{
					success = true,
					message = "Broadcast sent."
				});
			}
			catch (Exception ex)
			{
				BifrostheimPlugin.Log.LogError((object)$"[WebApiRouter] Broadcast error: {ex}");
				AddLog("error", "BROADCAST", "Broadcast error: " + ex.Message);
				await SendJsonAsync(response, 200, new
				{
					success = false,
					message = "Broadcast attempted: " + ex.Message
				});
			}
		}

		private static async Task HandleGetRestartStatus(HttpListenerResponse response)
		{
			List<PendingConfigChange> pendingChanges;
			lock (_pendingLock)
			{
				pendingChanges = _pendingChanges.ToList();
			}
			bool enabled = BifrostheimPlugin.DailyRestartEnabled?.Value ?? _dailyRestart.enabled;
			string time = BifrostheimPlugin.DailyRestartTime?.Value ?? _dailyRestart.time;
			string mode = BifrostheimPlugin.LifecycleRestartMode?.Value ?? _lifecycleConfig.mode;
			string scriptPath = BifrostheimPlugin.LifecycleScriptPath?.Value ?? _lifecycleConfig.scriptPath;
			var data = new
			{
				scheduledRestart = (_scheduledRestart.active ? new
				{
					active = true,
					targetTimestamp = _scheduledRestart.targetTimestamp,
					totalMinutes = _scheduledRestart.totalMinutes,
					remainingSeconds = Math.Max(0, (int)((_scheduledRestart.targetTimestamp - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()) / 1000)),
					reason = _scheduledRestart.reason
				} : null),
				dailyRestart = new { enabled, time },
				lifecycleConfig = new { mode, scriptPath },
				pendingChanges = pendingChanges
			};
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleGetPendingChanges(HttpListenerResponse response)
		{
			List<PendingConfigChange> pendingChanges;
			lock (_pendingLock)
			{
				pendingChanges = _pendingChanges.ToList();
			}
			await SendJsonAsync(response, 200, new
			{
				success = true,
				pendingChanges = pendingChanges
			});
		}

		private static async Task HandleClearPendingChanges(HttpListenerResponse response, string clientIp)
		{
			ClearPendingChanges();
			AddLog("info", "RESTART", "Cleared pending restart notifications list.");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "Pending changes cleared."
			});
		}

		private static async Task HandleScheduleRestart(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			ScheduleRestartRequest scheduleRestartRequest = SimpleJson.DeserializeObject<ScheduleRestartRequest>(await ReadBodyAsync(request));
			int minutes = scheduleRestartRequest?.minutes ?? 5;
			string reason = scheduleRestartRequest?.reason ?? "Scheduled maintenance";
			long targetTimestamp = DateTimeOffset.UtcNow.AddMinutes(minutes).ToUnixTimeMilliseconds();
			_scheduledRestart = new ScheduledRestartState
			{
				active = true,
				minutes = minutes,
				totalMinutes = minutes,
				targetTimestamp = targetTimestamp,
				reason = reason
			};
			_restartWarningsSent.Clear();
			_isExecutingRestart = false;
			AddLog("warn", "RESTART", $"Server restart scheduled in {minutes} minutes (Reason: {reason})");
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				ZNetHelper.BroadcastServerMessage(string.Format("⚠\ufe0f SERVER RESTART scheduled in {0} minute{1}! Reason: {2}.", minutes, (minutes > 1) ? "s" : "", reason));
			});
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = $"Restart scheduled in {minutes} minutes.",
				targetTimestamp = targetTimestamp
			});
		}

		private static async Task HandleCancelRestart(HttpListenerResponse response, string clientIp)
		{
			_scheduledRestart = new ScheduledRestartState();
			_restartWarningsSent.Clear();
			_isExecutingRestart = false;
			AddLog("info", "RESTART", "Scheduled server restart cancelled.");
			await MainThreadDispatcher.EnqueueAsync(delegate
			{
				ZNetHelper.BroadcastServerMessage("ℹ\ufe0f The scheduled server restart has been CANCELLED by an administrator.");
			});
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "Scheduled restart cancelled."
			});
		}

		private static async Task HandleUpdateDailyRestart(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			DailyRestartRequest dailyRestartRequest = SimpleJson.DeserializeObject<DailyRestartRequest>(await ReadBodyAsync(request));
			_dailyRestart = new DailyRestartState
			{
				enabled = (dailyRestartRequest?.enabled ?? false),
				time = (dailyRestartRequest?.time ?? "04:00")
			};
			if (BifrostheimPlugin.DailyRestartEnabled != null)
			{
				BifrostheimPlugin.DailyRestartEnabled.Value = _dailyRestart.enabled;
			}
			if (BifrostheimPlugin.DailyRestartTime != null)
			{
				BifrostheimPlugin.DailyRestartTime.Value = _dailyRestart.time;
			}
			try
			{
				BifrostheimPlugin instance = BifrostheimPlugin.Instance;
				if (instance != null)
				{
					ConfigFile config = ((BaseUnityPlugin)instance).Config;
					if (config != null)
					{
						config.Save();
					}
				}
			}
			catch
			{
			}
			AddLog("info", "RESTART", "Updated daily restart: " + (_dailyRestart.enabled ? ("Enabled at " + _dailyRestart.time) : "Disabled") + " (Saved to config).");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				dailyRestart = _dailyRestart
			});
		}

		private static async Task HandleGetLifecycleConfig(HttpListenerResponse response)
		{
			LifecycleConfigState data = new LifecycleConfigState
			{
				mode = (BifrostheimPlugin.LifecycleRestartMode?.Value ?? _lifecycleConfig.mode),
				scriptPath = (BifrostheimPlugin.LifecycleScriptPath?.Value ?? _lifecycleConfig.scriptPath)
			};
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleSaveLifecycleConfig(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			LifecycleConfigState lifecycleConfigState = SimpleJson.DeserializeObject<LifecycleConfigState>(await ReadBodyAsync(request));
			if (lifecycleConfigState != null)
			{
				_lifecycleConfig = lifecycleConfigState;
				if (BifrostheimPlugin.LifecycleRestartMode != null)
				{
					BifrostheimPlugin.LifecycleRestartMode.Value = lifecycleConfigState.mode;
				}
				if (BifrostheimPlugin.LifecycleScriptPath != null)
				{
					BifrostheimPlugin.LifecycleScriptPath.Value = lifecycleConfigState.scriptPath;
				}
				try
				{
					BifrostheimPlugin instance = BifrostheimPlugin.Instance;
					if (instance != null)
					{
						ConfigFile config = ((BaseUnityPlugin)instance).Config;
						if (config != null)
						{
							config.Save();
						}
					}
				}
				catch
				{
				}
			}
			LifecycleConfigState lifecycleConfigState2 = new LifecycleConfigState
			{
				mode = (BifrostheimPlugin.LifecycleRestartMode?.Value ?? _lifecycleConfig.mode),
				scriptPath = (BifrostheimPlugin.LifecycleScriptPath?.Value ?? _lifecycleConfig.scriptPath)
			};
			AddLog("info", "RESTART", "Updated restart strategy: " + lifecycleConfigState2.mode + " (Saved to config).");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				lifecycleConfig = lifecycleConfigState2
			});
		}

		public static void TickLifecycle()
		{
			if ((DateTime.UtcNow - _lastLifecycleTick).TotalSeconds < 1.0)
			{
				return;
			}
			_lastLifecycleTick = DateTime.UtcNow;
			try
			{
				if (_scheduledRestart != null && _scheduledRestart.active && !_isExecutingRestart)
				{
					long num = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
					int num2 = Math.Max(0, (int)((_scheduledRestart.targetTimestamp - num) / 1000));
					int[] array = new int[7] { 900, 600, 300, 120, 60, 30, 10 };
					foreach (int num3 in array)
					{
						if (num2 <= num3 && num2 > num3 - 3 && !_restartWarningsSent.Contains(num3))
						{
							_restartWarningsSent.Add(num3);
							string text = ((num3 >= 60) ? string.Format("{0} minute{1}", num3 / 60, (num3 / 60 > 1) ? "s" : "") : $"{num3} seconds");
							ZNetHelper.BroadcastServerMessage("⚠\ufe0f SERVER RESTART in " + text + "! Reason: " + _scheduledRestart.reason + ". Please find shelter.");
							AddLog("warn", "RESTART", "Broadcast in-game warning: " + text + " remaining.");
						}
					}
					if (num2 <= 0)
					{
						_isExecutingRestart = true;
						ExecuteServerRestartSequence();
					}
				}
				bool num4 = BifrostheimPlugin.DailyRestartEnabled?.Value ?? _dailyRestart.enabled;
				string text2 = BifrostheimPlugin.DailyRestartTime?.Value ?? _dailyRestart.time;
				if (num4 && !string.IsNullOrWhiteSpace(text2) && (_scheduledRestart == null || !_scheduledRestart.active))
				{
					string text3 = DateTime.UtcNow.ToString("yyyy-MM-dd");
					if (DateTime.Now.ToString("HH:mm") == text2 && _lastDailyRestartDate != text3)
					{
						_lastDailyRestartDate = text3;
						int num5 = 5;
						long targetTimestamp = DateTimeOffset.UtcNow.AddMinutes(num5).ToUnixTimeMilliseconds();
						_scheduledRestart = new ScheduledRestartState
						{
							active = true,
							minutes = num5,
							totalMinutes = num5,
							targetTimestamp = targetTimestamp,
							reason = "Automated daily maintenance"
						};
						_restartWarningsSent.Clear();
						AddLog("warn", "RESTART", $"Daily restart triggered automatically for {num5}m countdown.");
						ZNetHelper.BroadcastServerMessage($"⚠\ufe0f AUTOMATED DAILY RESTART scheduled in {num5} minutes. World will be saved.");
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = BifrostheimPlugin.Log;
				if (log != null)
				{
					log.LogError((object)("[WebApiRouter] Lifecycle tick error: " + ex.Message));
				}
			}
		}

		private static void ExecuteServerRestartSequence()
		{
			AddLog("warn", "RESTART", "Executing server restart sequence: saving world and terminating process...");
			ZNetHelper.BroadcastServerMessage("⚠\ufe0f [SERVER RESTART] Server is restarting NOW. World saving...");
			Task.Run(async delegate
			{
				_ = 3;
				try
				{
					await MainThreadDispatcher.EnqueueAsync(delegate
					{
						try
						{
							if ((Object)(object)ZNet.instance != (Object)null)
							{
								ZNet.instance.Save(true, false, false);
								ManualLogSource log5 = BifrostheimPlugin.Log;
								if (log5 != null)
								{
									log5.LogInfo((object)"[WebApiRouter] World save completed before restart.");
								}
							}
						}
						catch (Exception ex2)
						{
							ManualLogSource log6 = BifrostheimPlugin.Log;
							if (log6 != null)
							{
								log6.LogError((object)("[WebApiRouter] Error saving world before restart: " + ex2.Message));
							}
						}
					});
					ClearPendingChanges();
					WebPortalServer.Stop();
					string obj = BifrostheimPlugin.LifecycleRestartMode?.Value ?? _lifecycleConfig.mode;
					string text = BifrostheimPlugin.LifecycleScriptPath?.Value ?? _lifecycleConfig.scriptPath;
					if (obj.Equals("SpawnProcess", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(text))
					{
						try
						{
							ManualLogSource log = BifrostheimPlugin.Log;
							if (log != null)
							{
								log.LogInfo((object)("[WebApiRouter] Spawning external restart process: '" + text + "'"));
							}
							Process.Start(new ProcessStartInfo
							{
								FileName = text,
								UseShellExecute = true
							});
						}
						catch (Exception ex)
						{
							ManualLogSource log2 = BifrostheimPlugin.Log;
							if (log2 != null)
							{
								log2.LogError((object)("[WebApiRouter] Failed to spawn external restart script: " + ex.Message));
							}
						}
					}
					try
					{
						Process.Start(new ProcessStartInfo
						{
							FileName = "supervisorctl",
							Arguments = "shutdown",
							UseShellExecute = false,
							RedirectStandardOutput = true,
							RedirectStandardError = true,
							CreateNoWindow = true
						});
						ManualLogSource log3 = BifrostheimPlugin.Log;
						if (log3 != null)
						{
							log3.LogInfo((object)"[WebApiRouter] Dispatched 'supervisorctl shutdown' to trigger Docker container restart.");
						}
					}
					catch
					{
					}
					try
					{
						Process.Start(new ProcessStartInfo
						{
							FileName = "kill",
							Arguments = "-15 1",
							UseShellExecute = false,
							CreateNoWindow = true
						});
					}
					catch
					{
					}
					await MainThreadDispatcher.EnqueueAsync(delegate
					{
						ManualLogSource log5 = BifrostheimPlugin.Log;
						if (log5 != null)
						{
							log5.LogInfo((object)"[WebApiRouter] Terminating server via Application.Quit() and Environment.Exit().");
						}
						Application.Quit();
					});
					await Task.Delay(500);
					Environment.Exit(0);
					await Task.Delay(1000);
					Process.GetCurrentProcess().Kill();
				}
				catch (Exception arg)
				{
					ManualLogSource log4 = BifrostheimPlugin.Log;
					if (log4 != null)
					{
						log4.LogError((object)$"[WebApiRouter] Error during restart execution: {arg}");
					}
					_isExecutingRestart = false;
				}
			});
		}

		private static async Task HandleGetCharacterBindings(HttpListenerResponse response)
		{
			List<Dictionary<string, object>> data = ConfigSyncManager.LoadCharacterVaultBindings();
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleUnbindCharacter(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			string text = SimpleJson.DeserializeObject<UnbindRequest>(await ReadBodyAsync(request))?.steamId?.Trim() ?? string.Empty;
			if (!string.IsNullOrWhiteSpace(text))
			{
				ConfigSyncManager.UnbindCharacter(text);
			}
			AddLog("info", "CHARVAULT", "Unbound character binding for '" + text + "'.");
			await SendJsonAsync(response, 200, new
			{
				success = true
			});
		}

		private static async Task HandleWipeCharacters(HttpListenerResponse response, string clientIp)
		{
			ConfigSyncManager.WipeCharacters();
			AddLog("warn", "CHARVAULT", "Triggered character bindings wipe.");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				message = "CharactersVault data wiped successfully."
			});
		}

		private static async Task HandleGetValgrindConfig(HttpListenerResponse response)
		{
			ValgrindConfigDto data = ConfigSyncManager.LoadValgrindConfig();
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleSaveValgrindConfig(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			ValgrindConfigDto valgrindConfigDto = SimpleJson.DeserializeObject<ValgrindConfigDto>(await ReadBodyAsync(request));
			if (valgrindConfigDto != null)
			{
				ConfigSyncManager.SaveValgrindConfig(valgrindConfigDto);
			}
			RecordPendingChange("valgrind", "Valgrind");
			AddLog("info", "VALGRIND", "Updated Valgrind configuration (Mode: " + valgrindConfigDto?.calculationMode + ") - Saved to disk.");
			ValgrindConfigDto config = ConfigSyncManager.LoadValgrindConfig();
			await SendJsonAsync(response, 200, new
			{
				success = true,
				config = config
			});
		}

		private static async Task HandleGetDagrNottConfig(HttpListenerResponse response)
		{
			DagrNottConfigDto data = ConfigSyncManager.LoadDagrNottConfig();
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleSaveDagrNottConfig(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			DagrNottConfigDto dagrNottConfigDto = SimpleJson.DeserializeObject<DagrNottConfigDto>(await ReadBodyAsync(request));
			if (dagrNottConfigDto != null)
			{
				ConfigSyncManager.SaveDagrNottConfig(dagrNottConfigDto);
			}
			RecordPendingChange("dagrnott", "Dagr & Nott");
			AddLog("info", "DAGRNOTT", $"Updated Dagr & Nott cycle (Dawn: {dagrNottConfigDto?.dawnMultiplier:F2}x, Day: {dagrNottConfigDto?.dayMultiplier:F2}x, Dusk: {dagrNottConfigDto?.duskMultiplier:F2}x, Night: {dagrNottConfigDto?.nightMultiplier:F2}x | ~{dagrNottConfigDto?.totalMinutes:F1}m total) - Saved to disk.");
			DagrNottConfigDto config = ConfigSyncManager.LoadDagrNottConfig();
			await SendJsonAsync(response, 200, new
			{
				success = true,
				config = config
			});
		}

		private static async Task HandleGetSkaldConfig(HttpListenerResponse response)
		{
			SkaldConfigDto data = ConfigSyncManager.LoadSkaldConfig();
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleSaveSkaldConfig(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			SkaldConfigDto skaldConfigDto = SimpleJson.DeserializeObject<SkaldConfigDto>(await ReadBodyAsync(request));
			if (skaldConfigDto != null)
			{
				ConfigSyncManager.SaveSkaldConfig(skaldConfigDto);
			}
			RecordPendingChange("skald", "Skald");
			AddLog("info", "SKALD", "Updated Skald Viking chronicle configuration - Saved to disk.");
			SkaldConfigDto config = ConfigSyncManager.LoadSkaldConfig();
			await SendJsonAsync(response, 200, new
			{
				success = true,
				config = config
			});
		}

		private static async Task HandleGetSkaldChronicle(HttpListenerResponse response)
		{
			try
			{
				Type type = null;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					if (assembly.GetName().Name == "Skald" || assembly.GetName().Name == "Skald_VikingKillFeed")
					{
						type = assembly.GetType("Skald.Logic.ChronicleRegistry");
						if (type != null)
						{
							break;
						}
					}
				}
				if (type != null)
				{
					MethodInfo method = type.GetMethod("GetRecentDeaths", BindingFlags.Static | BindingFlags.Public);
					if (method != null && method.Invoke(null, new object[1] { 100 }) is IEnumerable enumerable)
					{
						List<SkaldDeathRecordDto> list = new List<SkaldDeathRecordDto>();
						foreach (object item in enumerable)
						{
							if (item != null)
							{
								Type type2 = item.GetType();
								list.Add(new SkaldDeathRecordDto
								{
									id = (type2.GetProperty("Id")?.GetValue(item)?.ToString() ?? Guid.NewGuid().ToString()),
									victimName = (type2.GetProperty("VictimName")?.GetValue(item)?.ToString() ?? ""),
									victimSteamId = (type2.GetProperty("VictimSteamId")?.GetValue(item)?.ToString() ?? ""),
									killerName = (type2.GetProperty("KillerName")?.GetValue(item)?.ToString() ?? ""),
									category = (type2.GetProperty("Category")?.GetValue(item)?.ToString() ?? ""),
									biome = (type2.GetProperty("Biome")?.GetValue(item)?.ToString() ?? ""),
									formattedMessage = (type2.GetProperty("FormattedMessage")?.GetValue(item)?.ToString() ?? ""),
									timestamp = ((type2.GetProperty("Timestamp")?.GetValue(item) is DateTime dateTime) ? dateTime.ToString("yyyy-MM-dd HH:mm:ss") : DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"))
								});
							}
						}
						if (list.Count > 0)
						{
							list.Reverse();
							await SendJsonAsync(response, 200, list);
							return;
						}
					}
				}
			}
			catch
			{
			}
			await SendJsonAsync(response, 200, _skaldChronicle);
		}

		private static async Task HandleTestDeathAnnouncement(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			await ReadBodyAsync(request);
			SkaldDeathRecordDto skaldDeathRecordDto = new SkaldDeathRecordDto
			{
				id = Guid.NewGuid().ToString(),
				victimName = "VikingWarrior",
				victimSteamId = "Steam_76561198000000001",
				killerName = "Troll",
				category = "Monsters",
				biome = "BlackForest",
				formattedMessage = "VikingWarrior was crushed by Troll in Black Forest.",
				timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")
			};
			_skaldChronicle.Insert(0, skaldDeathRecordDto);
			if (_skaldChronicle.Count > 100)
			{
				_skaldChronicle.RemoveAt(_skaldChronicle.Count - 1);
			}
			await SendJsonAsync(response, 200, new
			{
				success = true,
				record = skaldDeathRecordDto
			});
		}

		private static async Task HandleGetNjororConfig(HttpListenerResponse response)
		{
			NjororConfigDto data = ConfigSyncManager.LoadNjororConfig();
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleSaveNjororConfig(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			NjororConfigDto njororConfigDto = SimpleJson.DeserializeObject<NjororConfigDto>(await ReadBodyAsync(request));
			if (njororConfigDto != null)
			{
				ConfigSyncManager.SaveNjororConfig(njororConfigDto);
			}
			RecordPendingChange("njoror", "Njörðr");
			AddLog("info", "NJOROR", "Updated Njörðr fair winds configuration - Saved to disk.");
			NjororConfigDto config = ConfigSyncManager.LoadNjororConfig();
			await SendJsonAsync(response, 200, new
			{
				success = true,
				config = config
			});
		}

		private static async Task HandleGetOtherModsList(HttpListenerResponse response)
		{
			List<OtherModSummaryDto> mods = ConfigSyncManager.ScanOtherModConfigFiles();
			await SendJsonAsync(response, 200, new { mods });
		}

		private static async Task HandleGetOtherModConfig(HttpListenerRequest request, HttpListenerResponse response)
		{
			string text = request.QueryString["file"];
			if (string.IsNullOrWhiteSpace(text))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Missing 'file' query parameter."
				});
				return;
			}
			OtherModConfigDetailDto data = ConfigSyncManager.ParseModConfigFile(text);
			await SendJsonAsync(response, 200, data);
		}

		private static async Task HandleSaveOtherModConfig(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			SaveOtherModConfigRequest saveOtherModConfigRequest = SimpleJson.DeserializeObject<SaveOtherModConfigRequest>(await ReadBodyAsync(request));
			if (saveOtherModConfigRequest == null || string.IsNullOrWhiteSpace(saveOtherModConfigRequest.fileName))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Invalid save request or missing fileName."
				});
				return;
			}
			OtherModConfigDetailDto otherModConfigDetailDto = ConfigSyncManager.SaveOtherModConfig(saveOtherModConfigRequest);
			string text = ((!string.IsNullOrEmpty(otherModConfigDetailDto.displayName)) ? otherModConfigDetailDto.displayName : saveOtherModConfigRequest.fileName);
			RecordPendingChange(saveOtherModConfigRequest.fileName, text);
			AddLog("info", "CONFIG", "Updated mod config '" + saveOtherModConfigRequest.fileName + "' (" + text + ") - Staged restart pending.");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				config = otherModConfigDetailDto
			});
		}

		private static async Task HandleResetOtherModDefaults(HttpListenerRequest request, HttpListenerResponse response, string clientIp)
		{
			ResetOtherModConfigRequest resetOtherModConfigRequest = SimpleJson.DeserializeObject<ResetOtherModConfigRequest>(await ReadBodyAsync(request));
			if (resetOtherModConfigRequest == null || string.IsNullOrWhiteSpace(resetOtherModConfigRequest.fileName))
			{
				await SendJsonAsync(response, 400, new
				{
					success = false,
					message = "Invalid reset request or missing fileName."
				});
				return;
			}
			OtherModConfigDetailDto otherModConfigDetailDto = ConfigSyncManager.ResetOtherModConfigDefaults(resetOtherModConfigRequest.fileName);
			string text = ((!string.IsNullOrEmpty(otherModConfigDetailDto.displayName)) ? otherModConfigDetailDto.displayName : resetOtherModConfigRequest.fileName);
			RecordPendingChange(resetOtherModConfigRequest.fileName, text);
			AddLog("warn", "CONFIG", "Reset mod config '" + resetOtherModConfigRequest.fileName + "' (" + text + ") to default values.");
			await SendJsonAsync(response, 200, new
			{
				success = true,
				config = otherModConfigDetailDto
			});
		}

		private static async Task<string> ReadBodyAsync(HttpListenerRequest request)
		{
			using StreamReader reader = new StreamReader(request.InputStream, request.ContentEncoding);
			return await reader.ReadToEndAsync();
		}

		private static async Task SendJsonAsync(HttpListenerResponse response, int statusCode, object data)
		{
			string s = SimpleJson.SerializeObject(data);
			byte[] bytes = Encoding.UTF8.GetBytes(s);
			response.StatusCode = statusCode;
			response.ContentType = "application/json; charset=utf-8";
			response.ContentLength64 = bytes.Length;
			using (Stream stream = response.OutputStream)
			{
				await stream.WriteAsync(bytes, 0, bytes.Length);
			}
			response.Close();
		}
	}
	public class ServerTelemetryDto
	{
		public string uptime { get; set; } = "0h 0m 0s";

		public long uptimeSeconds { get; set; }

		public int onlineCount { get; set; }

		public int maxPlayers { get; set; } = 10;

		public int fps { get; set; } = 60;

		public string tickRate { get; set; } = "50 Hz";

		public int activeZdos { get; set; }

		public int memoryMb { get; set; }
	}
	public class ValgrindConfigDto
	{
		public string calculationMode { get; set; } = "TieredBrackets";

		public bool useTopNSkillsOnly { get; set; }

		public int topNSkillsCount { get; set; } = 5;

		public bool resetAccumulatorOnDeath { get; set; } = true;

		public bool enableDebugLogging { get; set; }

		public float earlyGameLossPercent { get; set; } = 8f;

		public float midGameLossPercent { get; set; } = 5f;

		public float lateGameLossPercent { get; set; } = 2.5f;

		public float endgameLossPercent { get; set; } = 1f;

		public float curveMaxLossPercent { get; set; } = 8f;

		public float curveMinLossPercent { get; set; } = 1f;
	}
	public class DagrNottConfigDto
	{
		public float dawnMultiplier { get; set; } = 0.9f;

		public float dayMultiplier { get; set; } = 0.5f;

		public float duskMultiplier { get; set; } = 0.9f;

		public float nightMultiplier { get; set; } = 0.3f;

		public bool logPhaseTransitions { get; set; } = true;

		public float dawnMinutes { get; set; } = 5f;

		public float dayMinutes { get; set; } = 30f;

		public float duskMinutes { get; set; } = 5f;

		public float nightMinutes { get; set; } = 20f;

		public float totalMinutes { get; set; } = 60f;
	}
	public class SkaldConfigDto
	{
		public bool enabled { get; set; } = true;

		public bool enableBosses { get; set; } = true;

		public bool includeBiome { get; set; } = true;

		public bool logToConsole { get; set; } = true;

		public string monsterTemplates { get; set; } = "{victim} was slain by a {killer} in the {biome};{victim} was torn apart by a {killer};A {killer} claimed the soul of {victim}";

		public string bossTemplates { get; set; } = "{victim} was annihilated by the mythical {killer}!;The legendary {killer} crushed {victim} into dust";

		public string overwhelmedMessages { get; set; } = "{victim} was defeated in glorious battle against a horde in the {biome};{victim} fell fighting valiantly against overwhelming odds";

		public string genericDeathMessages { get; set; } = "{victim} has departed for the halls of Valhalla;The Norns have cut the thread of {victim}'s life;{victim} died in the {biome}";
	}
	public class SkaldDeathRecordDto
	{
		public string id { get; set; } = string.Empty;

		public string victimName { get; set; } = string.Empty;

		public string victimSteamId { get; set; } = string.Empty;

		public string killerName { get; set; } = string.Empty;

		public string category { get; set; } = string.Empty;

		public string biome { get; set; } = string.Empty;

		public string formattedMessage { get; set; } = string.Empty;

		public string timestamp { get; set; } = string.Empty;
	}
	public class NjororConfigDto
	{
		public bool enableFairWinds { get; set; } = true;

		public float headwindMitigationPercent { get; set; } = 60f;

		public float minWindSpeedMultiplier { get; set; } = 1f;

		public bool alwaysTailwindInOcean { get; set; }

		public bool checkDeflectOnWindChange { get; set; } = true;

		public int checkDeflectTimeSeconds { get; set; }

		public bool enableWeatherTuning { get; set; } = true;

		public float stormFrequencyMultiplier { get; set; } = 1f;

		public float rainFrequencyMultiplier { get; set; } = 1f;

		public float clearFrequencyMultiplier { get; set; } = 1f;

		public bool enableSerpentTuning { get; set; } = true;

		public float daytimeSerpentSpawnChance { get; set; }

		public float nighttimeSerpentSpawnChance { get; set; } = 5f;

		public float serpentSpawnIntervalSeconds { get; set; } = 1000f;

		public bool allowCalmWeatherDaySerpents { get; set; }
	}
	public class ConsoleLogEntry
	{
		public string time { get; set; } = string.Empty;

		public string source { get; set; } = string.Empty;

		public string text { get; set; } = string.Empty;

		public string level { get; set; } = "info";
	}
	public class AuthRequest
	{
		public string? password { get; set; }
	}
	public class KickRequest
	{
		public string? name { get; set; }
	}
	public class BanRequest
	{
		public string? name { get; set; }

		public string? reason { get; set; }
	}
	public class UnbanRequest
	{
		public string? steamId { get; set; }
	}
	public class UnbindRequest
	{
		public string? steamId { get; set; }

		public string? name { get; set; }
	}
	public class ManualBanRequest
	{
		public string? steamId { get; set; }

		public string? name { get; set; }

		public string? reason { get; set; }
	}
	public class ExecCommandRequest
	{
		public string? command { get; set; }
	}
	public class BroadcastRequest
	{
		public string? message { get; set; }
	}
	public class ScheduleRestartRequest
	{
		public int minutes { get; set; }

		public string? reason { get; set; }
	}
	public class DailyRestartRequest
	{
		public bool enabled { get; set; }

		public string? time { get; set; }
	}
	public class ScheduledRestartState
	{
		public bool active { get; set; }

		public int minutes { get; set; }

		public int totalMinutes { get; set; }

		public long targetTimestamp { get; set; }

		public string reason { get; set; } = string.Empty;
	}
	public class DailyRestartState
	{
		public bool enabled { get; set; }

		public string time { get; set; } = "04:00";
	}
	public class LifecycleConfigState
	{
		public string mode { get; set; } = "ExitOnly";

		public string scriptPath { get; set; } = string.Empty;
	}
	public class PendingConfigChange
	{
		public string module { get; set; } = string.Empty;

		public string moduleName { get; set; } = string.Empty;

		public string timestamp { get; set; } = string.Empty;
	}
	public class OtherModSummaryDto
	{
		public string fileName { get; set; } = string.Empty;

		public string filePath { get; set; } = string.Empty;

		public string displayName { get; set; } = string.Empty;

		public string pluginGuid { get; set; } = string.Empty;

		public string pluginName { get; set; } = string.Empty;

		public string pluginVersion { get; set; } = string.Empty;

		public int sectionCount { get; set; }

		public int settingCount { get; set; }

		public long fileSizeBytes { get; set; }

		public string lastModified { get; set; } = string.Empty;

		public bool isLoadedInGame { get; set; }

		public bool isFirstParty { get; set; }
	}
	public class OtherModConfigEntryDto
	{
		public string key { get; set; } = string.Empty;

		public string value { get; set; } = string.Empty;

		public string? defaultValue { get; set; }

		public string valueType { get; set; } = "String";

		public string description { get; set; } = string.Empty;

		public List<string>? acceptableValues { get; set; }

		public float? minRange { get; set; }

		public float? maxRange { get; set; }
	}
	public class OtherModSectionDto
	{
		public string name { get; set; } = string.Empty;

		public List<OtherModConfigEntryDto> entries { get; set; } = new List<OtherModConfigEntryDto>();
	}
	public class OtherModConfigDetailDto
	{
		public string fileName { get; set; } = string.Empty;

		public string displayName { get; set; } = string.Empty;

		public string pluginGuid { get; set; } = string.Empty;

		public string pluginName { get; set; } = string.Empty;

		public string pluginVersion { get; set; } = string.Empty;

		public bool isLoadedInGame { get; set; }

		public List<OtherModSectionDto> sections { get; set; } = new List<OtherModSectionDto>();

		public string rawContent { get; set; } = string.Empty;

		public string lastModified { get; set; } = string.Empty;
	}
	public class SaveOtherModConfigRequest
	{
		public string fileName { get; set; } = string.Empty;

		public Dictionary<string, Dictionary<string, string>>? updates { get; set; }

		public string? rawContent { get; set; }

		public bool saveRaw { get; set; }
	}
	public class ResetOtherModConfigRequest
	{
		public string fileName { get; set; } = string.Empty;
	}
	public static class WebPortalServer
	{
		private static HttpListener? _listener;

		private static CancellationTokenSource? _cts;

		private static byte[]? _embeddedIndexHtmlBytes;

		private static bool _isRunning;

		public static string AdminPassword { get; private set; } = string.Empty;

		public static void Start(int port, string password)
		{
			if (_isRunning)
			{
				return;
			}
			AdminPassword = password ?? string.Empty;
			_cts = new CancellationTokenSource();
			Task.Run(async delegate
			{
				try
				{
					string[] obj = new string[4]
					{
						$"http://*:{port}/",
						$"http://+:{port}/",
						$"http://127.0.0.1:{port}/",
						$"http://localhost:{port}/"
					};
					bool flag = false;
					string[] array = obj;
					foreach (string text in array)
					{
						try
						{
							_listener = new HttpListener();
							_listener.Prefixes.Add(text);
							_listener.Start();
							flag = true;
							BifrostheimPlugin.Log.LogInfo((object)("[WebPortalServer] Listening on " + text));
						}
						catch (Exception ex)
						{
							BifrostheimPlugin.Log.LogWarning((object)("[WebPortalServer] Could not bind prefix '" + text + "': " + ex.Message));
							try
							{
								_listener?.Close();
							}
							catch
							{
							}
							continue;
						}
						break;
					}
					if (!flag || _listener == null)
					{
						BifrostheimPlugin.Log.LogError((object)$"[WebPortalServer] Failed to bind HTTP listener on port {port}.");
					}
					else
					{
						_isRunning = true;
						LoadEmbeddedAssets();
						while (!_cts.Token.IsCancellationRequested && _listener.IsListening)
						{
							try
							{
								ProcessRequestAsync(await _listener.GetContextAsync());
							}
							catch (HttpListenerException) when (_cts.Token.IsCancellationRequested)
							{
								break;
							}
							catch (Exception ex3)
							{
								if (!_cts.Token.IsCancellationRequested)
								{
									BifrostheimPlugin.Log.LogWarning((object)("[WebPortalServer] Request accept error: " + ex3.Message));
								}
							}
						}
					}
				}
				catch (Exception arg)
				{
					BifrostheimPlugin.Log.LogError((object)$"[WebPortalServer] Server error: {arg}");
				}
				finally
				{
					_isRunning = false;
				}
			});
		}

		public static void Stop()
		{
			if (!_isRunning)
			{
				return;
			}
			try
			{
				_cts?.Cancel();
				_listener?.Stop();
				_listener?.Close();
				BifrostheimPlugin.Log.LogInfo((object)"[WebPortalServer] Web portal server stopped.");
			}
			catch (Exception ex)
			{
				BifrostheimPlugin.Log.LogWarning((object)("[WebPortalServer] Error stopping server: " + ex.Message));
			}
			finally
			{
				_isRunning = false;
			}
		}

		private static async Task ProcessRequestAsync(HttpListenerContext context)
		{
			_ = 1;
			try
			{
				string clientIp = GetClientIp(context.Request);
				string text = context.Request.Url?.AbsolutePath ?? "/";
				if (context.Request.HttpMethod.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase))
				{
					context.Response.AddHeader("Access-Control-Allow-Origin", "*");
					context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
					context.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Admin-Password");
					context.Response.StatusCode = 204;
					context.Response.Close();
				}
				else if (text.StartsWith("/api/", StringComparison.OrdinalIgnoreCase))
				{
					await WebApiRouter.HandleApiRequestAsync(context, text, clientIp);
				}
				else
				{
					await ServeStaticSpaAsync(context.Response);
				}
			}
			catch (Exception arg)
			{
				BifrostheimPlugin.Log.LogError((object)$"[WebPortalServer] Error processing request: {arg}");
				try
				{
					context.Response.StatusCode = 500;
					context.Response.Close();
				}
				catch
				{
				}
			}
		}

		private static async Task ServeStaticSpaAsync(HttpListenerResponse response)
		{
			byte[] array = _embeddedIndexHtmlBytes ?? GetDefaultFallbackHtml();
			response.StatusCode = 200;
			response.ContentType = "text/html; charset=utf-8";
			response.ContentLength64 = array.Length;
			response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
			using (Stream output = response.OutputStream)
			{
				await output.WriteAsync(array, 0, array.Length);
			}
			response.Close();
		}

		private static void LoadEmbeddedAssets()
		{
			try
			{
				Assembly executingAssembly = Assembly.GetExecutingAssembly();
				string name = "Bifrostheim.dist.index.html";
				using (Stream stream = executingAssembly.GetManifestResourceStream(name))
				{
					if (stream != null)
					{
						using (MemoryStream memoryStream = new MemoryStream())
						{
							stream.CopyTo(memoryStream);
							_embeddedIndexHtmlBytes = memoryStream.ToArray();
							BifrostheimPlugin.Log.LogInfo((object)$"[WebPortalServer] Loaded embedded React bundle ({_embeddedIndexHtmlBytes.Length / 1024} KB).");
							return;
						}
					}
				}
				string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
				foreach (string text in manifestResourceNames)
				{
					if (!text.EndsWith("index.html", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					using Stream stream2 = executingAssembly.GetManifestResourceStream(text);
					if (stream2 != null)
					{
						using (MemoryStream memoryStream2 = new MemoryStream())
						{
							stream2.CopyTo(memoryStream2);
							_embeddedIndexHtmlBytes = memoryStream2.ToArray();
							BifrostheimPlugin.Log.LogInfo((object)$"[WebPortalServer] Loaded embedded resource '{text}' ({_embeddedIndexHtmlBytes.Length / 1024} KB).");
							return;
						}
					}
				}
				BifrostheimPlugin.Log.LogWarning((object)"[WebPortalServer] Embedded index.html not found in assembly resources. Using fallback UI.");
			}
			catch (Exception ex)
			{
				BifrostheimPlugin.Log.LogError((object)("[WebPortalServer] Error loading embedded assets: " + ex.Message));
			}
		}

		private static byte[] GetDefaultFallbackHtml()
		{
			string s = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>Bifröstheim Server Portal</title><style>body{background:#030712;color:#f3f4f6;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;} .card{background:#111827;padding:2rem;border-radius:12px;border:1px solid #374151;text-align:center;max-width:480px;}</style></head><body><div class=\"card\"><h1 style=\"color:#f97316;\">Bifröstheim</h1><p>Embedded web portal bundle not found in assembly.</p><p>API is active at <code>/api/modules/installed</code></p></div></body></html>";
			return Encoding.UTF8.GetBytes(s);
		}

		private static string GetClientIp(HttpListenerRequest request)
		{
			string text = request.Headers["X-Forwarded-For"];
			if (!string.IsNullOrWhiteSpace(text))
			{
				string[] array = text.Split(new char[1] { ',' });
				if (array.Length != 0 && !string.IsNullOrWhiteSpace(array[0]))
				{
					return array[0].Trim();
				}
			}
			return request.RemoteEndPoint?.Address?.ToString() ?? "127.0.0.1";
		}
	}
}
namespace Bifrostheim.Helpers
{
	public class BepInExLogListener : ILogListener, IDisposable
	{
		public void LogEvent(object sender, LogEventArgs eventArgs)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			if (eventArgs == null || eventArgs.Data == null)
			{
				return;
			}
			string level;
			if ((eventArgs.Level & 3) != 0)
			{
				level = "error";
			}
			else if ((eventArgs.Level & 4) != 0)
			{
				level = "warn";
			}
			else if ((eventArgs.Level & 0x18) != 0)
			{
				level = "info";
			}
			else
			{
				if ((eventArgs.Level & 0x20) == 0)
				{
					return;
				}
				ConfigEntry<bool> verboseLogging = BifrostheimPlugin.VerboseLogging;
				if (verboseLogging == null || !verboseLogging.Value)
				{
					return;
				}
				level = "info";
			}
			ILogSource source = eventArgs.Source;
			string text = ((source != null) ? source.SourceName : null) ?? "Server";
			string text2 = eventArgs.Data.ToString() ?? string.Empty;
			if (!string.IsNullOrWhiteSpace(text2) && (!text.Equals("Bifrostheim", StringComparison.OrdinalIgnoreCase) || !text2.Contains("[WebPortalServer]")))
			{
				WebApiRouter.AddLog(level, text, text2);
			}
		}

		public void Dispose()
		{
		}
	}
	public static class ConfigSyncManager
	{
		public static Func<string, string, bool>? OnlinePlayerChecker { get; set; }

		public static string GetConfigDirectory()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("BEPINEX_CONFIG_PATH");
			if (!string.IsNullOrWhiteSpace(environmentVariable))
			{
				if (!Directory.Exists(environmentVariable))
				{
					Directory.CreateDirectory(environmentVariable);
				}
				return environmentVariable;
			}
			try
			{
				if (!string.IsNullOrWhiteSpace(Paths.ConfigPath))
				{
					if (!Directory.Exists(Paths.ConfigPath))
					{
						Directory.CreateDirectory(Paths.ConfigPath);
					}
					return Paths.ConfigPath;
				}
			}
			catch
			{
			}
			try
			{
				string text = Path.Combine(Directory.GetCurrentDirectory(), "BepInEx", "config");
				if (!Directory.Exists(text))
				{
					Directory.CreateDirectory(text);
				}
				return text;
			}
			catch
			{
			}
			return AppDomain.CurrentDomain.BaseDirectory;
		}

		public static string ResolveConfigFile(string primaryFileName, params string[] alternativeFileNames)
		{
			string configDirectory = GetConfigDirectory();
			string text = Path.Combine(configDirectory, primaryFileName);
			if (File.Exists(text))
			{
				return text;
			}
			foreach (string path in alternativeFileNames)
			{
				string text2 = Path.Combine(configDirectory, path);
				if (File.Exists(text2))
				{
					return text2;
				}
			}
			return text;
		}

		public static Dictionary<string, Dictionary<string, string>> ReadIniFile(string filePath)
		{
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
			if (!File.Exists(filePath))
			{
				return dictionary;
			}
			try
			{
				string[] array = File.ReadAllLines(filePath, Encoding.UTF8);
				string key = "General";
				if (!dictionary.ContainsKey(key))
				{
					dictionary[key] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				}
				string[] array2 = array;
				for (int i = 0; i < array2.Length; i++)
				{
					string text = array2[i].Trim();
					if (string.IsNullOrEmpty(text) || text.StartsWith("#") || text.StartsWith(";"))
					{
						continue;
					}
					if (text.StartsWith("[") && text.EndsWith("]"))
					{
						key = text.Substring(1, text.Length - 2).Trim();
						if (!dictionary.ContainsKey(key))
						{
							dictionary[key] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
						}
						continue;
					}
					int num = text.IndexOf('=');
					if (num > 0)
					{
						string key2 = text.Substring(0, num).Trim();
						string value = text.Substring(num + 1).Trim();
						dictionary[key][key2] = value;
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = BifrostheimPlugin.Log;
				if (log != null)
				{
					log.LogError((object)("[ConfigSyncManager] Error reading INI file '" + filePath + "': " + ex.Message));
				}
			}
			return dictionary;
		}

		public static void WriteIniFile(string filePath, Dictionary<string, Dictionary<string, string>> updates, string defaultHeader = "")
		{
			try
			{
				string directoryName = Path.GetDirectoryName(filePath);
				if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				if (!File.Exists(filePath))
				{
					StringBuilder stringBuilder = new StringBuilder();
					if (!string.IsNullOrEmpty(defaultHeader))
					{
						stringBuilder.AppendLine(defaultHeader);
						stringBuilder.AppendLine();
					}
					foreach (KeyValuePair<string, Dictionary<string, string>> update in updates)
					{
						stringBuilder.AppendLine("[" + update.Key + "]");
						stringBuilder.AppendLine();
						foreach (KeyValuePair<string, string> item in update.Value)
						{
							stringBuilder.AppendLine(item.Key + " = " + item.Value);
						}
						stringBuilder.AppendLine();
					}
					File.WriteAllText(filePath, stringBuilder.ToString(), Encoding.UTF8);
					ManualLogSource log = BifrostheimPlugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("[ConfigSyncManager] Created new config file at '" + filePath + "'."));
					}
					return;
				}
				List<string> list = File.ReadAllLines(filePath, Encoding.UTF8).ToList();
				Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, Dictionary<string, string>> update2 in updates)
				{
					dictionary[update2.Key] = new Dictionary<string, string>(update2.Value, StringComparer.OrdinalIgnoreCase);
				}
				string text = "";
				for (int i = 0; i < list.Count; i++)
				{
					string text2 = list[i].Trim();
					if (text2.StartsWith("[") && text2.EndsWith("]"))
					{
						if (!string.IsNullOrEmpty(text) && dictionary.TryGetValue(text, out var value) && value.Count > 0)
						{
							foreach (KeyValuePair<string, string> item2 in value.ToList())
							{
								list.Insert(i, item2.Key + " = " + item2.Value);
								value.Remove(item2.Key);
								i++;
							}
						}
						text = text2.Substring(1, text2.Length - 2).Trim();
					}
					else
					{
						if (text2.StartsWith("#") || text2.StartsWith(";"))
						{
							continue;
						}
						int num = text2.IndexOf('=');
						if (num > 0)
						{
							string text3 = text2.Substring(0, num).Trim();
							if (dictionary.TryGetValue(text, out var value2) && value2.TryGetValue(text3, out var value3))
							{
								list[i] = text3 + " = " + value3;
								value2.Remove(text3);
							}
						}
					}
				}
				if (dictionary.TryGetValue(text, out var value4) && value4.Count > 0)
				{
					foreach (KeyValuePair<string, string> item3 in value4)
					{
						list.Add(item3.Key + " = " + item3.Value);
					}
					dictionary.Remove(text);
				}
				foreach (KeyValuePair<string, Dictionary<string, string>> item4 in dictionary)
				{
					if (item4.Value.Count == 0)
					{
						continue;
					}
					list.Add("");
					list.Add("[" + item4.Key + "]");
					list.Add("");
					foreach (KeyValuePair<string, string> item5 in item4.Value)
					{
						list.Add(item5.Key + " = " + item5.Value);
					}
				}
				File.WriteAllLines(filePath, list, Encoding.UTF8);
				ManualLogSource log2 = BifrostheimPlugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)("[ConfigSyncManager] Saved configuration updates to '" + filePath + "'."));
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = BifrostheimPlugin.Log;
				if (log3 != null)
				{
					log3.LogError((object)("[ConfigSyncManager] Error writing INI file '" + filePath + "': " + ex.Message));
				}
			}
		}

		public static void SyncLivePluginConfig(string[] candidateGuids, Action<ConfigFile> updateAction)
		{
			try
			{
				if (Chainloader.PluginInfos == null)
				{
					return;
				}
				foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
				{
					string guid = pluginInfo.Key;
					PluginInfo value = pluginInfo.Value;
					if (value == null || !candidateGuids.Any((string g) => string.Equals(guid, g, StringComparison.OrdinalIgnoreCase) || guid.IndexOf(g, StringComparison.OrdinalIgnoreCase) >= 0))
					{
						continue;
					}
					BaseUnityPlugin instance = value.Instance;
					ConfigFile val = ((instance != null) ? instance.Config : null);
					if (val == null)
					{
						continue;
					}
					updateAction(val);
					try
					{
						val.Save();
						ManualLogSource log = BifrostheimPlugin.Log;
						if (log != null)
						{
							log.LogInfo((object)("[ConfigSyncManager] Live-synchronized and saved ConfigFile for loaded plugin '" + guid + "'."));
						}
					}
					catch (Exception ex)
					{
						ManualLogSource log2 = BifrostheimPlugin.Log;
						if (log2 != null)
						{
							log2.LogWarning((object)("[ConfigSyncManager] Failed to call ConfigFile.Save() on plugin '" + guid + "': " + ex.Message));
						}
					}
				}
			}
			catch (Exception ex2)
			{
				ManualLogSource log3 = BifrostheimPlugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[ConfigSyncManager] Live plugin sync error: " + ex2.Message));
				}
			}
		}

		private static void TrySetEntryValue(ConfigFile configFile, string keyName, object value)
		{
			try
			{
				PropertyInfo property = ((object)configFile).GetType().GetProperty("Keys", BindingFlags.Instance | BindingFlags.Public);
				if (!(property != null) || !(property.GetValue(configFile) is IEnumerable<ConfigDefinition> enumerable))
				{
					return;
				}
				foreach (ConfigDefinition item in enumerable)
				{
					if (!item.Key.Equals(keyName, StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					PropertyInfo property2 = ((object)configFile).GetType().GetProperty("Item", new Type[1] { typeof(ConfigDefinition) });
					if (property2 != null)
					{
						object? value2 = property2.GetValue(configFile, new object[1] { item });
						ConfigEntryBase val = (ConfigEntryBase)((value2 is ConfigEntryBase) ? value2 : null);
						if (val != null)
						{
							val.BoxedValue = Convert.ChangeType(value, val.SettingType, CultureInfo.InvariantCulture);
							break;
						}
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = BifrostheimPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[ConfigSyncManager] Failed to set live entry '" + keyName + "': " + ex.Message));
				}
			}
		}

		public static ValgrindConfigDto LoadValgrindConfig()
		{
			ValgrindConfigDto valgrindConfigDto = new ValgrindConfigDto();
			Dictionary<string, Dictionary<string, string>> ini = ReadIniFile(ResolveConfigFile("com.bigai.valgrind.cfg", "valgrind.cfg"));
			string text = FindValue(ini, "CalculationMode", "calculationMode", "calcMode");
			if (!string.IsNullOrWhiteSpace(text))
			{
				string text2 = text.Trim();
				if (text2.Equals("TieredBrackets", StringComparison.OrdinalIgnoreCase))
				{
					valgrindConfigDto.calculationMode = "TieredBrackets";
				}
				else if (text2.Equals("ContinuousCurve", StringComparison.OrdinalIgnoreCase))
				{
					valgrindConfigDto.calculationMode = "ContinuousCurve";
				}
				else if (text2.Equals("PerSkill", StringComparison.OrdinalIgnoreCase))
				{
					valgrindConfigDto.calculationMode = "PerSkill";
				}
				else
				{
					valgrindConfigDto.calculationMode = text2;
				}
			}
			if (bool.TryParse(FindValue(ini, "UseTopNSkillsOnly", "useTopNSkillsOnly"), out var result))
			{
				valgrindConfigDto.useTopNSkillsOnly = result;
			}
			if (int.TryParse(FindValue(ini, "TopNSkillsCount", "topNSkillsCount"), out var result2))
			{
				valgrindConfigDto.topNSkillsCount = Math.Max(1, Math.Min(20, result2));
			}
			if (bool.TryParse(FindValue(ini, "ResetAccumulatorOnDeath", "resetAccumulatorOnDeath"), out var result3))
			{
				valgrindConfigDto.resetAccumulatorOnDeath = result3;
			}
			if (bool.TryParse(FindValue(ini, "EnableDebugLogging", "enableDebugLogging"), out var result4))
			{
				valgrindConfigDto.enableDebugLogging = result4;
			}
			if (float.TryParse(FindValue(ini, "EarlyGameLossPercent", "earlyGameLossPercent"), NumberStyles.Any, CultureInfo.InvariantCulture, out var result5))
			{
				valgrindConfigDto.earlyGameLossPercent = result5;
			}
			if (float.TryParse(FindValue(ini, "MidGameLossPercent", "midGameLossPercent"), NumberStyles.Any, CultureInfo.InvariantCulture, out var result6))
			{
				valgrindConfigDto.midGameLossPercent = result6;
			}
			if (float.TryParse(FindValue(ini, "LateGameLossPercent", "lateGameLossPercent"), NumberStyles.Any, CultureInfo.InvariantCulture, out var result7))
			{
				valgrindConfigDto.lateGameLossPercent = result7;
			}
			if (float.TryParse(FindValue(ini, "EndgameLossPercent", "endgameLossPercent"), NumberStyles.Any, CultureInfo.InvariantCulture, out var result8))
			{
				valgrindConfigDto.endgameLossPercent = result8;
			}
			if (float.TryParse(FindValue(ini, "CurveMaxLossPercent", "curveMaxLossPercent"), NumberStyles.Any, CultureInfo.InvariantCulture, out var result9))
			{
				valgrindConfigDto.curveMaxLossPercent = result9;
			}
			if (float.TryParse(FindValue(ini, "CurveMinLossPercent", "curveMinLossPercent"), NumberStyles.Any, CultureInfo.InvariantCulture, out var result10))
			{
				valgrindConfigDto.curveMinLossPercent = result10;
			}
			return valgrindConfigDto;
		}

		public static void SaveValgrindConfig(ValgrindConfigDto dto)
		{
			string filePath = ResolveConfigFile("com.bigai.valgrind.cfg", "valgrind.cfg");
			Dictionary<string, Dictionary<string, string>> updates = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase)
			{
				["1 - General"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
				{
					["CalculationMode"] = dto.calculationMode,
					["UseTopNSkillsOnly"] = dto.useTopNSkillsOnly.ToString().ToLowerInvariant(),
					["TopNSkillsCount"] = dto.topNSkillsCount.ToString(CultureInfo.InvariantCulture),
					["ResetAccumulatorOnDeath"] = dto.resetAccumulatorOnDeath.ToString().ToLowerInvariant(),
					["EnableDebugLogging"] = dto.enableDebugLogging.ToString().ToLowerInvariant()
				},
				["2 - Tiered Brackets"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
				{
					["EarlyGameLossPercent"] = dto.earlyGameLossPercent.ToString("F1", CultureInfo.InvariantCulture),
					["MidGameLossPercent"] = dto.midGameLossPercent.ToString("F1", CultureInfo.InvariantCulture),
					["LateGameLossPercent"] = dto.lateGameLossPercent.ToString("F1", CultureInfo.InvariantCulture),
					["EndgameLossPercent"] = dto.endgameLossPercent.ToString("F1", CultureInfo.InvariantCulture)
				},
				["3 - Continuous Curve"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
				{
					["CurveMaxLossPercent"] = dto.curveMaxLossPercent.ToString("F1", CultureInfo.InvariantCulture),
					["CurveMinLossPercent"] = dto.curveMinLossPercent.ToString("F1", CultureInfo.InvariantCulture)
				}
			};
			WriteIniFile(filePath, updates, "## Settings file was created by plugin Valgrind\n## Plugin GUID: com.bigai.valgrind");
			SyncLivePluginConfig(new string[2] { "com.bigai.valgrind", "valgrind" }, delegate(ConfigFile config)
			{
				TrySetEntryValue(config, "CalculationMode", dto.calculationMode);
				TrySetEntryValue(config, "UseTopNSkillsOnly", dto.useTopNSkillsOnly);
				TrySetEntryValue(config, "TopNSkillsCount", dto.topNSkillsCount);
				TrySetEntryValue(config, "ResetAccumulatorOnDeath", dto.resetAccumulatorOnDeath);
				TrySetEntryValue(config, "EnableDebugLogging", dto.enableDebugLogging);
				TrySetEntryValue(config, "EarlyGameLossPercent", dto.earlyGameLossPercent);
				TrySetEntryValue(config, "MidGameLossPercent", dto.midGameLossPercent);
				TrySetEntryValue(config, "LateGameLossPercent", dto.lateGameLossPercent);
				TrySetEntryValue(config, "EndgameLossPercent", dto.endgameLossPercent);
				TrySetEntryValue(config, "CurveMaxLossPercent", dto.curveMaxLossPercent);
				TrySetEntryValue(config, "CurveMinLossPercent", dto.curveMinLossPercent);
			});
		}

		public static DagrNottConfigDto LoadDagrNottConfig()
		{
			DagrNottConfigDto dagrNottConfigDto = new DagrNottConfigDto();
			Dictionary<string, Dictionary<string, string>> ini = ReadIniFile(ResolveConfigFile("com.bigai.dagrnott_customdaycycle.cfg", "com.bigai.dagrandnott.cfg", "com.bigai.dagrnott.cfg", "dagrnott_customdaycycle.cfg", "dagrandnott.cfg", "dagrnott.cfg"));
			string text = FindValue(ini, "DawnMultiplier", "dawnMultiplier");
			if (text != null && float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
			{
				dagrNottConfigDto.dawnMultiplier = (float)Math.Round(Math.Max(0.01f, result), 2);
			}
			string text2 = FindValue(ini, "DayMultiplier", "dayMultiplier");
			if (text2 != null && float.TryParse(text2, NumberStyles.Any, CultureInfo.InvariantCulture, out var result2))
			{
				dagrNottConfigDto.dayMultiplier = (float)Math.Round(Math.Max(0.01f, result2), 2);
			}
			string text3 = FindValue(ini, "DuskMultiplier", "duskMultiplier");
			if (text3 != null && float.TryParse(text3, NumberStyles.Any, CultureInfo.InvariantCulture, out var result3))
			{
				dagrNottConfigDto.duskMultiplier = (float)Math.Round(Math.Max(0.01f, result3), 2);
			}
			string text4 = FindValue(ini, "NightMultiplier", "nightMultiplier");
			if (text4 != null && float.TryParse(text4, NumberStyles.Any, CultureInfo.InvariantCulture, out var result4))
			{
				dagrNottConfigDto.nightMultiplier = (float)Math.Round(Math.Max(0.01f, result4), 2);
			}
			string text5 = FindValue(ini, "LogPhaseTransitions", "logPhaseTransitions");
			if (text5 != null && bool.TryParse(text5, out var result5))
			{
				dagrNottConfigDto.logPhaseTransitions = result5;
			}
			dagrNottConfigDto.dawnMinutes = (float)Math.Round(4.5f / Math.Max(0.001f, dagrNottConfigDto.dawnMultiplier), 1);
			dagrNottConfigDto.dayMinutes = (float)Math.Round(15f / Math.Max(0.001f, dagrNottConfigDto.dayMultiplier), 1);
			dagrNottConfigDto.duskMinutes = (float)Math.Round(4.5f / Math.Max(0.001f, dagrNottConfigDto.duskMultiplier), 1);
			dagrNottConfigDto.nightMinutes = (float)Math.Round(6f / Math.Max(0.001f, dagrNottConfigDto.nightMultiplier), 1);
			dagrNottConfigDto.totalMinutes = (float)Math.Round(dagrNottConfigDto.dawnMinutes + dagrNottConfigDto.dayMinutes + dagrNottConfigDto.duskMinutes + dagrNottConfigDto.nightMinutes, 1);
			return dagrNottConfigDto;
		}

		public static void SaveDagrNottConfig(DagrNottConfigDto dto)
		{
			string filePath = ResolveConfigFile("com.bigai.dagrnott_customdaycycle.cfg", "com.bigai.dagrandnott.cfg", "com.bigai.dagrnott.cfg", "dagrnott_customdaycycle.cfg", "dagrandnott.cfg", "dagrnott.cfg");
			Dictionary<string, Dictionary<string, string>> updates = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase)
			{
				["DayCycle"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
				{
					["DawnMultiplier"] = dto.dawnMultiplier.ToString("F2", CultureInfo.InvariantCulture),
					["DayMultiplier"] = dto.dayMultiplier.ToString("F2", CultureInfo.InvariantCulture),
					["DuskMultiplier"] = dto.duskMultiplier.ToString("F2", CultureInfo.InvariantCulture),
					["NightMultiplier"] = dto.nightMultiplier.ToString("F2", CultureInfo.InvariantCulture)
				},
				["Logging"] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["LogPhaseTransitions"] = dto.logPhaseTransitions.ToString().ToLowerInvariant() }
			};
			WriteIniFile(filePath, updates, "## Settings file was created by plugin DagrNott_CustomDayCycle\n## Plugin GUID: com.bigai.dagrnott_customdaycycle");
			SyncLivePluginConfig(new string[6] { "com.bigai.dagrnott_customdaycycle", "com.bigai.dagrandnott", "com.bigai.dagrnott", "dagrnott_customdaycycle", "dagrandnott", "dagrnott" }, delegate(ConfigFile config)
			{
				TrySetEntryValue(config, "DawnMultiplier", dto.dawnMultiplier);
				TrySetEntryValue(config, "DayMultiplier", dto.dayMultiplier);
				TrySetEntryValue(config, "DuskMultiplier", dto.duskMultiplier);
				TrySetEntryValue(config, "NightMultiplier", dto.nightMultiplier);
				TrySetEntryValue(config, "LogPhaseTransitions", dto.logPhaseTransitions);
			});
		}

		public static SkaldConfigDto LoadSkaldConfig()
		{
			SkaldConfigDto skaldConfigDto = new SkaldConfigDto();
			Dictionary<string, Dictionary<string, string>> ini = ReadIniFile(ResolveConfigFile("com.bigai.skald_vikingkillfeed.cfg", "com.bigai.skald.cfg", "skald_vikingkillfeed.cfg", "skald.cfg"));
			if (bool.TryParse(FindValue(ini, "EnableDeathAnnouncements", "Enabled"), out var result))
			{
				skaldConfigDto.enabled = result;
			}
			if (bool.TryParse(Fin