Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Muninn v0.7.0
plugins/GuildTelemetry.dll
Decompiled 17 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GuildTelemetry.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("GuildTelemetry")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.7.0.0")] [assembly: AssemblyInformationalVersion("0.7.0+8dc8a4ee5924c88e4590acca90b8fc35bc40353e")] [assembly: AssemblyProduct("GuildTelemetry")] [assembly: AssemblyTitle("GuildTelemetry")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.7.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 GuildTelemetry { internal sealed class Announcer { private readonly ManualLogSource log; private readonly Telemetry telemetry; private readonly AnnouncementScheduler scheduler = new AnnouncementScheduler(); public Announcer(ManualLogSource log, Telemetry telemetry) { this.log = log; this.telemetry = telemetry; } public void Update(TelemetryPipeline pipeline, float now) { IngestResponse ingestResponse = pipeline.TakeResponse(); if (ingestResponse != null) { scheduler.Apply(ingestResponse.Announcements, now); } foreach (Banner item in scheduler.Due(now)) { Show(item); } } private void Show(Banner banner) { if (ZRoutedRpc.instance == null) { log.LogWarning((object)("GuildTelemetry: no routed RPC yet, announcement " + banner.AnnouncementId + " dropped")); return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, banner.Text }); } catch (Exception ex) { log.LogWarning((object)("GuildTelemetry: ShowMessage failed for announcement " + banner.AnnouncementId + ": " + ex.Message)); return; } telemetry.AnnouncementShown(banner); log.LogInfo((object)("GuildTelemetry: announced \"" + banner.Text + "\" (announcement " + banner.AnnouncementId + ")")); } } internal static class ComfortCatalogueSender { private static int started; public static void Start(ManualLogSource log, string ingestUrl, string secret) { if (Interlocked.Exchange(ref started, 1) == 1) { return; } try { string problem; ComfortCatalogue comfortCatalogue = ComfortReader.Read(out problem); if (comfortCatalogue == null) { log.LogWarning((object)("GuildTelemetry: comfort catalogue not sent: " + problem)); return; } if (comfortCatalogue.Pieces.Count == 0) { log.LogWarning((object)"GuildTelemetry: comfort catalogue not sent: the game data yielded no comfort pieces"); return; } byte[] body = Encoding.UTF8.GetBytes(comfortCatalogue.ToJson()); string url = ingestUrl.TrimEnd(new char[1] { '/' }) + "/catalogue"; string summary = comfortCatalogue.Pieces.Count + " comfort pieces for game " + comfortCatalogue.GameVersion; Thread thread = new Thread((ThreadStart)delegate { Send(log, url, secret, body, summary); }); thread.Name = "GuildTelemetry catalogue"; thread.IsBackground = true; thread.Priority = ThreadPriority.BelowNormal; thread.Start(); } catch (Exception ex) { log.LogWarning((object)("GuildTelemetry: comfort catalogue could not be read: " + ex)); } } private static void Send(ManualLogSource log, string url, string secret, byte[] body, string summary) { try { if (new OneShotUpload(new HttpTransport(), Thread.Sleep, () => DateTime.UtcNow).Send(url, secret, body, "application/json", delegate(string message) { log.LogWarning((object)("GuildTelemetry: comfort catalogue " + message)); }) == UploadOutcome.Stored) { log.LogInfo((object)("GuildTelemetry: sent " + summary)); } } catch (Exception ex) { log.LogWarning((object)("GuildTelemetry: comfort catalogue upload failed: " + ex)); } } } internal static class ComfortReader { public static ComfortCatalogue? Read(out string? problem) { //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; ObjectDB instance2 = ObjectDB.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null) { problem = "the prefab scene or the object database is not ready"; return null; } StatusEffect statusEffect = instance2.GetStatusEffect(SEMan.s_statusEffectRested); SE_Rested val = (SE_Rested)(object)((statusEffect is SE_Rested) ? statusEffect : null); if ((Object)(object)val == (Object)null) { problem = "the object database has no Rested effect"; return null; } HashSet<string> hashSet = BuildablePrefabs(instance); List<ComfortSeason> list = new List<ComfortSeason>(); Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); foreach (SeasonalItemGroup item in SeasonalGroups(instance)) { ComfortSeason season = WindowOf(item); if (season != null && !list.Exists((ComfortSeason known) => known.Name == season.Name)) { list.Add(season); } foreach (GameObject piece in item.Pieces) { if ((Object)(object)piece != (Object)null && !dictionary.ContainsKey(((Object)piece).name)) { dictionary[((Object)piece).name] = ((Object)item).name; } } } List<ComfortPiece> list2 = new List<ComfortPiece>(); foreach (GameObject prefab in instance.m_prefabs) { if ((Object)(object)prefab == (Object)null || !hashSet.Contains(((Object)prefab).name) || (Object)(object)prefab.GetComponent<ItemDrop>() != (Object)null) { continue; } Piece component = prefab.GetComponent<Piece>(); if (!((Object)(object)component == (Object)null) && component.m_comfort > 0) { string value = null; if (component.m_enabled || dictionary.TryGetValue(((Object)prefab).name, out value)) { list2.Add(new ComfortPiece(((Object)prefab).name, component.m_name ?? string.Empty, NameOf(prefab, component), component.m_comfort, ((object)Unsafe.As<ComfortGroup, ComfortGroup>(ref component.m_comfortGroup)/*cast due to .constrained prefix*/).ToString(), ConditionOf(prefab, component), value)); } } } problem = null; return new ComfortCatalogue(((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString(), "0.7.0", DateTime.UtcNow, Radius(), val.m_baseTTL, val.m_TTLPerComfortLevel, Enum.GetNames(typeof(ComfortGroup)), list, list2); } private static HashSet<string> BuildablePrefabs(ZNetScene scene) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (GameObject prefab in scene.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { continue; } PieceTable buildPieces = component.m_itemData.m_shared.m_buildPieces; if ((Object)(object)buildPieces == (Object)null || buildPieces.m_pieces == null) { continue; } foreach (GameObject piece in buildPieces.m_pieces) { if ((Object)(object)piece != (Object)null) { hashSet.Add(((Object)piece).name); } } } return hashSet; } private static List<SeasonalItemGroup> SeasonalGroups(ZNetScene scene) { List<SeasonalItemGroup> list = new List<SeasonalItemGroup>(); GameObject prefab = scene.GetPrefab("Player"); if ((Object)(object)prefab == (Object)null) { return list; } Player component = prefab.GetComponent<Player>(); FieldInfo field = typeof(Player).GetField("m_seasonalItemGroups", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((Object)(object)component == (Object)null || field == null || !(field.GetValue(component) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { SeasonalItemGroup val = (SeasonalItemGroup)((item is SeasonalItemGroup) ? item : null); if ((Object)(object)val != (Object)null && val.Pieces != null && !string.IsNullOrEmpty(((Object)val).name)) { list.Add(val); } } return list; } private static ComfortSeason? WindowOf(SeasonalItemGroup group) { try { DateTime startDate = group.GetStartDate(); DateTime endDate = group.GetEndDate(); return new ComfortSeason(((Object)group).name, startDate.Day, startDate.Month, endDate.Day, endDate.Month); } catch (ArgumentOutOfRangeException) { return null; } } private static string NameOf(GameObject prefab, Piece piece) { string text = piece.m_name ?? string.Empty; if (text.Length == 0) { return ((Object)prefab).name; } string text2 = Localization.instance.Localize(text); if (!string.IsNullOrEmpty(text2) && !(text2 == "[" + text.TrimStart(new char[1] { '$' }) + "]")) { return text2; } return ((Object)prefab).name; } private static string? ConditionOf(GameObject prefab, Piece piece) { if ((Object)(object)piece.m_comfortObject == (Object)null) { return null; } Fireplace component = prefab.GetComponent<Fireplace>(); if (!((Object)(object)component != (Object)null) || !((Object)(object)component.m_enabledObjectHigh != (Object)null) || !((Object)(object)component.m_enabledObjectLow != (Object)null) || !piece.m_comfortObject.transform.IsChildOf(component.m_enabledObjectHigh.transform)) { return "lit"; } return "lit_dry"; } private static double? Radius() { FieldInfo field = typeof(SE_Rested).GetField("c_ComfortRadius", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (((field == null) ? null : (field.IsLiteral ? field.GetRawConstantValue() : field.GetValue(null))) is float num && num > 0f) { return num; } return null; } } internal static class Creators { public unsafe static IList<string?> History() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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_0036: 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_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) List<string> list = new List<string>(); World world = ZNet.World; if (world == null || world.m_playerHistory == null) { return list; } foreach (CrossNetworkUserInfo item2 in world.m_playerHistory) { PlatformUserID id = item2.m_id; object item; if (!((PlatformUserID)(ref id)).IsValid) { item = null; } else { id = item2.m_id; item = ((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString(); } list.Add((string)item); } return list; } public static string? AccountOf(ZDO zdo, IList<string?> history) { return CreatorAccounts.AccountAt(history, zdo.GetInt(ZDOVars.s_creatorIndex, -1)); } public static CreatorAccounts Scan(out int scanned, out long elapsedMs) { Stopwatch stopwatch = Stopwatch.StartNew(); CreatorAccounts creatorAccounts = new CreatorAccounts(); scanned = 0; IList<string> list = History(); FieldInfo field = typeof(ZDOMan).GetField("m_objectsByID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (list.Count > 0 && ZDOMan.instance != null && field != null && field.GetValue(ZDOMan.instance) is Dictionary<ZDOID, ZDO> dictionary) { foreach (ZDO value in dictionary.Values) { scanned++; long num = value.GetLong(ZDOVars.s_creator, 0L); if (num != 0L && !creatorAccounts.Knows(num)) { creatorAccounts.Add(num, AccountOf(value, list)); } } } elapsedMs = stopwatch.ElapsedMilliseconds; return creatorAccounts; } } internal static class GameVersions { public static uint NetworkVersion() { FieldInfo field = typeof(Version).GetField("c_networkVersion", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); object obj = ((field == null) ? null : (field.IsLiteral ? field.GetRawConstantValue() : field.GetValue(null))); if (obj is uint) { return (uint)obj; } if (obj is int) { return (uint)(int)obj; } return 40u; } } [BepInPlugin("com.guildsite.telemetry", "GuildTelemetry", "0.7.0")] [BepInProcess("valheim_server.exe")] public sealed class GuildTelemetryPlugin : BaseUnityPlugin { private static ManualLogSource? log; private Harmony? harmony; private PluginConfig? config; private TelemetryPipeline? pipeline; private Telemetry? telemetry; private Announcer? announcer; private readonly List<string> missingHooks = new List<string>(); private bool disabled; private float nextBiomeSample; private float nextPositionSample; private float nextHeartbeat; internal static ManualLogSource Log => log ?? throw new InvalidOperationException("GuildTelemetry has not been loaded."); private void Awake() { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)string.Format("{0} {1} loaded on Valheim {2} (network version {3})", "GuildTelemetry", "0.7.0", Version.GetVersionString(false), GameVersions.NetworkVersion())); try { config = new PluginConfig(((BaseUnityPlugin)this).Config); string text = config.Validate(); if (text != null) { disabled = true; Log.LogError((object)("GuildTelemetry: configuration invalid (" + text + "); set Url and Secret in " + ((BaseUnityPlugin)this).Config.ConfigFilePath + " and restart. The plugin stays inactive.")); } else { telemetry = new Telemetry(Log, config, missingHooks); announcer = new Announcer(Log, telemetry); harmony = new Harmony("com.guildsite.telemetry"); missingHooks.AddRange(Hooks.Install(harmony, telemetry)); Log.LogInfo((object)("GuildTelemetry: hooks installed" + ((missingHooks.Count > 0) ? (" (missing: " + string.Join(", ", missingHooks.ToArray()) + ")") : string.Empty))); } } catch (Exception ex) { disabled = true; Log.LogError((object)("GuildTelemetry: initialisation failed, the plugin stays inactive: " + ex)); } } private void Update() { if (disabled || telemetry == null || config == null) { return; } try { if (!telemetry.Started) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)ZoneSystem.instance == (Object)null || WorldGenerator.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { return; } StartPipeline(); telemetry.ServerStarted(); if (config.MapEnabled.Value) { MapRenderer.Start(Log, Path.Combine(Paths.ConfigPath, "GuildTelemetry"), ZNet.instance.GetWorldUID(), config.Url.Value.Trim(), config.Secret.Value.Trim()); } if (config.CatalogEnabled.Value) { ComfortCatalogueSender.Start(Log, config.Url.Value.Trim(), config.Secret.Value.Trim()); } float realtimeSinceStartup = Time.realtimeSinceStartup; nextBiomeSample = realtimeSinceStartup + (float)config.BiomeSampleSeconds.Value; nextPositionSample = realtimeSinceStartup + (float)config.PositionSampleSeconds.Value; nextHeartbeat = realtimeSinceStartup + (float)config.HeartbeatSeconds.Value; } Hooks.EnsureDestroyHook(); float realtimeSinceStartup2 = Time.realtimeSinceStartup; if (realtimeSinceStartup2 >= nextBiomeSample) { nextBiomeSample = realtimeSinceStartup2 + (float)Math.Max(1, config.BiomeSampleSeconds.Value); telemetry.SampleBiomes(); } if (realtimeSinceStartup2 >= nextPositionSample) { nextPositionSample = realtimeSinceStartup2 + (float)Math.Max(1, config.PositionSampleSeconds.Value); telemetry.SamplePositions(); } if (realtimeSinceStartup2 >= nextHeartbeat) { nextHeartbeat = realtimeSinceStartup2 + (float)Math.Max(5, config.HeartbeatSeconds.Value); telemetry.Heartbeat(); } if (pipeline != null && announcer != null) { announcer.Update(pipeline, realtimeSinceStartup2); } } catch (Exception ex) { Log.LogWarning((object)("GuildTelemetry: update failed: " + ex.Message)); } } private void StartPipeline() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (telemetry != null && config != null) { string text = ZNet.m_ServerName ?? string.Empty; string text2 = ZNet.instance.GetWorldName() ?? string.Empty; long worldUID = ZNet.instance.GetWorldUID(); BatchMetadata metadata = new BatchMetadata("GuildTelemetry", "0.7.0", ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString(), (int)GameVersions.NetworkVersion(), text, text2, worldUID); EventJournal journal = new EventJournal(Path.Combine(Paths.ConfigPath, "GuildTelemetry"), (long)Math.Max(1, config.JournalMaxMB.Value) * 1024L * 1024); PipelineOptions pipelineOptions = new PipelineOptions { Url = config.Url.Value.Trim(), Secret = config.Secret.Value.Trim(), FlushIntervalMs = Math.Max(500, config.FlushSeconds.Value * 1000) }; pipeline = new TelemetryPipeline(pipelineOptions, metadata, journal, new HttpTransport(), delegate(string message) { Log.LogInfo((object)("GuildTelemetry: " + message)); }); pipeline.Start(); telemetry.AttachPipeline(pipeline); Log.LogInfo((object)("GuildTelemetry: sending to " + pipelineOptions.Url + " for server \"" + text + "\" world \"" + text2 + "\"")); } } private void OnDestroy() { Hooks.Uninstall(); Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } pipeline?.Dispose(); } } internal static class Hooks { private static Telemetry? telemetry; private static int onDeathHash; private static int damageHash; private static int showMessageHash; private static int spawnBossHash; private static int chatMessageHash; private static int sayHash; private static int registerKillHash; private static ZDOID characterBeforeRpc = ZDOID.None; private static bool destroyHooked; public static List<string> Install(Harmony harmony, Telemetry target) { telemetry = target; onDeathHash = StringExtensionMethods.GetStableHashCode("OnDeath"); damageHash = StringExtensionMethods.GetStableHashCode("RPC_Damage"); showMessageHash = StringExtensionMethods.GetStableHashCode("ShowMessage"); spawnBossHash = StringExtensionMethods.GetStableHashCode("RPC_SpawnBoss"); chatMessageHash = StringExtensionMethods.GetStableHashCode("ChatMessage"); sayHash = StringExtensionMethods.GetStableHashCode("Say"); registerKillHash = StringExtensionMethods.GetStableHashCode("RPC_RegisterKill"); List<string> list = new List<string>(); Type typeFromHandle = typeof(Hooks); Patch(harmony, list, typeof(ZNet), "RPC_PeerInfo", new Type[2] { typeof(ZRpc), typeof(ZPackage) }, null, typeFromHandle.GetMethod("AfterPeerInfo", BindingFlags.Static | BindingFlags.NonPublic)); Patch(harmony, list, typeof(ZNet), "RPC_CharacterID", new Type[2] { typeof(ZRpc), typeof(ZDOID) }, typeFromHandle.GetMethod("BeforeCharacterId", BindingFlags.Static | BindingFlags.NonPublic), typeFromHandle.GetMethod("AfterCharacterId", BindingFlags.Static | BindingFlags.NonPublic)); Patch(harmony, list, typeof(ZNet), "RPC_Disconnect", new Type[1] { typeof(ZRpc) }, typeFromHandle.GetMethod("BeforeRpcDisconnect", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZNet), "InternalKick", new Type[1] { typeof(ZNetPeer) }, typeFromHandle.GetMethod("BeforeKick", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, typeFromHandle.GetMethod("BeforeDisconnect", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZNet), "Shutdown", new Type[1] { typeof(bool) }, typeFromHandle.GetMethod("BeforeShutdown", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZoneSystem), "RPC_SetGlobalKey", new Type[2] { typeof(long), typeof(string) }, typeFromHandle.GetMethod("BeforeSetGlobalKey", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(RandEventSystem), "SetRandomEvent", new Type[2] { typeof(RandomEvent), typeof(Vector3) }, typeFromHandle.GetMethod("BeforeSetRandomEvent", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZRoutedRpc), "RPC_RoutedRPC", new Type[2] { typeof(ZRpc), typeof(ZPackage) }, typeFromHandle.GetMethod("BeforeRoutedRpc", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[4] { typeof(long), typeof(ZDOID), typeof(string), typeof(object[]) }, typeFromHandle.GetMethod("BeforeInvokeRoutedRpc", BindingFlags.Static | BindingFlags.NonPublic), null); Patch(harmony, list, typeof(ZDOMan), "CreateNewZDO", new Type[3] { typeof(ZDOID), typeof(Vector3), typeof(int) }, null, typeFromHandle.GetMethod("AfterCreateNewZdo", BindingFlags.Static | BindingFlags.NonPublic)); Patch(harmony, list, typeof(ZDO), "Deserialize", new Type[1] { typeof(ZPackage) }, null, typeFromHandle.GetMethod("AfterZdoDeserialize", BindingFlags.Static | BindingFlags.NonPublic)); try { ZNet.WorldSaveStarted = (Action)Delegate.Combine(ZNet.WorldSaveStarted, new Action(OnWorldSaveStarted)); ZNet.WorldSaveFinished = (Action)Delegate.Combine(ZNet.WorldSaveFinished, new Action(OnWorldSaveFinished)); } catch (Exception ex) { list.Add("ZNet.WorldSaveStarted"); GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: save events unavailable: " + ex.Message)); } return list; } public static void Uninstall() { ZNet.WorldSaveStarted = (Action)Delegate.Remove(ZNet.WorldSaveStarted, new Action(OnWorldSaveStarted)); ZNet.WorldSaveFinished = (Action)Delegate.Remove(ZNet.WorldSaveFinished, new Action(OnWorldSaveFinished)); } public static void EnsureDestroyHook() { if (!destroyHooked && ZDOMan.instance != null) { ZDOMan instance = ZDOMan.instance; instance.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(instance.m_onZDODestroyed, new Action<ZDO>(OnZdoDestroyed)); destroyHooked = true; } } private static void Patch(Harmony harmony, List<string> missing, Type type, string name, Type[] parameters, MethodInfo? prefix, MethodInfo? postfix) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) string text = type.Name + "." + name; try { MethodInfo methodInfo = AccessTools.Method(type, name, parameters, (Type[])null); if (methodInfo == null) { missing.Add(text); GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: hook target missing: " + text)); } else { harmony.Patch((MethodBase)methodInfo, (!(prefix != null)) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (!(postfix != null)) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { missing.Add(text); GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: hook failed for " + text + ": " + ex.Message)); } } private static void Guard(string hook, Action action) { try { if (telemetry != null) { action(); } } catch (Exception ex) { GuildTelemetryPlugin.Log.LogWarning((object)("GuildTelemetry: " + hook + " failed: " + ex)); } } private static void AfterPeerInfo(ZNet __instance, ZRpc rpc) { Guard("RPC_PeerInfo", delegate { if (__instance.IsServer()) { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null && peer.IsReady() && telemetry.Peers.Get(peer.m_uid) == null) { telemetry.PeerJoined(peer); } } }); } private static void BeforeCharacterId(ZNet __instance, ZRpc rpc) { Guard("RPC_CharacterID prefix", delegate { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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) characterBeforeRpc = __instance.GetPeer(rpc)?.m_characterID ?? ZDOID.None; }); } private static void AfterCharacterId(ZNet __instance, ZRpc rpc, ZDOID characterID) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) Guard("RPC_CharacterID", delegate { //IL_003e: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null) { PeerState peerState = telemetry.Peers.Get(peer.m_uid); if (peerState != null) { if (!((ZDOID)(ref characterID)).IsNone()) { if (peerState.CharacterId != characterID) { telemetry.PeerSpawned(peerState, characterID); } } else if (!((ZDOID)(ref characterBeforeRpc)).IsNone()) { telemetry.PeerDied(peerState); peerState.CharacterId = ZDOID.None; } } } }); } private static void BeforeRpcDisconnect(ZNet __instance, ZRpc rpc) { Guard("RPC_Disconnect", delegate { ZNetPeer peer = __instance.GetPeer(rpc); PeerState peerState = ((peer != null) ? telemetry.Peers.Get(peer.m_uid) : null); if (peerState != null) { peerState.Graceful = true; } }); } private static void BeforeKick(ZNetPeer peer) { Guard("InternalKick", delegate { PeerState peerState = ((peer != null) ? telemetry.Peers.Get(peer.m_uid) : null); if (peerState != null) { peerState.Kicked = true; } }); } private static void BeforeDisconnect(ZNetPeer peer) { Guard("Disconnect", delegate { PeerState peerState = ((peer != null) ? telemetry.Peers.Get(peer.m_uid) : null); if (peerState != null) { string reason = (peerState.Kicked ? "kicked" : (peerState.Graceful ? "disconnect" : "timeout")); telemetry.PeerLeft(peerState, reason); } }); } private static void BeforeShutdown() { Guard("Shutdown", delegate { telemetry.Stopping(); }); } private static void OnWorldSaveStarted() { Guard("WorldSaveStarted", delegate { telemetry.SaveStarted(); }); } private static void OnWorldSaveFinished() { Guard("WorldSaveFinished", delegate { telemetry.SaveFinished(); }); } private static void BeforeSetGlobalKey(long sender, string name) { Guard("RPC_SetGlobalKey", delegate { telemetry.GlobalKey(sender, name); }); } private static void BeforeSetRandomEvent(RandEventSystem __instance, RandomEvent ev, Vector3 pos) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) Guard("SetRandomEvent", delegate { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { telemetry.RaidChanged(__instance.m_randomEvent, ev, pos); } }); } private static void BeforeRoutedRpc(ZPackage pkg) { Guard("RPC_RoutedRPC", delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int pos = pkg.GetPos(); RoutedRPCData val = new RoutedRPCData(); val.Deserialize(pkg); pkg.SetPos(pos); HandleRouted(val); }); } private static void BeforeInvokeRoutedRpc(long targetPeerID, ZDOID targetZDO, string methodName, object[] parameters) { Guard("InvokeRoutedRPC", delegate { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (!(methodName != "ChatMessage") && parameters.Length >= 4) { Vector3 value = (Vector3)((parameters[0] is Vector3) ? ((Vector3)parameters[0]) : Vector3.zero); int num = ((parameters[1] is int) ? ((int)parameters[1]) : 0); object obj = parameters[2]; UserInfo val = (UserInfo)((obj is UserInfo) ? obj : null); string text = (parameters[3] as string) ?? string.Empty; string text2 = num switch { 2 => "shout", 3 => "ping", _ => "say", }; telemetry.Chat(0L, text2, (text2 == "ping") ? null : text, value, (val != null && ((PlatformUserID)(ref val.UserId)).IsValid) ? ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref val.UserId)/*cast due to .constrained prefix*/).ToString() : null); } }); } private static void HandleRouted(RoutedRPCData data) { //IL_0028: 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_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Expected O, but got Unknown //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) int methodHash = data.m_methodHash; ZPackage parameters = data.m_parameters; parameters.SetPos(0); if (methodHash == onDeathHash) { PeerState peerState = telemetry.Peers.ByCharacter(data.m_targetZDO); if (peerState != null) { telemetry.PeerDied(peerState); } } else if (methodHash == damageHash) { PeerState peerState2 = telemetry.Peers.ByCharacter(data.m_targetZDO); if (peerState2 != null) { HitData val = new HitData(); val.Deserialize(ref parameters); telemetry.RecordHit(peerState2, val); } } else if (methodHash == showMessageHash) { parameters.ReadInt(); string message = parameters.ReadString(); telemetry.BossAlert(data.m_senderPeerID, message); } else if (methodHash == spawnBossHash) { Vector3 point = parameters.ReadVector3(); telemetry.BossSummonRpc(data.m_senderPeerID, data.m_targetZDO, point); } else if (methodHash == chatMessageHash) { Vector3 value = parameters.ReadVector3(); int num = parameters.ReadInt(); UserInfo val2 = new UserInfo(); val2.Deserialize(ref parameters); string text = parameters.ReadString(); string text2 = num switch { 2 => "shout", 3 => "ping", _ => "say", }; telemetry.Chat(data.m_senderPeerID, text2, (text2 == "ping") ? null : text, value, ((PlatformUserID)(ref val2.UserId)).IsValid ? ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref val2.UserId)/*cast due to .constrained prefix*/).ToString() : null); } else if (methodHash == sayHash) { int num2 = parameters.ReadInt(); UserInfo val3 = new UserInfo(); val3.Deserialize(ref parameters); string text3 = parameters.ReadString(); string kind = ((num2 == 2) ? "shout" : "say"); telemetry.Chat(data.m_senderPeerID, kind, text3, null, ((PlatformUserID)(ref val3.UserId)).IsValid ? ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref val3.UserId)/*cast due to .constrained prefix*/).ToString() : null); } else if (methodHash == registerKillHash) { string enemyName = parameters.ReadString(); telemetry.KillCredited(data.m_targetPeerID, enemyName); } } private static void AfterCreateNewZdo(ZDOID uid, int prefabHashIn) { //IL_0007: 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) Guard("CreateNewZDO", delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) telemetry.ZdoCreated(uid, prefabHashIn); }); } private static void AfterZdoDeserialize(ZDO __instance) { Guard("ZDO.Deserialize", delegate { telemetry.ZdoDeserialized(__instance); }); } private static void OnZdoDestroyed(ZDO zdo) { Guard("ZDO destroyed", delegate { telemetry.ZdoDestroyed(zdo); }); } } internal static class MapRenderer { public const int Size = 2048; public const float RadiusMetres = 10000f; public const float WaterLevel = 30f; private const int UploadAttempts = 10; private const int UploadRetryMs = 30000; private static readonly byte[][] Palette = new byte[10][] { new byte[3] { 39, 75, 138 }, new byte[3] { 109, 184, 107 }, new byte[3] { 47, 107, 42 }, new byte[3] { 110, 90, 85 }, new byte[3] { 232, 232, 234 }, new byte[3] { 217, 200, 106 }, new byte[3] { 76, 74, 90 }, new byte[3] { 176, 52, 44 }, new byte[3] { 223, 233, 240 }, new byte[3] }; private static int started; public static string MarkerPath(string directory, long worldUid) { return Path.Combine(directory, "map-" + worldUid.ToString(CultureInfo.InvariantCulture) + ".uploaded"); } public static void Start(ManualLogSource log, string directory, long worldUid, string ingestUrl, string secret) { if (Interlocked.Exchange(ref started, 1) == 1) { return; } if (File.Exists(MarkerPath(directory, worldUid))) { log.LogInfo((object)("GuildTelemetry: map for world " + worldUid + " was uploaded earlier, skipping the render")); return; } WorldGenerator generator = WorldGenerator.instance; if (generator == null) { log.LogWarning((object)"GuildTelemetry: no world generator, the map is not rendered"); return; } Thread thread = new Thread((ThreadStart)delegate { Run(log, generator, directory, worldUid, ingestUrl, secret); }); thread.Name = "GuildTelemetry map"; thread.IsBackground = true; thread.Priority = ThreadPriority.BelowNormal; thread.Start(); } private static byte PaletteIndex(Biome biome) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if ((int)biome <= 16) { switch (biome - 1) { default: if ((int)biome != 8) { if ((int)biome != 16) { break; } return 5; } return 2; case 0: return 1; case 1: return 3; case 3: return 4; case 2: break; } } else if ((int)biome <= 64) { if ((int)biome == 32) { return 7; } if ((int)biome == 64) { return 8; } } else { if ((int)biome == 256) { return 0; } if ((int)biome == 512) { return 6; } } return 9; } public static byte[] Render(WorldGenerator generator, Action<int>? progress) { //IL_004b: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) byte[] array = new byte[4194304]; float num = 9.765625f; Color val = default(Color); for (int i = 0; i < 2048; i++) { float num2 = 10000f - ((float)i + 0.5f) * num; for (int j = 0; j < 2048; j++) { float num3 = -10000f + ((float)j + 0.5f) * num; Biome biome = generator.GetBiome(num3, num2, 0.02f, false); byte b = (byte)(((int)biome != 256) ? ((!(generator.GetBiomeHeight(biome, num3, num2, ref val, false, true) < 30f)) ? PaletteIndex(biome) : 0) : 0); array[i * 2048 + j] = b; } if (progress != null && (i + 1) % 512 == 0) { progress(i + 1); } } return array; } private static void Run(ManualLogSource log, WorldGenerator generator, string directory, long worldUid, string ingestUrl, string secret) { try { Stopwatch stopwatch = Stopwatch.StartNew(); byte[] pixels = Render(generator, delegate(int rows) { log.LogInfo((object)("GuildTelemetry: map render " + rows + "/" + 2048 + " rows")); }); byte[] array = PngEncoder.EncodeIndexed(2048, 2048, pixels, Palette); log.LogInfo((object)("GuildTelemetry: map rendered in " + stopwatch.ElapsedMilliseconds / 1000 + " s, " + array.Length + " bytes")); Upload(log, array, directory, worldUid, ingestUrl, secret); } catch (Exception ex) { log.LogWarning((object)("GuildTelemetry: map rendering failed: " + ex)); } } private static void Upload(ManualLogSource log, byte[] png, string directory, long worldUid, string ingestUrl, string secret) { string text = ingestUrl.TrimEnd(new char[1] { '/' }); text += "/map"; RequestSigner requestSigner = new RequestSigner(secret); HttpTransport httpTransport = new HttpTransport(); string body = RequestSigner.Sha256Hex(png); for (int i = 1; i <= 10; i++) { long unixTimestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; Dictionary<string, string> headers = new Dictionary<string, string> { { "X-Telemetry-Timestamp", unixTimestamp.ToString(CultureInfo.InvariantCulture) }, { "X-Telemetry-Signature", requestSigner.Sign(unixTimestamp, body) }, { "X-World-Uid", worldUid.ToString(CultureInfo.InvariantCulture) }, { "X-Map-Size", 2048.ToString(CultureInfo.InvariantCulture) }, { "X-Map-Radius", 10000f.ToString(CultureInfo.InvariantCulture) } }; SendResult sendResult = httpTransport.Post(text, png, "image/png", headers, 120000); if (sendResult.StatusCode >= 200 && sendResult.StatusCode < 300) { Directory.CreateDirectory(directory); File.WriteAllText(MarkerPath(directory, worldUid), JsonWriter.Timestamp(DateTime.UtcNow) + "\n"); log.LogInfo((object)("GuildTelemetry: map uploaded for world " + worldUid)); break; } if (sendResult.StatusCode == 401 || sendResult.StatusCode == 413 || sendResult.StatusCode == 422) { log.LogWarning((object)("GuildTelemetry: map upload refused (HTTP " + sendResult.StatusCode + "): " + sendResult.Error)); break; } log.LogWarning((object)("GuildTelemetry: map upload attempt " + i + " failed (" + (sendResult.Error ?? ("HTTP " + sendResult.StatusCode)) + ")")); Thread.Sleep(30000); } } } internal sealed class ObservedHit { public HitType HitType { get; } public ZDOID Attacker { get; } public DateTime AtUtc { get; } public ObservedHit(HitType hitType, ZDOID attacker, DateTime atUtc) { //IL_0007: 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_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) HitType = hitType; Attacker = attacker; AtUtc = atUtc; } } internal sealed class PeerState { public ZNetPeer Peer { get; } public long Uid { get; } public string Name { get; } public string? PlayfabId { get; } public string HostName { get; } public string PlatformUserId { get; } public string DisplayId { get; } public string Platform { get; } public DateTime JoinedAtUtc { get; } public ZDOID CharacterId { get; set; } public long PlayerId => Peer.m_playerID; public long ProfileId { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) ZDOID characterId = CharacterId; ZDO val = ((((ZDOID)(ref characterId)).IsNone() || ZDOMan.instance == null) ? null : ZDOMan.instance.GetZDO(CharacterId)); if (val == null) { return 0L; } return val.GetLong(ZDOVars.s_playerID, 0L); } } public bool Spawned { get; set; } public string LastBiome { get; set; } = "None"; public bool HasLastPosition { get; set; } public Vector3 LastPosition { get; set; } public double DistanceSinceHeartbeat { get; set; } public ObservedHit? LastHit { get; set; } public DateTime LastDeathUtc { get; set; } = DateTime.MinValue; public bool Graceful { get; set; } public bool Kicked { get; set; } public Vector3 Position => Peer.m_refPos; public double SessionSeconds => (DateTime.UtcNow - JoinedAtUtc).TotalSeconds; public PeerState(ZNetPeer peer, string platformUserId, string displayId, string platform) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) Peer = peer; Uid = peer.m_uid; Name = peer.m_playerName ?? string.Empty; PlayfabId = (string.IsNullOrEmpty(peer.m_playfabId) ? null : peer.m_playfabId); HostName = ((peer.m_socket != null) ? peer.m_socket.GetHostName() : string.Empty); PlatformUserId = platformUserId; DisplayId = displayId; Platform = platform; JoinedAtUtc = DateTime.UtcNow; CharacterId = ZDOID.None; } } internal sealed class PeerTracker { private readonly Dictionary<long, PeerState> peers = new Dictionary<long, PeerState>(); public IEnumerable<PeerState> All => peers.Values; public int Count => peers.Count; public PeerState? Get(long uid) { if (!peers.TryGetValue(uid, out PeerState value)) { return null; } return value; } public unsafe PeerState Register(ZNetPeer peer) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) PeerState peerState = Get(peer.m_uid); if (peerState != null) { return peerState; } PlatformUserID val = ResolvePlatformId((peer.m_socket != null) ? peer.m_socket.GetHostName() : string.Empty, peer.m_uid); string text = (((PlatformUserID)(ref val)).IsValid ? ((object)(*(PlatformUserID*)(&val))/*cast due to .constrained prefix*/).ToString() : ("Unknown_" + peer.m_uid)); string displayId = (((PlatformUserID)(ref val)).IsValid ? ((object)PlatformUserID.FilterPlatformUserID(val)/*cast due to .constrained prefix*/).ToString() : text); object obj; if (!((PlatformUserID)(ref val)).IsValid) { obj = "Steam"; } else { Platform platform = val.m_platform; obj = ((object)(*(Platform*)(&platform))/*cast due to .constrained prefix*/).ToString(); } string platform2 = (string)obj; PeerState peerState2 = new PeerState(peer, text, displayId, platform2); peers[peer.m_uid] = peerState2; return peerState2; } public void Remove(long uid) { peers.Remove(uid); } public PeerState? ByCharacter(ZDOID characterId) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref characterId)).IsNone()) { return null; } foreach (PeerState value in peers.Values) { if (value.CharacterId == characterId) { return value; } } return null; } public PeerState? ByProfileId(long profileId) { if (profileId == 0L) { return null; } foreach (PeerState value in peers.Values) { if (value.ProfileId == profileId) { return value; } } return null; } public List<string> NearbyIds(Vector3 point, float radius) { //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) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) List<string> list = new List<string>(); foreach (PeerState value in peers.Values) { ZDOID characterId = value.CharacterId; if (!((ZDOID)(ref characterId)).IsNone() && DistanceXZ(value.Position, point) <= radius) { list.Add(value.PlatformUserId); } } return list; } public static float DistanceXZ(Vector3 a, Vector3 b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //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) float num = a.x - b.x; float num2 = a.z - b.z; return Mathf.Sqrt(num * num + num2 * num2); } private static PlatformUserID ResolvePlatformId(string hostName, long uid) { //IL_000e: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(hostName)) { return PlatformUserID.None; } if ((int)ZNet.m_onlineBackend == 0) { return new PlatformUserID(new Platform("Steam"), hostName); } PlatformUserID result = default(PlatformUserID); if (PlatformUserID.TryParse(hostName, ref result) && ((PlatformUserID)(ref result)).IsValid) { return result; } if (uid != 0L) { return new PlatformUserID(new Platform("Steam"), hostName); } return PlatformUserID.None; } } internal sealed class PluginConfig { public ConfigEntry<string> Url { get; } public ConfigEntry<string> Secret { get; } public ConfigEntry<int> HeartbeatSeconds { get; } public ConfigEntry<int> FlushSeconds { get; } public ConfigEntry<int> BiomeSampleSeconds { get; } public ConfigEntry<int> PositionSampleSeconds { get; } public ConfigEntry<int> JournalMaxMB { get; } public ConfigEntry<bool> AllowInsecureHttp { get; } public ConfigEntry<bool> LogEvents { get; } public ConfigEntry<bool> MapEnabled { get; } public ConfigEntry<bool> CatalogEnabled { get; } public PluginConfig(ConfigFile file) { Url = file.Bind<string>("General", "Url", "", "Ingest endpoint of the guild site, for example https://guild.example.org/api/ingest"); Secret = file.Bind<string>("General", "Secret", "", "Shared telemetry secret (TELEMETRY_SECRET on the site)"); HeartbeatSeconds = file.Bind<int>("General", "HeartbeatSeconds", 60, "Seconds between server.heartbeat events"); FlushSeconds = file.Bind<int>("General", "FlushSeconds", 2, "Seconds between batch flushes"); BiomeSampleSeconds = file.Bind<int>("General", "BiomeSampleSeconds", 5, "Seconds between biome and distance samples"); PositionSampleSeconds = file.Bind<int>("General", "PositionSampleSeconds", 20, "Seconds between player.position events"); JournalMaxMB = file.Bind<int>("General", "JournalMaxMB", 50, "Hard cap of the on-disk journal in megabytes"); AllowInsecureHttp = file.Bind<bool>("General", "AllowInsecureHttp", false, "Allow an http:// Url (local rig only)"); LogEvents = file.Bind<bool>("General", "LogEvents", false, "Log every emitted event to the BepInEx log"); MapEnabled = file.Bind<bool>("General", "MapEnabled", true, "Render the world biome map once per world and upload it to the site"); CatalogEnabled = file.Bind<bool>("General", "CatalogEnabled", true, "Send the comfort pieces of the running game to the site at every server start"); } public string? Validate() { string text = Url.Value.Trim(); if (text.Length == 0) { return "Url is empty"; } if (text.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !AllowInsecureHttp.Value) { return "Url uses http:// but AllowInsecureHttp is false"; } if (!text.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !text.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { return "Url must start with https://"; } if (Secret.Value.Trim().Length == 0) { return "Secret is empty"; } return null; } } internal sealed class PrefabInfo { public string Name { get; } public bool IsPiece { get; set; } public bool IsCharacter { get; set; } public bool IsPlayer { get; set; } public bool IsBoss { get; set; } public bool IsRagdoll { get; set; } public bool IsAltar { get; set; } public string CharacterName { get; set; } = string.Empty; public string DefeatKey { get; set; } = string.Empty; public string AlertMessage { get; set; } = string.Empty; public string BossPrefabName { get; set; } = string.Empty; public string CreatureForRagdoll { get; set; } = string.Empty; public PrefabInfo(string name) { Name = name; } } internal sealed class PrefabCatalog { private readonly Dictionary<int, PrefabInfo> byHash = new Dictionary<int, PrefabInfo>(); private readonly Dictionary<string, List<int>> hashesByDefeatKey = new Dictionary<string, List<int>>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, int> hashByAlertMessage = new Dictionary<string, int>(StringComparer.Ordinal); private readonly Dictionary<string, string> prefabByCharacterName = new Dictionary<string, string>(StringComparer.Ordinal); private readonly Dictionary<int, string> creatureByRagdollHash = new Dictionary<int, string>(); private bool built; public bool Ready => built; public int Count => byHash.Count; public void EnsureBuilt() { if (built || (Object)(object)ZNetScene.instance == (Object)null) { return; } foreach (GameObject prefab in ZNetScene.instance.m_prefabs) { if (!((Object)(object)prefab == (Object)null)) { Classify(prefab); } } foreach (KeyValuePair<int, string> item in creatureByRagdollHash) { PrefabInfo prefabInfo = Get(item.Key); if (prefabInfo != null) { prefabInfo.CreatureForRagdoll = item.Value; } } built = true; } public PrefabInfo? Get(int hash) { if (byHash.TryGetValue(hash, out PrefabInfo value)) { return value; } if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(hash); if ((Object)(object)prefab == (Object)null) { return null; } return Classify(prefab); } public string NameOf(int hash) { PrefabInfo prefabInfo = Get(hash); if (prefabInfo == null) { return hash.ToString(); } return prefabInfo.Name; } public List<int> HashesForDefeatKey(string key) { if (!hashesByDefeatKey.TryGetValue(key, out List<int> value)) { return new List<int>(); } return value; } public bool TryPrefabForAlert(string message, out int hash) { return hashByAlertMessage.TryGetValue(message, out hash); } public string PrefabForCharacterName(string characterName) { if (!prefabByCharacterName.TryGetValue(characterName, out string value)) { return characterName; } return value; } private PrefabInfo Classify(GameObject prefab) { int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); PrefabInfo prefabInfo = new PrefabInfo(((Object)prefab).name); prefabInfo.IsPiece = (Object)(object)prefab.GetComponent<Piece>() != (Object)null; prefabInfo.IsRagdoll = (Object)(object)prefab.GetComponent<Ragdoll>() != (Object)null; Character component = prefab.GetComponent<Character>(); if ((Object)(object)component != (Object)null) { prefabInfo.IsCharacter = true; prefabInfo.IsPlayer = component is Player; prefabInfo.IsBoss = component.m_boss; prefabInfo.CharacterName = component.m_name ?? string.Empty; prefabInfo.DefeatKey = component.m_defeatSetGlobalKey ?? string.Empty; BaseAI component2 = prefab.GetComponent<BaseAI>(); if ((Object)(object)component2 != (Object)null) { prefabInfo.AlertMessage = component2.m_alertedMessage ?? string.Empty; } if (prefabInfo.CharacterName.Length > 0 && !prefabByCharacterName.ContainsKey(prefabInfo.CharacterName)) { prefabByCharacterName[prefabInfo.CharacterName] = ((Object)prefab).name; } if (prefabInfo.DefeatKey.Length > 0) { if (!hashesByDefeatKey.TryGetValue(prefabInfo.DefeatKey, out List<int> value)) { value = new List<int>(); hashesByDefeatKey[prefabInfo.DefeatKey] = value; } value.Add(stableHashCode); } if (prefabInfo.IsBoss && prefabInfo.AlertMessage.Length > 0) { hashByAlertMessage[prefabInfo.AlertMessage] = stableHashCode; } if (component.m_deathEffects != null && component.m_deathEffects.m_effectPrefabs != null) { EffectData[] effectPrefabs = component.m_deathEffects.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (val != null && (Object)(object)val.m_prefab != (Object)null && (Object)(object)val.m_prefab.GetComponent<Ragdoll>() != (Object)null) { creatureByRagdollHash[StringExtensionMethods.GetStableHashCode(((Object)val.m_prefab).name)] = ((Object)prefab).name; } } } } OfferingBowl component3 = prefab.GetComponent<OfferingBowl>(); if ((Object)(object)component3 != (Object)null && (Object)(object)component3.m_bossPrefab != (Object)null) { prefabInfo.IsAltar = true; prefabInfo.BossPrefabName = ((Object)component3.m_bossPrefab).name; } if (creatureByRagdollHash.TryGetValue(stableHashCode, out string value2)) { prefabInfo.CreatureForRagdoll = value2; } byHash[stableHashCode] = prefabInfo; return prefabInfo; } } internal sealed class KillCredit { public string EnemyName { get; } public string PlatformUserId { get; } public DateTime AtUtc { get; } public KillCredit(string enemyName, string platformUserId, DateTime atUtc) { EnemyName = enemyName; PlatformUserId = platformUserId; AtUtc = atUtc; } } internal sealed class RecentDeath { public string Prefab { get; } public Vector3 Position { get; } public DateTime AtUtc { get; } public RecentDeath(string prefab, Vector3 position, DateTime atUtc) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) Prefab = prefab; Position = position; AtUtc = atUtc; } } internal sealed class Telemetry { public const float BossNearbyRadius = 200f; public const float CreatureNearbyRadius = 100f; public const float TeleportThresholdMetres = 500f; private readonly ManualLogSource log; private readonly PluginConfig config; private TelemetryPipeline? pipeline; private readonly List<TelemetryEvent> buffered = new List<TelemetryEvent>(); private readonly List<string> missingHooks; private long seq; private readonly float startedRealtime = Time.realtimeSinceStartup; private Stopwatch? saveWatch; private DateTime? lastSaveFinishedUtc; private readonly Dictionary<ZDOID, DateTime> engagedBosses = new Dictionary<ZDOID, DateTime>(); private readonly Dictionary<string, DateTime> summonedByPrefab = new Dictionary<string, DateTime>(StringComparer.Ordinal); private readonly List<RecentDeath> recentDeaths = new List<RecentDeath>(); private readonly List<KillCredit> recentCredits = new List<KillCredit>(); private readonly Dictionary<string, DateTime> recentChat = new Dictionary<string, DateTime>(StringComparer.Ordinal); private readonly HashSet<ZDOID> pendingCreated = new HashSet<ZDOID>(); private float lastActiveBosses; private string? currentRaid; private DateTime currentRaidStartedUtc; public string RunId { get; } = Guid.NewGuid().ToString(); public PeerTracker Peers { get; } = new PeerTracker(); public PrefabCatalog Catalog { get; } = new PrefabCatalog(); public bool Started { get; private set; } public TelemetryPipeline? Pipeline => pipeline; public int QueueDepth { get { if (pipeline == null) { return buffered.Count; } return pipeline.QueueDepth; } } public long DroppedEvents { get { if (pipeline == null) { return 0L; } return pipeline.DroppedEvents; } } public double UptimeSeconds => Time.realtimeSinceStartup - startedRealtime; public Telemetry(ManualLogSource log, PluginConfig config, List<string> missingHooks) { this.log = log; this.config = config; this.missingHooks = missingHooks; } public void AttachPipeline(TelemetryPipeline started) { pipeline = started; foreach (TelemetryEvent item in buffered) { started.Enqueue(item); } buffered.Clear(); } public int WorldDay() { if ((Object)(object)EnvMan.instance != (Object)null) { return EnvMan.instance.GetDay(); } if (!((Object)(object)ZNet.instance != (Object)null)) { return 0; } return (int)(ZNet.instance.GetTimeSeconds() / 1800.0); } public static string BiomeAt(Vector3 position) { //IL_0012: 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_0018: Unknown result type (might be due to invalid IL or missing references) if (WorldGenerator.instance == null) { return "None"; } return ((object)WorldGenerator.instance.GetBiome(position)/*cast due to .constrained prefix*/).ToString(); } public void Emit(string type, string dataJson) { TelemetryEvent telemetryEvent = new TelemetryEvent(Guid.NewGuid().ToString(), Interlocked.Increment(ref seq), RunId, DateTime.UtcNow, type, WorldDay(), dataJson); if (pipeline == null) { if (buffered.Count < 1000) { buffered.Add(telemetryEvent); } } else if (!pipeline.Enqueue(telemetryEvent)) { log.LogWarning((object)("GuildTelemetry: queue full, dropped " + type)); } if (config.LogEvents.Value) { log.LogInfo((object)("GuildTelemetry: " + type + " " + dataJson)); } } private static JsonWriter Position(JsonWriter writer, Vector3 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) return writer.Property("x", Math.Round(position.x, 1)).Property("z", Math.Round(position.z, 1)); } private CreatorAccounts ScanCreators() { try { int scanned; long elapsedMs; CreatorAccounts creatorAccounts = Creators.Scan(out scanned, out elapsedMs); log.LogInfo((object)("GuildTelemetry: found " + creatorAccounts.Count + " piece creators among " + scanned + " objects in " + elapsedMs + " ms")); return creatorAccounts; } catch (Exception ex) { log.LogWarning((object)("GuildTelemetry: the piece creator scan failed: " + ex.Message)); return new CreatorAccounts(); } } private string? BuilderAccount(ZDO zdo, long creator) { if (creator == 0L) { return null; } string text = Creators.AccountOf(zdo, Creators.History()); if (text != null) { return text; } return Peers.ByProfileId(creator)?.PlatformUserId; } public void ServerStarted() { //IL_003f: 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) if (!Started && !((Object)(object)ZNet.instance == (Object)null)) { Started = true; Catalog.EnsureBuilt(); CreatorAccounts creatorAccounts = ScanCreators(); JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("game_version", ((object)Version.CurrentVersion/*cast due to .constrained prefix*/).ToString()).Property("network_version", GameVersions.NetworkVersion()) .Property("plugin_version", "0.7.0") .Property("bepinex_version", typeof(Paths).Assembly.GetName().Version.ToString()) .Property("unity_version", Application.unityVersion) .Property("world_name", ZNet.instance.GetWorldName() ?? string.Empty) .Property("world_uid", ZNet.instance.GetWorldUID()) .Property("net_time", Math.Round(ZNet.instance.GetTimeSeconds(), 1)) .Property("world_day", WorldDay()) .StringArray("global_keys", ((Object)(object)ZoneSystem.instance != (Object)null) ? ZoneSystem.instance.GetGlobalKeys() : new List<string>()) .StringArray("missing_hooks", missingHooks); creatorAccounts.Write(jsonWriter, "creators").EndObject(); Emit("server.started", jsonWriter.ToString()); log.LogInfo((object)("GuildTelemetry: server.started sent for world " + ZNet.instance.GetWorldName() + " with " + Catalog.Count + " classified prefabs")); } } public void Heartbeat() { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) if (!Started || (Object)(object)ZNet.instance == (Object)null) { return; } JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("uptime_s", Math.Round(UptimeSeconds, 1)).Property("net_time", Math.Round(ZNet.instance.GetTimeSeconds(), 1)) .Property("world_day", WorldDay()) .Name("last_save_age_s"); if (lastSaveFinishedUtc.HasValue) { jsonWriter.Value(Math.Round((DateTime.UtcNow - lastSaveFinishedUtc.Value).TotalSeconds, 1)); } else { jsonWriter.Null(); } jsonWriter.Property("queue_depth", QueueDepth).Property("dropped_events", DroppedEvents).Name("players") .BeginArray(); foreach (PeerState item in Peers.All) { Vector3 position = item.Position; jsonWriter.BeginObject().Property("platform_user_id", item.PlatformUserId).Property("name", item.Name) .Property("character_id", item.PlayerId) .Property("biome", item.Spawned ? BiomeAt(position) : item.LastBiome); Position(jsonWriter, position).Property("distance_since_last_m", Math.Round(item.DistanceSinceHeartbeat, 1)).EndObject(); item.DistanceSinceHeartbeat = 0.0; } jsonWriter.EndArray().EndObject(); Emit("server.heartbeat", jsonWriter.ToString()); } public void SampleBiomes() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) foreach (PeerState item in Peers.All) { if (!item.Spawned) { continue; } Vector3 position = item.Position; if (item.HasLastPosition) { float num = PeerTracker.DistanceXZ(position, item.LastPosition); if (num < 500f) { item.DistanceSinceHeartbeat += num; } } item.LastPosition = position; item.HasLastPosition = true; string text = BiomeAt(position); if (text != item.LastBiome) { string lastBiome = item.LastBiome; item.LastBiome = text; JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", item.PlatformUserId).Property("from", lastBiome) .Property("to", text); Position(jsonWriter, position).EndObject(); Emit("player.biome_changed", jsonWriter.ToString()); } } } public void SamplePositions() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) foreach (PeerState item in Peers.All) { if (item.Spawned) { Vector3 position = item.Position; JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", item.PlatformUserId); Position(jsonWriter, position).Property("biome", BiomeAt(position)).EndObject(); Emit("player.position", jsonWriter.ToString()); } } } public void PeerJoined(ZNetPeer peer) { PeerState peerState = Peers.Register(peer); JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", peerState.PlatformUserId).Property("display_id", peerState.DisplayId) .Property("platform", peerState.Platform) .Property("name", peerState.Name) .Property("peer_uid", peerState.Uid) .Property("playfab_id", peerState.PlayfabId) .EndObject(); Emit("player.joined", jsonWriter.ToString()); log.LogInfo((object)("GuildTelemetry: player joined " + peerState.Name + " (" + peerState.HostName + ") uid " + peerState.Uid)); } public void PeerLeft(PeerState state, string reason) { JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", state.PlatformUserId).Property("name", state.Name) .Property("session_s", Math.Round(state.SessionSeconds, 1)) .Property("reason", reason) .EndObject(); Emit("player.left", jsonWriter.ToString()); Peers.Remove(state.Uid); log.LogInfo((object)("GuildTelemetry: player left " + state.Name + " (" + state.HostName + ") uid " + state.Uid + " reason " + reason)); } public void PeerSpawned(PeerState state, ZDOID characterId) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) bool spawned = state.Spawned; state.CharacterId = characterId; state.Spawned = true; Vector3 position = state.Position; string value = (state.LastBiome = BiomeAt(position)); state.LastPosition = position; state.HasLastPosition = true; JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", state.PlatformUserId).Property("name", state.Name) .Property("character_id", state.PlayerId) .Property("respawn", spawned); Position(jsonWriter, position).Property("biome", value).EndObject(); Emit("player.spawned", jsonWriter.ToString()); } public void PeerDied(PeerState state) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) DateTime utcNow = DateTime.UtcNow; if ((utcNow - state.LastDeathUtc).TotalSeconds < 15.0) { return; } state.LastDeathUtc = utcNow; Vector3 position = state.Position; JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", state.PlatformUserId).Property("name", state.Name) .Property("character_id", state.PlayerId); Position(jsonWriter, position).Property("biome", BiomeAt(position)); ObservedHit lastHit = state.LastHit; if (lastHit != null && (utcNow - lastHit.AtUtc).TotalSeconds <= 10.0) { PeerState peerState = Peers.ByCharacter(lastHit.Attacker); string value = null; if (peerState == null) { ZDOID attacker = lastHit.Attacker; if (!((ZDOID)(ref attacker)).IsNone() && ZDOMan.instance != null) { ZDO zDO = ZDOMan.instance.GetZDO(lastHit.Attacker); if (zDO != null) { value = Catalog.NameOf(zDO.GetPrefab()); } } } jsonWriter.Name("observed_cause").BeginObject().Property("hit_type", ((object)lastHit.HitType/*cast due to .constrained prefix*/).ToString()) .Property("attacker_prefab", value) .Property("attacker_platform_user_id", peerState?.PlatformUserId) .Property("at", JsonWriter.Timestamp(lastHit.AtUtc)) .EndObject(); } else { jsonWriter.PropertyNull("observed_cause"); } jsonWriter.EndObject(); Emit("player.died", jsonWriter.ToString()); } public void RecordHit(PeerState target, HitData hit) { //IL_0002: 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) target.LastHit = new ObservedHit(hit.m_hitType, hit.m_attacker, DateTime.UtcNow); } public void SaveStarted() { saveWatch = Stopwatch.StartNew(); Emit("world.save_started", "{}"); } public void SaveFinished() { long value = ((saveWatch != null) ? saveWatch.ElapsedMilliseconds : 0); saveWatch = null; lastSaveFinishedUtc = DateTime.UtcNow; JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("duration_ms", value).EndObject(); Emit("world.saved", jsonWriter.ToString()); } public void Stopping() { List<PeerState> list = new List<PeerState>(Peers.All); foreach (PeerState item in list) { PeerLeft(item, "server_stop"); } JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("uptime_s", Math.Round(UptimeSeconds, 1)).Property("online_count", list.Count) .EndObject(); Emit("server.stopping", jsonWriter.ToString()); if (pipeline != null) { pipeline.FlushSync(1500); } } public void GlobalKey(long sender, string line) { //IL_0144: Unknown result type (might be due to invalid IL or missing references) string text = default(string); GlobalKeys val = default(GlobalKeys); string keyValue = ZoneSystem.GetKeyValue(line.ToLowerInvariant(), ref text, ref val); bool flag = (Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey(keyValue); PrefabInfo prefabInfo = CreatureForDefeatKey(keyValue); if (prefabInfo != null && prefabInfo.IsBoss) { BossDefeated(sender, keyValue, !flag); } else if (keyValue == "activebosses") { float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var result); if (result > lastActiveBosses) { BossEngagedByCount(sender); } lastActiveBosses = result; } else { PeerState peerState = Peers.Get(sender); JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("key", keyValue).Property("value", (text.Length > 0) ? text : null) .Property("first_time", !flag) .Property("prefab", prefabInfo?.Name) .Property("name_key", (prefabInfo != null && prefabInfo.CharacterName.Length > 0) ? prefabInfo.CharacterName : null) .Property("sender_platform_user_id", peerState?.PlatformUserId) .StringArray("nearby", (peerState != null) ? ((IEnumerable<string>)Peers.NearbyIds(peerState.Position, 100f)) : ((IEnumerable<string>)new List<string>())) .EndObject(); Emit("global_key.set", jsonWriter.ToString()); } } private PrefabInfo? CreatureForDefeatKey(string key) { PrefabInfo prefabInfo = null; foreach (int item in Catalog.HashesForDefeatKey(key)) { PrefabInfo prefabInfo2 = Catalog.Get(item); if (prefabInfo2 != null) { if (prefabInfo2.IsBoss) { return prefabInfo2; } if (prefabInfo == null) { prefabInfo = prefabInfo2; } } } return prefabInfo; } private ZDO? FindBossZdo(List<int> hashes, Vector3? near, bool requireNotEngaged) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance == null || hashes.Count == 0) { return null; } ZDO result = null; float num = float.MaxValue; foreach (ZDO value in ZDOMan.instance.m_objectsByID.Values) { if (hashes.Contains(value.GetPrefab()) && (!requireNotEngaged || !engagedBosses.ContainsKey(value.m_uid))) { float num2 = (near.HasValue ? PeerTracker.DistanceXZ(value.GetPosition(), near.Value) : 0f); if (num2 < num) { num = num2; result = value; } } } return result; } private void BossDefeated(long sender, string key, bool firstTime) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) PeerState peerState = Peers.Get(sender); Vector3? val = peerState?.Position; List<int> hashes = Catalog.HashesForDefeatKey(key); ZDO val2 = FindBossZdo(hashes, val, requireNotEngaged: false); PrefabInfo prefabInfo = ((val2 != null) ? Catalog.Get(val2.GetPrefab()) : null); List<string> list = new List<string>(); if (val2 != null) { foreach (PeerState item in Peers.All) { int s_attackers = ZDOVars.s_attackers; if (val2.GetBool(s_attackers + item.Name, false)) { list.Add(item.PlatformUserId); } } engagedBosses.Remove(val2.m_uid); } Vector3 point = (Vector3)((val2 != null) ? val2.GetPosition() : (((??)val) ?? Vector3.zero)); JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("key", key).Property("first_time", firstTime) .Property("sender_platform_user_id", peerState?.PlatformUserId) .StringArray("nearby", Peers.NearbyIds(point, 200f)) .Property("prefab", prefabInfo?.Name) .Property("name_key", (prefabInfo != null && prefabInfo.CharacterName.Length > 0) ? prefabInfo.CharacterName : null) .StringArray("participants", list) .EndObject(); Emit("boss.defeated", jsonWriter.ToString()); } private void BossEngagedByCount(long sender) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance == null) { return; } Vector3? val = Peers.Get(sender)?.Position; ZDO val2 = null; float num = float.MaxValue; foreach (ZDO value in ZDOMan.instance.m_objectsByID.Values) { PrefabInfo prefabInfo = Catalog.Get(value.GetPrefab()); if (prefabInfo != null && prefabInfo.IsBoss && !engagedBosses.ContainsKey(value.m_uid)) { float num2 = (val.HasValue ? PeerTracker.DistanceXZ(value.GetPosition(), val.Value) : 0f); if (num2 < num) { num = num2; val2 = value; } } } if (val2 != null) { BossEngaged(val2, Catalog.Get(val2.GetPrefab()), null); } } public void BossAlert(long sender, string message) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (!Catalog.TryPrefabForAlert(message, out var hash)) { return; } Vector3? near = Peers.Get(sender)?.Position; ZDO val = FindBossZdo(new List<int> { hash }, near, requireNotEngaged: false); PrefabInfo prefabInfo = Catalog.Get(hash); if (prefabInfo != null) { if (val != null) { BossEngaged(val, prefabInfo, message); } else if (near.HasValue) { EmitEngaged(prefabInfo, near.Value, message); } } } private void BossEngaged(ZDO boss, PrefabInfo info, string? message) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (!engagedBosses.ContainsKey(boss.m_uid)) { engagedBosses[boss.m_uid] = DateTime.UtcNow; EmitEngaged(info, boss.GetPosition(), message ?? info.AlertMessage); } } private void EmitEngaged(PrefabInfo info, Vector3 position, string message) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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) JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("prefab", info.Name).Property("name_key", info.CharacterName); Position(jsonWriter, position).Property("biome", BiomeAt(position)).Property("alert_message", message).StringArray("nearby", Peers.NearbyIds(position, 200f)) .EndObject(); Emit("boss.engaged", jsonWriter.ToString()); } public void BossSummonRpc(long sender, ZDOID altarId, Vector3 point) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (ZDOMan.instance == null) { return; } ZDO zDO = ZDOMan.instance.GetZDO(altarId); PrefabInfo prefabInfo = ((zDO != null) ? Catalog.Get(zDO.GetPrefab()) : null); if (prefabInfo != null && prefabInfo.IsAltar) { PrefabInfo prefabInfo2 = Catalog.Get(StringExtensionMethods.GetStableHashCode(prefabInfo.BossPrefabName)); if (prefabInfo2 != null && prefabInfo2.IsBoss) { PeerState peerState = Peers.Get(sender); EmitSummoned(prefabInfo.BossPrefabName, prefabInfo2.CharacterName, point, peerState?.PlatformUserId, "spawn_rpc"); } } } private void EmitSummoned(string prefab, string nameKey, Vector3 position, string? summoner, string method) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) summonedByPrefab[prefab] = DateTime.UtcNow; JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("prefab", prefab).Property("name_key", nameKey); Position(jsonWriter, position).Property("biome", BiomeAt(position)).Property("summoner_platform_user_id", summoner).Property("method", method) .EndObject(); Emit("boss.summoned", jsonWriter.ToString()); } public void Chat(long sender, string kind, string? text, Vector3? position, string? fallbackUserId) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) PeerState peerState = Peers.Get(sender); string text2 = ((peerState != null) ? peerState.PlatformUserId : ((!string.IsNullOrEmpty(fallbackUserId)) ? fallbackUserId : ("Server_" + (ZNet.m_ServerName ?? "server")))); Vector3 position2 = (Vector3)(((??)position) ?? peerState?.Position ?? Vector3.zero); string key = text2 + "|" + kind + "|" + (text ?? string.Empty); DateTime utcNow = DateTime.UtcNow; if (!recentChat.TryGetValue(key, out var value) || !((utcNow - value).TotalSeconds < 2.0)) { recentChat[key] = utcNow; if (recentChat.Count > 256) { recentChat.Clear(); } JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("platform_user_id", text2).Property("kind", kind) .Property("text", text); Position(jsonWriter, position2).Property("biome", BiomeAt(position2)).EndObject(); Emit("chat.message", jsonWriter.ToString()); } } public void AnnouncementShown(Banner banner) { JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("announcement_id", banner.AnnouncementId).Property("kind", banner.Kind) .Property("text", banner.Text); if (!banner.RemainingSeconds.HasValue) { jsonWriter.PropertyNull("remaining_s"); } else { jsonWriter.Property("remaining_s", banner.RemainingSeconds.Value); } jsonWriter.Property("final", banner.Final).EndObject(); Emit("announcement.shown", jsonWriter.ToString()); } public void KillCredited(long targetPeer, string enemyName) { PeerState peerState = Peers.Get(targetPeer); if (peerState != null) { DateTime now = DateTime.UtcNow; recentCredits.RemoveAll((KillCredit credit) => (now - credit.AtUtc).TotalSeconds > 15.0); recentCredits.Add(new KillCredit(enemyName, peerState.PlatformUserId, now)); } } public void ZdoCreated(ZDOID id, int prefabHash) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (prefabHash == 0) { pendingCreated.Add(id); return; } ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(id) : null); if (val != null) { Classify(val, prefabHash); } } public void ZdoDeserialized(ZDO zdo) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (pendingCreated.Remove(zdo.m_uid)) { Classify(zdo, zdo.GetPrefab()); } } public void ZdoDestroyed(ZDO zdo) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) pendingCreated.Remove(zdo.m_uid); engagedBosses.Remove(zdo.m_uid); PrefabInfo prefabInfo = Catalog.Get(zdo.GetPrefab()); if (prefabInfo == null) { return; } if (prefabInfo.IsPiece) { long num = zdo.GetLong(ZDOVars.s_creator, 0L); Vector3 position = zdo.GetPosition(); JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("prefab", prefabInfo.Name); Position(jsonWriter, position).Property("biome", BiomeAt(position)).Name("creator_character_id"); if (num != 0L) { jsonWriter.Value(num); } else { jsonWriter.Null(); } jsonWriter.Property("creator_platform_user_id", BuilderAccount(zdo, num)).EndObject(); Emit("structure.destroyed", jsonWriter.ToString()); } else if (prefabInfo.IsCharacter && !prefabInfo.IsPlayer && !(zdo.GetFloat(ZDOVars.s_health, 1f) > 0f)) { CreatureDied(prefabInfo.Name, prefabInfo, zdo.GetInt(ZDOVars.s_level, 1), zdo.GetPosition()); } } private void Classify(ZDO zdo, int prefabHash) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) PrefabInfo prefabInfo = Catalog.Get(prefabHash); if (prefabInfo == null) { return; } Vector3 position = zdo.GetPosition(); DateTime value; if (prefabInfo.IsPiece) { long num = zdo.GetLong(ZDOVars.s_creator, 0L); JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("prefab", prefabInfo.Name); Position(jsonWriter, position).Property("biome", BiomeAt(position)).Property("creator_character_id", num).Property("creator_platform_user_id", BuilderAccount(zdo, num)) .EndObject(); Emit("structure.built", jsonWriter.ToString()); } else if (prefabInfo.IsRagdoll) { string text = ((prefabInfo.CreatureForRagdoll.Length > 0) ? prefabInfo.CreatureForRagdoll : prefabInfo.Name); PrefabInfo prefabInfo2 = Catalog.Get(StringExtensionMethods.GetStableHashCode(text)); if (prefabInfo2 == null || !prefabInfo2.IsPlayer) { CreatureDied(text, prefabInfo2, zdo.GetInt(ZDOVars.s_level, 1), position); } } else if (prefabInfo.IsBoss && (!summonedByPrefab.TryGetValue(prefabInfo.Name, out value) || !((DateTime.UtcNow - value).TotalSeconds < 120.0))) { PeerState peerState = Peers.Get(zdo.GetOwner()); EmitSummoned(prefabInfo.Name, prefabInfo.CharacterName, position, peerState?.PlatformUserId, "zdo"); } } private void CreatureDied(string prefab, PrefabInfo? creatureInfo, int level, Vector3 position) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) DateTime now = DateTime.UtcNow; recentDeaths.RemoveAll((RecentDeath death) => (now - death.AtUtc).TotalSeconds > 10.0); foreach (RecentDeath recentDeath in recentDeaths) { if (recentDeath.Prefab == prefab && PeerTracker.DistanceXZ(recentDeath.Position, position) < 20f) { return; } } recentDeaths.Add(new RecentDeath(prefab, position, now)); List<string> list = new List<string>(); string text = ((creatureInfo != null) ? creatureInfo.CharacterName : string.Empty); recentCredits.RemoveAll((KillCredit credit) => (now - credit.AtUtc).TotalSeconds > 15.0); for (int num = recentCredits.Count - 1; num >= 0; num--) { KillCredit killCredit = recentCredits[num]; if (text.Length > 0 && killCredit.EnemyName == text && !list.Contains(killCredit.PlatformUserId)) { list.Add(killCredit.PlatformUserId); recentCredits.RemoveAt(num); } } JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("prefab", prefab).Property("level", Math.Max(1, level)); Position(jsonWriter, position).Property("biome", BiomeAt(position)).StringArray("credited", list).StringArray("nearby", Peers.NearbyIds(position, 100f)) .EndObject(); Emit("creature.died", jsonWriter.ToString()); } public void RaidChanged(RandomEvent? previous, RandomEvent? next, Vector3 position) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) if (previous != null && currentRaid != null) { JsonWriter jsonWriter = new JsonWriter(); jsonWriter.BeginObject().Property("name", currentRaid).Property("elapsed_s", Math.Round((DateTime.UtcNow - currentRaidStartedUtc).TotalSeconds, 1)) .Property("active_s", Math.Round(previous.m_time, 1)) .EndObject(); Emit("raid.ended", jsonWriter.ToString()); currentRaid = null; } if (next != null) { currentRaid = next.m_name; currentRaidStartedUtc = DateTime.UtcNow; JsonWriter jsonWriter2 = new JsonWriter(); jsonWriter2.BeginObject().Property("name", next.m_name); Position(jsonWriter2, position).Property("biome", BiomeAt(position)).Property("duration_s", Math.Round(next.m_duration, 1)).StringArray("nearby", Peers.NearbyIds(position, next.m_eventRange)) .EndObject(); Emit("raid.started", jsonWriter2.ToString()); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "com.guildsite.telemetry"; public const string PLUGIN_NAME = "GuildTelemetry"; public const string PLUGIN_VERSION = "0.7.0"; } } namespace GuildTelemetry.Core { public sealed class Banner { public long AnnouncementId { get; } public string Kind { get; } public string Text { get; } public int? RemainingSeconds { get; } public bool Final { get; } public Banner(long announcementId, string kind, string text, int? remainingSeconds, bool final) { AnnouncementId = announcementId; Kind = kind; Text = text; RemainingSeconds = remainingSeconds; Final = final; } } public sealed class AnnouncementScheduler { private sealed class RestartState { public string? Note { get; } public double DueAt { get; set; } public bool Announced { get; set; } public HashSet<int> FiredStages { get; } = new HashSet<int>(); public RestartState(string? note, double dueAt) { Note = note; DueAt = dueAt; } } public static readonly int[] StageMinutes = new int[4] { 15, 10, 5, 1 }; public const double StageToleranceSeconds = 45.0; public const double MaxRememberedMessages = 1000.0; private readonly HashSet<long> shownMessages = new HashSet<long>(); private readonly List<Announcement> pendingMessages = new List<Announcement>(); private readonly SortedDictionary<long, RestartState> restarts = new SortedDictionary<long, RestartState>(); public int PendingMessages => pendingMessages.Count; public int ActiveRestarts => restarts.Count; public void Apply(IList<Announcement> active, double now) { pendingMessages.Clear(); HashSet<long> hashSet = new HashSet<long>(); foreach (Announcement item in active) { if (item.IsRestart) { if (item.RestartInSeconds.HasValue) { double dueAt = now + item.RestartInSeconds.Value; if (restarts.TryGetValue(item.Id, out RestartState value)) { value.DueAt = dueAt; hashSet.Add(item.Id); } else if (item.RestartInSeconds.Value >= 0.0) { restarts[item.Id] = new RestartState(item.Text, dueAt); hashSet.Add(item.Id); } } } else if (item.Kind == "message" && !shownMessages.Contains(item.Id) && !string.IsNullOrEmpty(item.Text)) { pendingMessages.Add(item); } } List<long> list = new List<long>(); foreach (long key in restarts.Keys) { if (!hashSet.Contains(key)) { list.Add(key); } } foreach (long item2 in list) { restarts.Remove(item2); } } public List<Banner> Due(double now) { List<Banner> list = new List<Banner>(); foreach (Announcement pendingMessage in pendingMessages) { shownMessages.Add(pendingMessage.Id); list.Add(new Banner(pendingMessage.Id, "message", pendingMessage.Text, null, final: true)); } pendingMessages.Clear(); if ((double)shownMessages.Count > 1000.0) { shownMessages.Clear(); } List<long> list2 = new List<long>(); foreach (KeyValuePair<long, RestartState> restart in restarts) { Banner banner = NextRestartBanner(restart.Key, restart.Value, now); if (banner != null) { list.Add(banner); if (banner.Final) { list2.Add(restart.Key); } } } foreach (long item in list2) { restarts.Remove(item); } return list; } private static Banner? NextRestartBanner(long id, RestartState state, double now) { double num = state.DueAt - now; int value = (int)Math.Max(0.0, Math.Round(num)); if (num <= 0.0) { return new Banner(id, "restart", WithNote("Server restarting now", state.Note), 0, final: true); } int[] stageMinutes; if (!state.Announced) { state.Announced = true; stageMinutes = StageMinutes; foreach (int num2 in stageMinutes) { if ((double)(num2 * 60) >= num - 45.0) { state.FiredStages.Add(num2); } } return new Banner(id, "restart", WithNote(Describe(num), state.Note), value, final: false); } stageMinutes = StageMinutes; foreach (int num3 in stageMinutes) { if (state.FiredStages.Contains(num3)) { continue; } if (num > (double)(num3 * 60)) { break; } int[] stageMinutes2 = StageMinutes; foreach (int num4 in stageMinutes2) { if (num4 >= num3) { state.FiredStages.Add(num4); } } return new Banner(id, "restart", WithNote(InMinutes(num3), state.Note), value, final: false); } return null; } public static string Describe(double remainingSeconds) { if (remainingSeconds <= 0.0) { return "Server restarting now"; } if (remainingSeconds < 60.0) { return "Server restart in less than a minute"; } return InMinutes(Math.Max(1, (int)Math.Round(remainingSeconds / 60.0, MidpointRounding.AwayFromZero))); } private static string InMinutes(int minutes) { return "Server restart in " + minutes.ToString(CultureInfo.InvariantCulture) + " min"; } private static string WithNote(string text, string? note) { if (!string.IsNullOrEmpty(note)) { return text + ": " + note; } return text; } } public sealed class Batch { public List<PendingEvent> Events { get; } public byte[] Body { get; } public PendingEvent Last => Events[Events.Count - 1]; public Batch(List<PendingEvent> events, byte[] body) { Events = events; Body = body; } } public static class BatchBuilder { public const int MaxEvents = 200; public const int MaxBodyBytes = 524288; public const int FlushEventCount = 50; public static Batch Build(BatchMetadata metadata, IList<PendingEvent> pending, int maxEvents = 200, int maxBodyBytes = 524288) { string text = Prefix(metadata); int num = maxBodyBytes - Encoding.UTF8.GetByteCount(text) - 2; List<PendingEvent> list = new List<Pe