using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace PortalLimit.Server; [BepInPlugin("keith.valheim.portallimit.server", "Portal Limit and Discovery Server", "0.1.4")] public class PortalLimitServerPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(TeleportWorld), "SetConnectedPortal")] private static class TeleportWorldSetConnectedPortalPatch { private static void Prefix(TeleportWorld __instance, ref ZDOID targetID) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance))) { targetID = ZDOID.None; } } } [HarmonyPatch(typeof(TeleportWorld), "RPC_SetConnected")] private static class TeleportWorldRpcSetConnectedPatch { private static void Prefix(TeleportWorld __instance, ref ZDOID portalID) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance != (Object)null && string.IsNullOrWhiteSpace(GetPortalTag(__instance))) { portalID = ZDOID.None; } } } [HarmonyPatch(typeof(Piece), "OnPlaced")] private static class PieceOnPlacedPatch { private static void Postfix(Piece __instance) { EnforceIfNeeded(__instance); } } [HarmonyPatch(typeof(WearNTear), "RPC_Remove")] private static class WearNTearRemovePatch { private static bool Prefix(WearNTear __instance, long sender) { if ((Object)(object)Instance == (Object)null || (Object)(object)__instance == (Object)null) { return true; } Piece component = ((Component)__instance).GetComponent(); if (!Instance.IsLimitedPortal(component)) { return true; } long creator = component.GetCreator(); if (sender == creator || Instance.IsSenderAdmin(sender)) { return true; } if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.notice", new object[1] { "Only the builder or an admin can remove this portal." }); } Log.LogWarning((object)("Blocked portal remove request sender=" + sender + " creator=" + creator + ".")); return false; } } public const string PluginGuid = "keith.valheim.portallimit.server"; public const string PluginName = "Portal Limit and Discovery Server"; public const string PluginVersion = "0.1.4"; private const string RpcConfigRequest = "keith.valheim.portallimit.config_request"; private const string RpcConfigResponse = "keith.valheim.portallimit.config_response"; private const string RpcConfigUpdate = "keith.valheim.portallimit.config_update"; private const string RpcCountRequest = "keith.valheim.portallimit.count_request"; private const string RpcCountResponse = "keith.valheim.portallimit.count_response"; private const string RpcNotice = "keith.valheim.portallimit.notice"; private const string RpcDiscoveryMarkRequest = "keith.valheim.portallimit.discovery_mark_request"; private const string RpcDiscoveryUnlockRequest = "keith.valheim.portallimit.discovery_unlock_request"; private const string RpcDiscoveryRelockRequest = "keith.valheim.portallimit.discovery_relock_request"; private const string RpcDiscoveryUnlockListRequest = "keith.valheim.portallimit.discovery_unlock_list_request"; private const string RpcDiscoveryUnlockListResponse = "keith.valheim.portallimit.discovery_unlock_list_response"; private const string RpcAdminReportRequest = "keith.valheim.portallimit.admin_report_request"; private const string RpcAdminReportResponse = "keith.valheim.portallimit.admin_report_response"; private const string RpcPortalMetadataUpdate = "keith.valheim.portallimit.portal_metadata_update"; private const string DiscoveryRoleKey = "PortalLimit_DiscoveryRole"; private const string DiscoveryLinkKey = "PortalLimit_DiscoveryLink"; private const string DiscoveryColorKey = "PortalLimit_DiscoveryColor"; private const string DiscoveryMarkerTextKey = "PortalLimit_DiscoveryMarkerText"; private const string DiscoveryShowMarkerKey = "PortalLimit_DiscoveryShowMarker"; private const string DiscoverySuppressGlowKey = "PortalLimit_DiscoverySuppressGlow"; private const string DiscoveryLockedHoverTextKey = "PortalLimit_DiscoveryLockedHoverText"; private const string DiscoveryRemoteHoverTextKey = "PortalLimit_DiscoveryRemoteHoverText"; private const string DiscoveryUnlockedHoverTextKey = "PortalLimit_DiscoveryUnlockedHoverText"; private const string PortalBuiltUtcKey = "PortalLimit_BuiltUtc"; private const string PortalBuilderNameKey = "PortalLimit_BuilderName"; private const string PortalBuilderIdKey = "PortalLimit_BuilderId"; private const string PortalLastUsedUtcKey = "PortalLimit_LastUsedUtc"; private const string PortalLastUsedNameKey = "PortalLimit_LastUsedName"; private const string PortalLastUsedIdKey = "PortalLimit_LastUsedId"; private const int DiscoveryRoleNone = 0; private const int DiscoveryRoleLocked = 1; private const int DiscoveryRoleRemote = 2; internal static PortalLimitServerPlugin Instance; internal static ManualLogSource Log; private static readonly FieldInfo AllPiecesField = AccessTools.Field(typeof(Piece), "s_allPieces"); private static readonly FieldInfo PeerSocketField = AccessTools.Field(typeof(ZNetPeer), "m_socket"); private static readonly FieldInfo OnZdoDestroyedField = AccessTools.Field(typeof(ZDOMan), "m_onZDODestroyed"); private static readonly FieldInfo NetScenePrefabsField = AccessTools.Field(typeof(ZNetScene), "m_prefabs"); private static readonly MethodInfo GetPrefabNameMethod = AccessTools.Method(typeof(ZNetView), "GetPrefabName", (Type[])null, (Type[])null); private static readonly MethodInfo GetPortalTextMethod = AccessTools.Method(typeof(TeleportWorld), "GetText", (Type[])null, (Type[])null); private Harmony harmony; private bool rpcRegistered; private bool portalIndexBuilt; private bool zdoDestroyedHooked; private string dataDirectory; private string unlockFilePath; private string unlockHistoryFilePath; private string portalCountsFilePath; private string portalPlacementLogFilePath; private readonly Dictionary> unlockedLinksByPlayer = new Dictionary>(); private readonly Dictionary> portalIdsByCreator = new Dictionary>(); private readonly Dictionary creatorByPortalId = new Dictionary(); private readonly Dictionary creatorNamesByCreator = new Dictionary(); private ConfigEntry maxPortalsPerPlayer; private ConfigEntry adminBypass; private ConfigEntry enforceOnServer; private ConfigEntry portalPrefabNames; private ConfigEntry discoveryPortalsEnabled; private ConfigEntry discoveryAdminBypass; private ConfigEntry adminHoldEToEdit; private ConfigEntry showDiscoveryHoverText; private ConfigEntry discoveryLockedHoverText; private ConfigEntry discoveryRemoteHoverText; private ConfigEntry discoveryUnlockedHoverText; private ConfigEntry discoveryLockedMessage; private ConfigEntry discoveryUnlockMessage; private ConfigEntry discoveryShowWorldMarker; private ConfigEntry discoveryTintPortalModel; private ConfigEntry discoverySuppressLockedGlow; private ConfigEntry discoveryLockedColor; private ConfigEntry discoveryRemoteColor; private ConfigEntry discoveryUnlockedColor; private void Awake() { //IL_033e: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; maxPortalsPerPlayer = ((BaseUnityPlugin)this).Config.Bind("General", "MaxPortalsPerPlayer", 10, "Maximum number of matching portals each player may own. Set to 0 or below to disable the limit."); adminBypass = ((BaseUnityPlugin)this).Config.Bind("General", "AdminBypass", true, "Allow Valheim admins to exceed the portal limit."); enforceOnServer = ((BaseUnityPlugin)this).Config.Bind("General", "EnforceOnServer", true, "Delete newly placed portals on the server when they exceed the limit. Keep this enabled for real enforcement."); portalPrefabNames = ((BaseUnityPlugin)this).Config.Bind("General", "PortalPrefabNames", "portal_wood,portal_stone", "Comma-separated portal prefab names to limit. Leave empty to limit every piece with a TeleportWorld component."); discoveryPortalsEnabled = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "Enabled", true, "Enable per-player discovery-gated portals."); discoveryAdminBypass = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "AdminBypass", true, "Allow Valheim admins to use locked discovery portals without unlocking them."); adminHoldEToEdit = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "AdminHoldEToEdit", true, "Allow admins with the client plugin to hold E on a portal to open the Portal Limit editor."); showDiscoveryHoverText = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "ShowHoverText", true, "Show discovery portal status lines in portal hover text for clients with the plugin."); discoveryLockedHoverText = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "LockedHoverText", "Locked: find the other end first.", "Hover text shown for locked discovery portals before a player unlocks the link."); discoveryRemoteHoverText = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "RemoteHoverText", "Discovery portal: use this side to unlock the route.", "Hover text shown for the discovery side of a locked route."); discoveryUnlockedHoverText = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "UnlockedHoverText", "Discovery route unlocked.", "Hover text shown after the player unlocks the route."); discoveryLockedMessage = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "LockedMessage", "This portal is locked. Find and enter the other end first.", "Message shown when a player tries to use a locked discovery portal."); discoveryUnlockMessage = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "UnlockMessage", "Discovery portal unlocked.", "Message shown when a player unlocks a discovery portal route."); discoveryShowWorldMarker = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "ShowWorldMarker", true, "Show a visible marker on marked discovery portals for clients with the plugin."); discoveryTintPortalModel = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "TintPortalModel", false, "Tint the portal model color for clients with the plugin."); discoverySuppressLockedGlow = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "SuppressLockedGlow", false, "Hide connected portal glow and particle effects on locked portals before the route is unlocked."); discoveryLockedColor = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "LockedColor", "#ff9f1a", "Hex color for locked portals before the route is unlocked."); discoveryRemoteColor = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "RemoteColor", "#38a6ff", "Hex color for the far-side portal that unlocks the route."); discoveryUnlockedColor = ((BaseUnityPlugin)this).Config.Bind("Discovery Portals", "UnlockedColor", "#42ff68", "Hex color for portals after the player has unlocked the route."); dataDirectory = Path.Combine(Paths.ConfigPath, "PortalLimit"); unlockFilePath = Path.Combine(dataDirectory, "discovery_unlocks.txt"); unlockHistoryFilePath = Path.Combine(dataDirectory, "discovery_unlock_history.tsv"); portalCountsFilePath = Path.Combine(dataDirectory, "portal_counts.tsv"); portalPlacementLogFilePath = Path.Combine(dataDirectory, "portal_placements.tsv"); Directory.CreateDirectory(dataDirectory); EnsurePlacementLogHeader(); EnsureUnlockHistoryHeader(); LoadUnlocks(); harmony = new Harmony("keith.valheim.portallimit.server"); harmony.PatchAll(); ((MonoBehaviour)this).InvokeRepeating("TryRegisterRpcs", 1f, 1f); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Portal Limit server component loaded for dedicated or local-host server use."); } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } UnhookZdoDestroyed(); } private static bool IsDedicatedServerProcess() { string processName = Process.GetCurrentProcess().ProcessName; return processName.StartsWith("valheim_server", StringComparison.OrdinalIgnoreCase); } private void TryRegisterRpcs() { if (!rpcRegistered && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.Register("keith.valheim.portallimit.config_request", (Action)OnConfigRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.config_update", (Action)OnConfigUpdate); ZRoutedRpc.instance.Register("keith.valheim.portallimit.count_request", (Action)OnCountRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.discovery_mark_request", (Action)OnDiscoveryMarkRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.discovery_unlock_request", (Action)OnDiscoveryUnlockRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.discovery_relock_request", (Action)OnDiscoveryRelockRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.discovery_unlock_list_request", (Action)OnDiscoveryUnlockListRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.admin_report_request", (Action)OnAdminReportRequest); ZRoutedRpc.instance.Register("keith.valheim.portallimit.portal_metadata_update", (Action)OnPortalMetadataUpdate); rpcRegistered = true; ((MonoBehaviour)this).CancelInvoke("TryRegisterRpcs"); HookZdoDestroyed(); Log.LogInfo((object)"Registered portal limit server RPC handlers."); } } private void OnConfigRequest(long sender, string playerName) { if (IsDedicatedOrHostServer()) { bool flag = false; if (!string.IsNullOrEmpty(playerName) && playerName.StartsWith("[silent]", StringComparison.Ordinal)) { flag = true; playerName = playerName.Substring("[silent]".Length); } bool flag2 = IsSenderAdmin(sender); string message = (flag ? string.Empty : (flag2 ? "Portal limit settings loaded." : "Only admins can change portal limit settings.")); string text = BuildConfigPayload(flag2, message); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { text }); Log.LogInfo((object)("Config request from " + playerName + " admin=" + flag2)); } } private void OnConfigUpdate(long sender, string payload) { if (!IsDedicatedOrHostServer()) { return; } if (!IsSenderAdmin(sender)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: false, "Only admins can change portal limit settings.") }); return; } Dictionary dictionary = ParsePayload(payload); if (dictionary.TryGetValue("max", out var value) && int.TryParse(value, out var result)) { maxPortalsPerPlayer.Value = Math.Max(0, result); } if (dictionary.TryGetValue("adminBypass", out value) && bool.TryParse(value, out var result2)) { adminBypass.Value = result2; } if (dictionary.TryGetValue("enforceOnServer", out value) && bool.TryParse(value, out result2)) { enforceOnServer.Value = result2; } if (dictionary.TryGetValue("portalPrefabNames", out value)) { portalPrefabNames.Value = value.Trim(); portalIndexBuilt = false; } if (dictionary.TryGetValue("discoveryEnabled", out value) && bool.TryParse(value, out result2)) { discoveryPortalsEnabled.Value = result2; } if (dictionary.TryGetValue("discoveryAdminBypass", out value) && bool.TryParse(value, out result2)) { discoveryAdminBypass.Value = result2; } if (dictionary.TryGetValue("adminHoldEToEdit", out value) && bool.TryParse(value, out result2)) { adminHoldEToEdit.Value = result2; } if (dictionary.TryGetValue("showDiscoveryHoverText", out value) && bool.TryParse(value, out result2)) { showDiscoveryHoverText.Value = result2; } if (dictionary.TryGetValue("discoveryLockedHoverText", out value)) { discoveryLockedHoverText.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryRemoteHoverText", out value)) { discoveryRemoteHoverText.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryUnlockedHoverText", out value)) { discoveryUnlockedHoverText.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryLockedMessage", out value)) { discoveryLockedMessage.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryUnlockMessage", out value)) { discoveryUnlockMessage.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryShowWorldMarker", out value) && bool.TryParse(value, out result2)) { discoveryShowWorldMarker.Value = result2; } if (dictionary.TryGetValue("discoveryTintPortalModel", out value) && bool.TryParse(value, out result2)) { discoveryTintPortalModel.Value = result2; } if (dictionary.TryGetValue("discoverySuppressLockedGlow", out value) && bool.TryParse(value, out result2)) { discoverySuppressLockedGlow.Value = result2; } if (dictionary.TryGetValue("discoveryLockedColor", out value)) { discoveryLockedColor.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryRemoteColor", out value)) { discoveryRemoteColor.Value = value.Trim(); } if (dictionary.TryGetValue("discoveryUnlockedColor", out value)) { discoveryUnlockedColor.Value = value.Trim(); } ((BaseUnityPlugin)this).Config.Save(); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Portal limit settings saved.") }); Log.LogInfo((object)("Portal limit config updated by peer " + sender + ".")); } private void OnCountRequest(long sender, long creatorId) { if (IsDedicatedOrHostServer()) { EnsurePortalIndexBuilt(); bool flag = IsSenderAdmin(sender); long creatorId2 = (flag ? creatorId : sender); int num = CountPortals(creatorId2); int value = maxPortalsPerPlayer.Value; bool flag2 = value > 0 && num >= value; ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.count_response", new object[4] { num, value, flag2, BuildConfigPayload(flag, string.Empty) }); } } private void OnAdminReportRequest(long sender, string command) { if (IsDedicatedOrHostServer()) { if (!IsSenderAdmin(sender)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.admin_report_response", new object[1] { "Portal Limit: only admins can use server-wide F5 reports." }); } else { string text = BuildAdminReport(command); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.admin_report_response", new object[1] { text }); } } } private void OnDiscoveryMarkRequest(long sender, ZDOID portalId, string payload) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_04ac: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Unknown result type (might be due to invalid IL or missing references) if (!IsDedicatedOrHostServer()) { return; } if (!IsSenderAdmin(sender)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: false, "Only admins can mark discovery portals.") }); return; } if (ZDOMan.instance == null || ((ZDOID)(ref portalId)).IsNone()) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "No valid portal was targeted.") }); return; } ZDO zDO = ZDOMan.instance.GetZDO(portalId); if (zDO == null) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Could not find that portal on the server.") }); return; } Dictionary dictionary = ParsePayload(payload); int.TryParse(dictionary.TryGetValue("role", out var value) ? value : "0", out var result); bool.TryParse(dictionary.TryGetValue("showMarker", out value) ? value : discoveryShowWorldMarker.Value.ToString(), out var result2); bool.TryParse(dictionary.TryGetValue("suppressGlow", out value) ? value : discoverySuppressLockedGlow.Value.ToString(), out var result3); int num = ((result == 1 || result == 2) ? result : 0); string text = (dictionary.TryGetValue("link", out value) ? value.Trim() : string.Empty); string normalized = (dictionary.TryGetValue("markerColor", out value) ? value.Trim() : string.Empty); string text2 = NormalizeMarkerTextValue(dictionary.TryGetValue("markerText", out value) ? value : string.Empty); string text3 = (dictionary.TryGetValue("lockedHoverText", out value) ? value.Trim() : string.Empty); string text4 = (dictionary.TryGetValue("remoteHoverText", out value) ? value.Trim() : string.Empty); string text5 = (dictionary.TryGetValue("unlockedHoverText", out value) ? value.Trim() : string.Empty); if (num != 0 && string.IsNullOrWhiteSpace(text)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Enter a portal tag first.") }); return; } if (num != 0) { if (!TryNormalizeHexColor(normalized, out normalized)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Enter a marker color like #ff3030.") }); return; } if (TryFindDiscoveryEndpointConflict(portalId, num, text, out var conflictId)) { string text6 = ((num == 1) ? "locked entrance" : "unlocked entrance"); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "A " + text6 + " already exists for portal tag '" + text + "'.") }); Log.LogWarning((object)string.Concat("Rejected duplicate discovery ", text6, " for link '", text, "' existing=", conflictId, " attempted=", portalId)); return; } } zDO.Set("PortalLimit_DiscoveryRole", num); zDO.Set("PortalLimit_DiscoveryLink", (num == 0) ? string.Empty : text); zDO.Set("PortalLimit_DiscoveryColor", (num == 0) ? string.Empty : normalized); zDO.Set("PortalLimit_DiscoveryMarkerText", (num == 0) ? string.Empty : text2); zDO.Set("PortalLimit_DiscoveryShowMarker", (num != 0) ? (result2 ? 1 : 0) : 0); zDO.Set("PortalLimit_DiscoverySuppressGlow", (num != 0) ? (result3 ? 1 : 0) : 0); zDO.Set("PortalLimit_DiscoveryLockedHoverText", (num == 0) ? string.Empty : text3); zDO.Set("PortalLimit_DiscoveryRemoteHoverText", (num == 0) ? string.Empty : text4); zDO.Set("PortalLimit_DiscoveryUnlockedHoverText", (num == 0) ? string.Empty : text5); ZDOMan.instance.ForceSendZDO(portalId); string text7 = ((num == 0) ? "Portal settings saved." : "Portal settings saved."); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, text7) }); Log.LogInfo((object)(text7 + " zdo=" + portalId)); } private void OnDiscoveryRelockRequest(long sender, string link) { if (!IsDedicatedOrHostServer()) { return; } if (!IsSenderAdmin(sender)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: false, "Only admins can relock discovery portals.") }); return; } string text = (link ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Enter a portal tag first.") }); return; } int num = 0; foreach (HashSet value in unlockedLinksByPlayer.Values) { if (value.Remove(text)) { num++; } } SaveUnlocks(); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.config_response", new object[1] { BuildConfigPayload(isAdmin: true, "Relocked portal tag '" + text + "' for " + num + " player record(s).") }); Log.LogInfo((object)("Relocked discovery portal link '" + text + "' by admin sender " + sender + ".")); } private bool TryFindDiscoveryEndpointConflict(ZDOID currentPortalId, int role, string link, out ZDOID conflictId) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) conflictId = ZDOID.None; if (ZDOMan.instance == null || string.IsNullOrWhiteSpace(link)) { return false; } EnsurePortalIndexBuilt(); foreach (ZDOID item in new List(creatorByPortalId.Keys)) { ZDO zDO = ZDOMan.instance.GetZDO(item); if (zDO == null || ((ZDOID)(ref zDO.m_uid)).Equals(currentPortalId) || zDO.GetInt("PortalLimit_DiscoveryRole", 0) != role || !string.Equals(zDO.GetString("PortalLimit_DiscoveryLink", string.Empty), link, StringComparison.OrdinalIgnoreCase)) { continue; } conflictId = zDO.m_uid; return true; } return false; } private void OnDiscoveryUnlockRequest(long sender, long playerId, string link) { if (!IsDedicatedOrHostServer()) { return; } long num = (IsSenderAdmin(sender) ? playerId : sender); Dictionary dictionary = ParsePayload(link); string value; string text = ((dictionary.Count > 0 && dictionary.TryGetValue("link", out value)) ? value.Trim() : (link ?? string.Empty).Trim()); string playerName = (dictionary.TryGetValue("playerName", out value) ? value.Trim() : string.Empty); string portalId = (dictionary.TryGetValue("portalId", out value) ? value.Trim() : string.Empty); string x = (dictionary.TryGetValue("x", out value) ? value.Trim() : string.Empty); string y = (dictionary.TryGetValue("y", out value) ? value.Trim() : string.Empty); string z = (dictionary.TryGetValue("z", out value) ? value.Trim() : string.Empty); if (discoveryPortalsEnabled.Value && num != 0 && !string.IsNullOrWhiteSpace(text)) { HashSet orCreateUnlockSet = GetOrCreateUnlockSet(num); bool flag = orCreateUnlockSet.Add(text); if (flag) { SaveUnlocks(); AppendUnlockHistoryLog(num, playerName, text, portalId, x, y, z); Log.LogInfo((object)("Unlocked discovery portal link '" + text + "' for player " + num + ".")); } ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.discovery_unlock_list_response", new object[1] { BuildUnlockPayload(num, flag ? discoveryUnlockMessage.Value : string.Empty) }); } } private void OnDiscoveryUnlockListRequest(long sender, long playerId) { if (IsDedicatedOrHostServer()) { long playerId2 = (IsSenderAdmin(sender) ? playerId : sender); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "keith.valheim.portallimit.discovery_unlock_list_response", new object[1] { BuildUnlockPayload(playerId2, string.Empty) }); } } private void OnPortalMetadataUpdate(long sender, string payload) { //IL_0269: Unknown result type (might be due to invalid IL or missing references) if (!IsDedicatedOrHostServer()) { return; } Dictionary dictionary = ParsePayload(payload); string value; string a = (dictionary.TryGetValue("action", out value) ? value.Trim() : string.Empty); string portalKey = (dictionary.TryGetValue("portalId", out value) ? value.Trim() : string.Empty); string text = (dictionary.TryGetValue("playerName", out value) ? value.Trim() : string.Empty); if (!dictionary.TryGetValue("playerId", out value) || !long.TryParse(value, out var result)) { result = sender; } if (result == 0) { result = sender; } ZDO val = FindPortalZdoByKey(portalKey); if (val == null) { return; } bool flag = IsSenderAdmin(sender); long @long = val.GetLong(ZDOVars.s_creator, 0L); bool flag2 = @long == 0 || @long == sender || @long == result; if (!flag && !flag2 && !string.Equals(a, "used", StringComparison.OrdinalIgnoreCase)) { return; } if (!string.IsNullOrWhiteSpace(text)) { creatorNamesByCreator[result] = text; } if (string.Equals(a, "placed", StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(val.GetString("PortalLimit_BuiltUtc", string.Empty))) { val.Set("PortalLimit_BuiltUtc", DateTime.UtcNow.ToString("o")); } if (!string.IsNullOrWhiteSpace(text)) { val.Set("PortalLimit_BuilderName", text); } if (result != 0) { val.Set("PortalLimit_BuilderId", result); TrackPortal(val, result); } } else if (string.Equals(a, "used", StringComparison.OrdinalIgnoreCase)) { val.Set("PortalLimit_LastUsedUtc", DateTime.UtcNow.ToString("o")); if (!string.IsNullOrWhiteSpace(text)) { val.Set("PortalLimit_LastUsedName", text); } if (result != 0) { val.Set("PortalLimit_LastUsedId", result); } } if (ZDOMan.instance != null) { ZDOMan.instance.ForceSendZDO(val.m_uid); } } internal static void EnforceIfNeeded(Piece piece) { if ((Object)(object)Instance == (Object)null || !Instance.enforceOnServer.Value || !IsDedicatedOrHostServer() || !Instance.IsLimitedPortal(piece)) { return; } long creator = piece.GetCreator(); if (creator == 0) { ((MonoBehaviour)Instance).StartCoroutine(Instance.EnforceNextFrame(piece)); return; } Instance.EnsurePortalIndexBuilt(); Instance.TrackPortal(piece, creator); int num = Instance.CountPortals(creator); int value = Instance.maxPortalsPerPlayer.Value; if (Instance.adminBypass.Value && Instance.IsCreatorAdmin(creator)) { Instance.SavePortalCounts(); Instance.AppendPortalPlacementLog(piece, creator, "PLACED_ADMIN_BYPASS"); Instance.SendPortalCountNotice(creator, num, value); } else if (value <= 0) { Instance.SavePortalCounts(); Instance.AppendPortalPlacementLog(piece, creator, "PLACED"); Instance.SendPortalCountNotice(creator, num, value); } else if (num <= Instance.maxPortalsPerPlayer.Value) { Instance.SavePortalCounts(); Instance.AppendPortalPlacementLog(piece, creator, "PLACED"); Instance.SendPortalCountNotice(creator, num, value); } else { ((MonoBehaviour)Instance).StartCoroutine(Instance.RemoveExcessPortalNextFrame(piece, creator, num)); } } private IEnumerator EnforceNextFrame(Piece piece) { yield return null; if (!((Object)(object)piece == (Object)null)) { long creatorId = piece.GetCreator(); if (creatorId == 0) { Log.LogWarning((object)"Could not enforce portal limit because placed portal has no creator id."); } else { EnforceIfNeeded(piece); } } } private IEnumerator RemoveExcessPortalNextFrame(Piece piece, long creatorId, int count) { yield return null; if (!((Object)(object)piece == (Object)null)) { Log.LogInfo((object)("Removing portal from creator " + creatorId + " because they have " + count + "/" + maxPortalsPerPlayer.Value + ".")); AppendPortalPlacementLog(piece, creatorId, "REMOVED_OVER_LIMIT"); UntrackPortal(piece); RemovePiece(piece); if (ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(creatorId, "keith.valheim.portallimit.notice", new object[1] { "Portal limit reached: " + maxPortalsPerPlayer.Value + " portal(s) per player." }); } SavePortalCounts(); } } private void SendPortalCountNotice(long creatorId, int count, int max) { if (ZRoutedRpc.instance != null && creatorId != 0) { string text = ((max > 0) ? max.ToString() : "unlimited"); ZRoutedRpc.instance.InvokeRoutedRPC(creatorId, "keith.valheim.portallimit.notice", new object[1] { "Portals placed: " + count + " / " + text }); } } private void RemovePiece(Piece piece) { WearNTear component = ((Component)piece).GetComponent(); if ((Object)(object)component != (Object)null) { component.Remove(true); return; } ZNetView component2 = ((Component)piece).GetComponent(); if ((Object)(object)component2 != (Object)null && component2.IsValid()) { component2.Destroy(); } } private bool IsLimitedPortal(Piece piece) { if ((Object)(object)piece == (Object)null || (Object)(object)((Component)piece).GetComponent() == (Object)null) { return false; } HashSet portalPrefabNameSet = GetPortalPrefabNameSet(); if (portalPrefabNameSet.Count == 0) { return true; } ZNetView component = ((Component)piece).GetComponent(); string prefabName = GetPrefabName(component, piece); return portalPrefabNameSet.Contains(prefabName); } private static string GetPrefabName(ZNetView nview, Piece piece) { if ((Object)(object)nview != (Object)null && GetPrefabNameMethod != null) { object obj = GetPrefabNameMethod.Invoke(nview, null); if (obj != null) { return obj.ToString(); } } return ((Object)(object)piece != (Object)null) ? ((Object)piece).name.Replace("(Clone)", string.Empty) : string.Empty; } private int CountPortals(long creatorId) { if (creatorId == 0) { return 0; } EnsurePortalIndexBuilt(); HashSet value; return portalIdsByCreator.TryGetValue(creatorId, out value) ? value.Count : 0; } private void EnsurePortalIndexBuilt() { if (!portalIndexBuilt && ZDOMan.instance != null) { RebuildPortalIndex(); } } private void RebuildPortalIndex() { portalIdsByCreator.Clear(); creatorByPortalId.Clear(); creatorNamesByCreator.Clear(); if (ZDOMan.instance == null) { return; } HashSet strictPortalPrefabNameSet = GetStrictPortalPrefabNameSet(); foreach (string item in strictPortalPrefabNameSet) { int num = 0; List list = new List(); while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(item, list, ref num)) { } foreach (ZDO item2 in list) { TrackPortal(item2); } } portalIndexBuilt = true; SavePortalCounts(); Log.LogInfo((object)("Built global portal index with " + creatorByPortalId.Count + " portal(s) across " + portalIdsByCreator.Count + " creator(s).")); } private HashSet GetStrictPortalPrefabNameSet() { HashSet portalPrefabNameSet = GetPortalPrefabNameSet(); if (portalPrefabNameSet.Count > 0) { return portalPrefabNameSet; } if ((Object)(object)ZNetScene.instance == (Object)null || NetScenePrefabsField == null) { return portalPrefabNameSet; } if (!(NetScenePrefabsField.GetValue(ZNetScene.instance) is IEnumerable enumerable)) { return portalPrefabNameSet; } foreach (object item in enumerable) { GameObject val = (GameObject)((item is GameObject) ? item : null); if ((Object)(object)val != (Object)null && (Object)(object)val.GetComponent() != (Object)null) { portalPrefabNameSet.Add(((Object)val).name.Replace("(Clone)", string.Empty)); } } return portalPrefabNameSet; } private void TrackPortal(Piece piece, long creatorId) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null && string.IsNullOrWhiteSpace(val2.GetString("PortalLimit_BuiltUtc", string.Empty))) { val2.Set("PortalLimit_BuiltUtc", DateTime.UtcNow.ToString("o")); if (ZDOMan.instance != null) { ZDOMan.instance.ForceSendZDO(val2.m_uid); } } TrackPortal(val2, creatorId); } private void TrackPortal(ZDO zdo) { TrackPortal(zdo, (zdo != null) ? zdo.GetLong(ZDOVars.s_creator, 0L) : 0); } private void TrackPortal(ZDO zdo, long creatorId) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if (zdo != null && creatorId != 0) { ZDOID uid = zdo.m_uid; string @string = zdo.GetString("PortalLimit_BuilderName", string.Empty); if (string.IsNullOrWhiteSpace(@string)) { @string = zdo.GetString(ZDOVars.s_creatorName, string.Empty); } if (creatorByPortalId.TryGetValue(uid, out var value) && value != creatorId) { RemovePortalIdFromCreator(value, uid); } creatorByPortalId[uid] = creatorId; if (!string.IsNullOrWhiteSpace(@string)) { creatorNamesByCreator[creatorId] = @string; } if (!portalIdsByCreator.TryGetValue(creatorId, out var value2)) { value2 = new HashSet(); portalIdsByCreator[creatorId] = value2; } value2.Add(uid); } } private void UntrackPortal(Piece piece) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null) { UntrackPortal(val2.m_uid); } } private void UntrackPortal(ZDOID id) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (creatorByPortalId.TryGetValue(id, out var value)) { creatorByPortalId.Remove(id); RemovePortalIdFromCreator(value, id); } } private void RemovePortalIdFromCreator(long creatorId, ZDOID id) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (portalIdsByCreator.TryGetValue(creatorId, out var value)) { value.Remove(id); if (value.Count == 0) { portalIdsByCreator.Remove(creatorId); creatorNamesByCreator.Remove(creatorId); } } } private void HookZdoDestroyed() { if (!zdoDestroyedHooked && ZDOMan.instance != null && !(OnZdoDestroyedField == null)) { Action a = OnZdoDestroyedField.GetValue(ZDOMan.instance) as Action; a = (Action)Delegate.Combine(a, new Action(OnZdoDestroyed)); OnZdoDestroyedField.SetValue(ZDOMan.instance, a); zdoDestroyedHooked = true; } } private void UnhookZdoDestroyed() { if (zdoDestroyedHooked && ZDOMan.instance != null && !(OnZdoDestroyedField == null)) { Action source = OnZdoDestroyedField.GetValue(ZDOMan.instance) as Action; source = (Action)Delegate.Remove(source, new Action(OnZdoDestroyed)); OnZdoDestroyedField.SetValue(ZDOMan.instance, source); zdoDestroyedHooked = false; } } private void OnZdoDestroyed(ZDO zdo) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (zdo != null) { UntrackPortal(zdo.m_uid); SavePortalCounts(); } } private HashSet GetPortalPrefabNameSet() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); string[] array = portalPrefabNames.Value.Split(new char[1] { ',' }); foreach (string text in array) { string text2 = text.Trim(); if (!string.IsNullOrWhiteSpace(text2)) { hashSet.Add(text2); } } return hashSet; } private bool IsCreatorAdmin(long creatorId) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return false; } ZNetPeer peer = instance.GetPeer(creatorId); if (peer != null) { return IsPeerAdmin(peer); } return instance.IsServer() && !IsDedicatedServerProcess(); } private bool IsSenderAdmin(long sender) { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return false; } ZNetPeer peer = instance.GetPeer(sender); if (peer != null) { return IsPeerAdmin(peer); } return instance.IsServer() && !IsDedicatedServerProcess(); } private bool IsPeerAdmin(ZNetPeer peer) { try { ISocket val = (ISocket)((PeerSocketField != null) ? /*isinst with value type is only supported in some contexts*/: null); string text = ((val != null) ? val.GetHostName() : string.Empty); return !string.IsNullOrWhiteSpace(text) && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsAdmin(text); } catch (Exception ex) { Log.LogWarning((object)("Could not check admin status: " + ex.Message)); return false; } } private string BuildConfigPayload(bool isAdmin, string message) { return "isAdmin=" + isAdmin + "\nmax=" + maxPortalsPerPlayer.Value + "\nadminBypass=" + adminBypass.Value + "\nenforceOnServer=" + enforceOnServer.Value + "\nportalPrefabNames=" + Escape(portalPrefabNames.Value) + "\ndiscoveryEnabled=" + discoveryPortalsEnabled.Value + "\ndiscoveryAdminBypass=" + discoveryAdminBypass.Value + "\nadminHoldEToEdit=" + adminHoldEToEdit.Value + "\nshowDiscoveryHoverText=" + showDiscoveryHoverText.Value + "\ndiscoveryLockedHoverText=" + Escape(discoveryLockedHoverText.Value) + "\ndiscoveryRemoteHoverText=" + Escape(discoveryRemoteHoverText.Value) + "\ndiscoveryUnlockedHoverText=" + Escape(discoveryUnlockedHoverText.Value) + "\ndiscoveryLockedMessage=" + Escape(discoveryLockedMessage.Value) + "\ndiscoveryUnlockMessage=" + Escape(discoveryUnlockMessage.Value) + "\ndiscoveryShowWorldMarker=" + discoveryShowWorldMarker.Value + "\ndiscoveryTintPortalModel=" + discoveryTintPortalModel.Value + "\ndiscoverySuppressLockedGlow=" + discoverySuppressLockedGlow.Value + "\ndiscoveryLockedColor=" + Escape(discoveryLockedColor.Value) + "\ndiscoveryRemoteColor=" + Escape(discoveryRemoteColor.Value) + "\ndiscoveryUnlockedColor=" + Escape(discoveryUnlockedColor.Value) + "\nmessage=" + Escape(message); } private string BuildUnlockPayload(long playerId, string message) { HashSet orCreateUnlockSet = GetOrCreateUnlockSet(playerId); return "links=" + Escape(string.Join(";", new List(orCreateUnlockSet).ToArray())) + "\nmessage=" + Escape(message); } private HashSet GetOrCreateUnlockSet(long playerId) { if (!unlockedLinksByPlayer.TryGetValue(playerId, out var value)) { value = new HashSet(StringComparer.OrdinalIgnoreCase); unlockedLinksByPlayer[playerId] = value; } return value; } private void LoadUnlocks() { unlockedLinksByPlayer.Clear(); if (!File.Exists(unlockFilePath)) { return; } string[] array = File.ReadAllLines(unlockFilePath); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text) || text.TrimStart(new char[0]).StartsWith("#", StringComparison.Ordinal)) { continue; } string[] array2 = text.Split(new char[1] { '|' }); if (array2.Length >= 2 && long.TryParse(array2[0].Trim(), out var result)) { string text2 = array2[1].Trim(); if (!string.IsNullOrWhiteSpace(text2)) { GetOrCreateUnlockSet(result).Add(text2); } } } } private void SaveUnlocks() { List list = new List(); list.Add("# player id | discovery link id"); foreach (KeyValuePair> item in unlockedLinksByPlayer) { foreach (string item2 in item.Value) { list.Add(item.Key + " | " + item2); } } File.WriteAllLines(unlockFilePath, list.ToArray()); } private string BuildAdminReport(string command) { string text = (command ?? string.Empty).Trim().ToLowerInvariant(); EnsurePortalIndexBuilt(); if (text.StartsWith("portalinfo|", StringComparison.Ordinal)) { return BuildPortalInformationReport(command); } if (text == "export") { SavePortalCounts(); EnsurePlacementLogHeader(); EnsureUnlockHistoryHeader(); return "Portal Limit export refreshed.\nportal_counts.tsv: " + portalCountsFilePath + "\nportal_placements.tsv: " + portalPlacementLogFilePath + "\ndiscovery_unlock_history.tsv: " + unlockHistoryFilePath; } if (text == "portals") { return BuildPortalListReport(); } return BuildPlayerCountReport(); } private string BuildPlayerCountReport() { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (HashSet value in portalIdsByCreator.Values) { num += value.Count; } stringBuilder.AppendLine("Portal Limit server counts"); stringBuilder.AppendLine("Total counted portals: " + num); stringBuilder.AppendLine("Player limit: " + ((maxPortalsPerPlayer.Value > 0) ? maxPortalsPerPlayer.Value.ToString() : "unlimited")); List list = new List(portalIdsByCreator.Keys); list.Sort((long left, long right) => CountPortals(right).CompareTo(CountPortals(left))); foreach (long item in list) { int num2 = CountPortals(item); string creatorName = GetCreatorName(item); string text = ((maxPortalsPerPlayer.Value > 0 && num2 > maxPortalsPerPlayer.Value) ? " OVER LIMIT" : string.Empty); stringBuilder.AppendLine(num2 + " / " + ((maxPortalsPerPlayer.Value > 0) ? maxPortalsPerPlayer.Value.ToString() : "unlimited") + " - " + creatorName + " (" + item + ")" + text); } return stringBuilder.ToString().TrimEnd(new char[0]); } private string BuildPortalListReport() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Portal Limit portal list"); stringBuilder.AppendLine("playerName | playerId | prefab | tag | role | x,y,z | zdoId"); List list = new List(creatorByPortalId.Keys); list.Sort((ZDOID left, ZDOID right) => ((object)(ZDOID)(ref left)).ToString().CompareTo(((object)(ZDOID)(ref right)).ToString())); foreach (ZDOID item in list) { ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(item) : null); if (val != null) { long @long = val.GetLong(ZDOVars.s_creator, 0L); Vector3 position = val.GetPosition(); stringBuilder.AppendLine(GetCreatorName(@long) + " | " + @long + " | " + GetPrefabName(val) + " | " + GetPortalTag(val) + " | " + GetDiscoveryRoleName(val.GetInt("PortalLimit_DiscoveryRole", 0)) + " | " + FormatPosition(position) + " | " + val.m_uid); } } return stringBuilder.ToString().TrimEnd(new char[0]); } private string BuildPortalInformationReport(string command) { //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) string[] array = (command ?? string.Empty).Split(new char[1] { '|' }); string fallbackTag = ((array.Length > 1) ? Unescape(array[1]).Trim() : string.Empty); string portalKey = ((array.Length > 2) ? Unescape(array[2]).Trim() : string.Empty); ZDO zdo = FindPortalZdoByKey(portalKey); string text = ResolvePortalInformationTag(zdo, fallbackTag); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(BuildServerPortalIdentity(zdo, text)); stringBuilder.AppendLine(); stringBuilder.AppendLine("Players who unlocked this portal tag:"); List list = BuildUnlockLinesForTag(text); if (list.Count == 0) { stringBuilder.AppendLine("No recorded unlocks yet."); } else { foreach (string item in list) { stringBuilder.AppendLine(item); } } StringBuilder stringBuilder2 = new StringBuilder(); int num = 0; foreach (HashSet value in portalIdsByCreator.Values) { num += value.Count; } int num2 = 0; int num3 = 0; int num4 = 0; foreach (ZDOID key in creatorByPortalId.Keys) { ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(key) : null); switch ((val != null) ? val.GetInt("PortalLimit_DiscoveryRole", 0) : 0) { case 1: num2++; break; case 2: num3++; break; default: num4++; break; } } stringBuilder2.AppendLine("Count of all portals built in world: " + num); stringBuilder2.AppendLine("Normal portals: " + num4); stringBuilder2.AppendLine("Locked entrances: " + num2); stringBuilder2.AppendLine("Unlocked entrances: " + num3); stringBuilder2.AppendLine("Known portal builders: " + portalIdsByCreator.Count); stringBuilder2.AppendLine("Player limit: " + ((maxPortalsPerPlayer.Value > 0) ? maxPortalsPerPlayer.Value.ToString() : "unlimited")); stringBuilder2.AppendLine("Tip: blank-tag portals are kept unconnected so players do not get fake teleport links."); stringBuilder2.AppendLine("Tip: duplicate locked or unlocked discovery endpoints with the same tag are rejected."); return "PortalInfoResponse\n" + stringBuilder.ToString().TrimEnd(new char[0]) + "\n---WORLD---\n" + stringBuilder2.ToString().TrimEnd(new char[0]); } private ZDO FindPortalZdoByKey(string portalKey) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(portalKey) || ZDOMan.instance == null) { return null; } EnsurePortalIndexBuilt(); foreach (ZDOID key in creatorByPortalId.Keys) { ZDOID current = key; if (!string.Equals(((object)(ZDOID)(ref current)).ToString(), portalKey, StringComparison.Ordinal)) { continue; } return ZDOMan.instance.GetZDO(current); } HashSet strictPortalPrefabNameSet = GetStrictPortalPrefabNameSet(); foreach (string item in strictPortalPrefabNameSet) { int num = 0; List list = new List(); while (!ZDOMan.instance.GetAllZDOsWithPrefabIterative(item, list, ref num)) { } foreach (ZDO item2 in list) { if (item2 != null && string.Equals(((object)(ZDOID)(ref item2.m_uid)).ToString(), portalKey, StringComparison.Ordinal)) { TrackPortal(item2); return item2; } } } return null; } private static string ResolvePortalInformationTag(ZDO zdo, string fallbackTag) { string text = ((zdo != null) ? zdo.GetString("PortalLimit_DiscoveryLink", string.Empty) : string.Empty); if (string.IsNullOrWhiteSpace(text)) { text = GetPortalTag(zdo); } if (string.IsNullOrWhiteSpace(text)) { text = fallbackTag; } return string.IsNullOrWhiteSpace(text) ? string.Empty : text.Trim(); } private string BuildServerPortalIdentity(ZDO zdo, string fallbackTag) { //IL_01f9: Unknown result type (might be due to invalid IL or missing references) if (zdo == null) { return "Portal Tag: " + (string.IsNullOrWhiteSpace(fallbackTag) ? "(none)" : fallbackTag) + "\nPlayer Who Built the Portal: unknown\nDate the portal was built: unknown\nLast time this portal was used: unknown\nPortal role: unknown\nPortal id: unknown"; } long num = zdo.GetLong(ZDOVars.s_creator, 0L); long @long = zdo.GetLong("PortalLimit_BuilderId", 0L); if (@long != 0) { num = @long; } string text = zdo.GetString("PortalLimit_BuilderName", string.Empty); if (string.IsNullOrWhiteSpace(text)) { text = zdo.GetString(ZDOVars.s_creatorName, string.Empty); } if (string.IsNullOrWhiteSpace(text)) { text = GetCreatorName(num); } string text2 = zdo.GetString("PortalLimit_DiscoveryLink", string.Empty); if (string.IsNullOrWhiteSpace(text2)) { text2 = GetPortalTag(zdo); } if (string.IsNullOrWhiteSpace(text2)) { text2 = (string.IsNullOrWhiteSpace(fallbackTag) ? "(none)" : fallbackTag); } string text3 = zdo.GetString("PortalLimit_BuiltUtc", string.Empty); if (string.IsNullOrWhiteSpace(text3)) { text3 = FindPortalBuiltUtcFromPlacementLog(((object)(ZDOID)(ref zdo.m_uid)).ToString()); } string text4 = FormatDateOnly(text3); if (string.IsNullOrWhiteSpace(text4)) { text4 = "unknown"; } string text5 = BuildLastUsedText(zdo); string text6 = ((num != 0) ? num.ToString() : "unknown"); return "Portal Tag: " + text2 + "\nPlayer Who Built the Portal: " + text + ", Steam ID: " + text6 + "\nDate the portal was built: " + text4 + "\nLast time this portal was used: " + text5 + "\nPortal role: " + GetDiscoveryRoleName(zdo.GetInt("PortalLimit_DiscoveryRole", 0)) + "\nPortal id: " + zdo.m_uid; } private string BuildLastUsedText(ZDO zdo) { if (zdo == null) { return "unknown"; } string text = FormatDateTimeText(zdo.GetString("PortalLimit_LastUsedUtc", string.Empty)); if (string.IsNullOrWhiteSpace(text)) { return "unknown"; } string text2 = zdo.GetString("PortalLimit_LastUsedName", string.Empty); long @long = zdo.GetLong("PortalLimit_LastUsedId", 0L); if (string.IsNullOrWhiteSpace(text2) && @long != 0) { text2 = GetCreatorName(@long); } string text3 = (string.IsNullOrWhiteSpace(text2) ? "unknown" : text2); string text4 = ((@long != 0) ? @long.ToString() : "unknown"); return text + ", by " + text3 + ", Steam ID: " + text4; } private List BuildUnlockLinesForTag(string tag) { List list = new List(); HashSet hashSet = new HashSet(); if (!string.IsNullOrWhiteSpace(tag)) { foreach (KeyValuePair> item in unlockedLinksByPlayer) { if (item.Value.Contains(tag)) { hashSet.Add(item.Key); } } } HashSet hashSet2 = new HashSet(); if (File.Exists(unlockHistoryFilePath)) { string[] array = File.ReadAllLines(unlockHistoryFilePath); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text) || text.StartsWith("timeUtc\t", StringComparison.OrdinalIgnoreCase)) { continue; } string[] array2 = text.Split(new char[1] { '\t' }); if (array2.Length >= 4 && string.Equals(array2[3], tag, StringComparison.OrdinalIgnoreCase)) { if (!long.TryParse(array2[2], out var result)) { result = 0L; } hashSet2.Add(result); string text2 = (string.IsNullOrWhiteSpace(array2[1]) ? GetCreatorName(result) : array2[1]); string text3 = FormatDateOnly(array2[0]); string text4 = FormatTimeOnly(array2[0]); list.Add(text2 + ", " + text3 + ", " + text4 + ", " + result); } } } foreach (long item2 in hashSet) { if (!hashSet2.Contains(item2)) { list.Add(GetCreatorName(item2) + ", unknown date, unknown time, " + item2); } } list.Sort(StringComparer.OrdinalIgnoreCase); return list; } private void EnsurePlacementLogHeader() { if (!string.IsNullOrEmpty(portalPlacementLogFilePath) && !File.Exists(portalPlacementLogFilePath)) { File.WriteAllText(portalPlacementLogFilePath, "timeUtc\tresult\tplayerName\tplayerId\tprefab\tportalTag\trole\tx\ty\tz\tzdoId" + Environment.NewLine); } } private void EnsureUnlockHistoryHeader() { if (!string.IsNullOrEmpty(unlockHistoryFilePath) && !File.Exists(unlockHistoryFilePath)) { File.WriteAllText(unlockHistoryFilePath, "timeUtc\tplayerName\tplayerId\tportalTag\tunlockPortalId\tx\ty\tz" + Environment.NewLine); } } private string FindPortalBuiltUtcFromPlacementLog(string portalId) { if (string.IsNullOrWhiteSpace(portalId) || !File.Exists(portalPlacementLogFilePath)) { return string.Empty; } string[] array = File.ReadAllLines(portalPlacementLogFilePath); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("timeUtc\t", StringComparison.OrdinalIgnoreCase)) { string[] array2 = text.Split(new char[1] { '\t' }); if (array2.Length >= 11 && string.Equals(array2[10], portalId, StringComparison.Ordinal) && string.Equals(array2[1], "PLACED", StringComparison.OrdinalIgnoreCase)) { return array2[0]; } } } return string.Empty; } private static string FormatDateOnly(string isoDate) { if (DateTime.TryParse(isoDate, null, DateTimeStyles.AdjustToUniversal, out var result)) { return result.ToUniversalTime().ToString("MM/dd/yyyy"); } return "unknown date"; } private static string FormatTimeOnly(string isoDate) { if (DateTime.TryParse(isoDate, null, DateTimeStyles.AdjustToUniversal, out var result)) { return result.ToUniversalTime().ToString("HH:mm:ss"); } return "unknown time"; } private static string FormatDateTimeText(string isoDate) { if (DateTime.TryParse(isoDate, null, DateTimeStyles.AdjustToUniversal, out var result)) { return result.ToUniversalTime().ToString("MM/dd/yyyy, HH:mm:ss"); } return string.Empty; } private void AppendUnlockHistoryLog(long playerId, string playerName, string link, string portalId, string x, string y, string z) { EnsureUnlockHistoryHeader(); string value = (string.IsNullOrWhiteSpace(playerName) ? GetCreatorName(playerId) : playerName); string text = DateTime.UtcNow.ToString("o") + "\t" + EscapeReportField(value) + "\t" + playerId + "\t" + EscapeReportField(link) + "\t" + EscapeReportField(portalId) + "\t" + EscapeReportField(x) + "\t" + EscapeReportField(y) + "\t" + EscapeReportField(z); File.AppendAllText(unlockHistoryFilePath, text + Environment.NewLine); } private void AppendPortalPlacementLog(Piece piece, long creatorId, string result) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) ZNetView val = (((Object)(object)piece != (Object)null) ? ((Component)piece).GetComponent() : null); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null) { EnsurePlacementLogHeader(); Vector3 position = val2.GetPosition(); string text = DateTime.UtcNow.ToString("o") + "\t" + EscapeReportField(result) + "\t" + EscapeReportField(GetCreatorName(creatorId)) + "\t" + creatorId + "\t" + EscapeReportField(GetPrefabName(val2)) + "\t" + EscapeReportField(GetPortalTag(val2)) + "\t" + EscapeReportField(GetDiscoveryRoleName(val2.GetInt("PortalLimit_DiscoveryRole", 0))) + "\t" + position.x.ToString("F1") + "\t" + position.y.ToString("F1") + "\t" + position.z.ToString("F1") + "\t" + val2.m_uid; File.AppendAllText(portalPlacementLogFilePath, text + Environment.NewLine); } } private string GetCreatorName(long creatorId) { string value; return (creatorNamesByCreator.TryGetValue(creatorId, out value) && !string.IsNullOrWhiteSpace(value)) ? value : "unknown"; } private static string GetPrefabName(ZDO zdo) { if (zdo == null || (Object)(object)ZNetScene.instance == (Object)null) { return "unknown"; } GameObject prefab = ZNetScene.instance.GetPrefab(zdo.GetPrefab()); return ((Object)(object)prefab != (Object)null) ? ((Object)prefab).name : zdo.GetPrefab().ToString(); } private static string GetPortalTag(ZDO zdo) { return (zdo != null) ? zdo.GetString("tag", string.Empty) : string.Empty; } private static string FormatPosition(Vector3 position) { return position.x.ToString("F1") + "," + position.y.ToString("F1") + "," + position.z.ToString("F1"); } private static string GetDiscoveryRoleName(int role) { return role switch { 1 => "LOCKED ENTRANCE", 2 => "UNLOCKED ENTRANCE", _ => "NORMAL PORTAL", }; } private void SavePortalCounts() { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(portalCountsFilePath)) { return; } List list = new List(); list.Add("# PortalLimit global portal count export"); list.Add("# Generated from saved Valheim portal ZDO creator data. The world save remains authoritative."); list.Add("creatorId\tcreatorName\tportalCount\tportalZdoIds"); List list2 = new List(portalIdsByCreator.Keys); list2.Sort(); foreach (long item in list2) { if (!portalIdsByCreator.TryGetValue(item, out var value)) { continue; } List list3 = new List(); foreach (ZDOID item2 in value) { ZDOID current2 = item2; list3.Add(((object)(ZDOID)(ref current2)).ToString()); } list3.Sort(StringComparer.Ordinal); creatorNamesByCreator.TryGetValue(item, out var value2); list.Add(item + "\t" + EscapeReportField(value2) + "\t" + value.Count + "\t" + EscapeReportField(string.Join(",", list3.ToArray()))); } File.WriteAllLines(portalCountsFilePath, list.ToArray()); } private static string EscapeReportField(string value) { return (value ?? string.Empty).Replace("\t", " ").Replace("\r", " ").Replace("\n", " "); } private static Dictionary ParsePayload(string payload) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(payload)) { return dictionary; } string[] array = payload.Split(new char[1] { '\n' }); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { dictionary[text.Substring(0, num)] = Unescape(text.Substring(num + 1)); } } return dictionary; } private static string Escape(string value) { return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\n", "\\n").Replace("=", "\\e"); } private static string NormalizeMarkerTextValue(string value) { string text = (value ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { return "UNLOCK ON OTHER SIDE"; } if (string.Equals(text, "Locked", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "LOCKED", StringComparison.OrdinalIgnoreCase)) { return "LOCKED"; } if (string.Equals(text, "X", StringComparison.OrdinalIgnoreCase)) { return "X"; } if (string.Equals(text, "UnlockOnOtherSide", StringComparison.OrdinalIgnoreCase) || string.Equals(text, "UNLOCK ON OTHER SIDE", StringComparison.OrdinalIgnoreCase)) { return "UNLOCK ON OTHER SIDE"; } return text; } private static bool TryNormalizeHexColor(string text, out string normalized) { normalized = (text ?? string.Empty).Trim(); if (!normalized.StartsWith("#", StringComparison.Ordinal)) { normalized = "#" + normalized; } Color val = default(Color); if (normalized.Length == 7 && ColorUtility.TryParseHtmlString(normalized, ref val)) { return true; } normalized = string.Empty; return false; } private static string GetPortalTag(TeleportWorld portal) { if ((Object)(object)portal == (Object)null) { return string.Empty; } try { if (GetPortalTextMethod != null) { object obj = GetPortalTextMethod.Invoke(portal, null); return (obj != null) ? obj.ToString() : string.Empty; } } catch (Exception ex) { Log.LogWarning((object)("Could not read portal tag: " + ex.Message)); } ZNetView component = ((Component)portal).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); return (val != null) ? val.GetString("tag", string.Empty) : string.Empty; } private static string Unescape(string value) { return (value ?? string.Empty).Replace("\\e", "=").Replace("\\n", "\n").Replace("\\\\", "\\"); } private static bool IsDedicatedOrHostServer() { return (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); } }