using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Splatform; using TMPro; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("PraetorisClient")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("PraetorisClient")] [assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("3A303D15-4E60-4110-8EDF-EDF38560FBE2")] [assembly: AssemblyFileVersion("0.1.31")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.31.0")] [module: UnverifiableCode] 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; } } } namespace PraetorisClient { internal static class BotApiClient { public static IEnumerator PostLinkRoutine(LinkRequest link, Action sendResult) { string linkApiUrl = PraetorisClientPlugin.GetLinkApiUrl(); string botApiKey = PraetorisClientPlugin.GetBotApiKey(); if (string.IsNullOrWhiteSpace(linkApiUrl)) { sendResult(link.Sender, link.RequestId, arg3: false, "Link API URL is not configured on the server."); yield break; } if (string.IsNullOrWhiteSpace(botApiKey)) { sendResult(link.Sender, link.RequestId, arg3: false, "Bot API key is not configured on the server."); yield break; } string s = JsonLinkRequest(link); byte[] bytes = Encoding.UTF8.GetBytes(s); UnityWebRequest request = new UnityWebRequest(linkApiUrl, "POST") { uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes), downloadHandler = (DownloadHandler)new DownloadHandlerBuffer() }; try { request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("X-API-Key", botApiKey); request.SetRequestHeader("User-Agent", "PraetorisClient/0.1"); yield return request.SendWebRequest(); string text = ((request.downloadHandler != null) ? request.downloadHandler.text : ""); if ((int)request.result != 1 || request.responseCode < 200 || request.responseCode >= 300) { string text2 = ((!string.IsNullOrWhiteSpace(text)) ? text : (string.IsNullOrWhiteSpace(request.error) ? ("HTTP " + request.responseCode) : (request.error + " (HTTP " + request.responseCode + ")"))); PraetorisClientPlugin.Log.LogWarning((object)("Discord link API failed for " + link.PlayerId + ": " + text2)); sendResult(link.Sender, link.RequestId, arg3: false, text2); } else { string arg = (string.IsNullOrWhiteSpace(text) ? "Discord link complete." : text); PraetorisClientPlugin.Log.LogInfo((object)("Discord link API accepted " + link.PlayerId + ".")); sendResult(link.Sender, link.RequestId, arg3: true, arg); } } finally { ((IDisposable)request)?.Dispose(); } } private static string JsonLinkRequest(LinkRequest link) { return "{\"requestId\":\"" + EscapeJson(link.RequestId) + "\",\"code\":\"" + EscapeJson(link.Code) + "\",\"playerId\":\"" + EscapeJson(link.PlayerId) + "\",\"playerName\":\"" + EscapeJson(link.PlayerName) + "\",\"endpoint\":\"" + EscapeJson(link.Endpoint) + "\",\"platformDisplayName\":\"" + EscapeJson(link.PlatformDisplayName) + "\",\"receivedAtUtc\":\"" + EscapeJson(link.ReceivedAtUtc.ToString("O", CultureInfo.InvariantCulture)) + "\"}"; } private static string EscapeJson(string value) { return (value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", "\\r") .Replace("\n", "\\n"); } } internal static class CreativeBiomeOverride { private sealed class OverrideZone { public Vector3 Center { get; } public float Radius { get; } public float RadiusSquared { get; } public float TerrainOuterRadius { get; } public float TerrainOuterRadiusSquared { get; } public float TerrainEdgeFalloffWidth { get; } public float TerrainEdgeFloorHeight { get; } public float VisualRadius { get; } public float VisualRadiusSquared { get; } public float TerrainPatchHalfSize { get; } public float GrassResetRadius { get; } public Biome Biome { get; } public bool SuppressSpawns { get; } public bool UseTerrainSource { get; } public Vector3 TerrainSourceCenter { get; } public bool SuppressVegetationDrops { get; } public OverrideZone(Vector3 center, float radius, Biome biome, bool suppressSpawns, bool useTerrainSource, Vector3 terrainSourceCenter, float terrainPatchHalfSize, float terrainEdgeFalloffWidth, float terrainEdgeFloorHeight, bool suppressVegetationDrops) { //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_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: 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) Center = center; Radius = radius; RadiusSquared = radius * radius; TerrainOuterRadius = radius + Mathf.Max(0f, terrainEdgeFalloffWidth); TerrainOuterRadiusSquared = TerrainOuterRadius * TerrainOuterRadius; TerrainEdgeFalloffWidth = Mathf.Max(0f, terrainEdgeFalloffWidth); TerrainEdgeFloorHeight = terrainEdgeFloorHeight; VisualRadius = radius + 16f; VisualRadiusSquared = VisualRadius * VisualRadius; TerrainPatchHalfSize = (useTerrainSource ? Mathf.Max(32f, terrainPatchHalfSize) : radius); GrassResetRadius = (useTerrainSource ? (Mathf.Sqrt(2f) * (TerrainPatchHalfSize + 32f)) : radius); Biome = biome; SuppressSpawns = suppressSpawns; UseTerrainSource = useTerrainSource; TerrainSourceCenter = terrainSourceCenter; SuppressVegetationDrops = suppressVegetationDrops; } public bool Contains(float x, float z) { return ContainsRadius(x, z, RadiusSquared); } public bool ContainsTerrain(float x, float z) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //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_002d: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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) if (!UseTerrainSource) { return ContainsRadius(x, z, RadiusSquared); } Vector3 zonePos = ZoneSystem.GetZonePos(ZoneSystem.GetZone(new Vector3(x, 0f, z))); if (Mathf.Abs(zonePos.x - Center.x) <= TerrainPatchHalfSize && Mathf.Abs(zonePos.z - Center.z) <= TerrainPatchHalfSize) { return ContainsRadius(x, z, TerrainOuterRadiusSquared); } return false; } public bool IntersectsTerrain(Heightmap heightmap) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) //IL_0091: 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) Vector3 position = ((Component)heightmap).transform.position; Vector3 zonePos = ZoneSystem.GetZonePos(ZoneSystem.GetZone(position)); if (Mathf.Abs(zonePos.x - Center.x) > TerrainPatchHalfSize || Mathf.Abs(zonePos.z - Center.z) > TerrainPatchHalfSize) { return false; } float num = (float)heightmap.m_width * heightmap.m_scale * 0.5f; float num2 = Math.Max(Math.Abs(position.x - Center.x) - num, 0f); float num3 = Math.Max(Math.Abs(position.z - Center.z) - num, 0f); return num2 * num2 + num3 * num3 <= TerrainOuterRadiusSquared; } private bool ContainsRadius(float x, float z, float radiusSquared) { //IL_0002: 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) float num = x - Center.x; float num2 = z - Center.z; return num * num + num2 * num2 <= radiusSquared; } public Vector2 MapToSource(float x, float z) { //IL_0001: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) return new Vector2(TerrainSourceCenter.x + (x - Center.x), TerrainSourceCenter.z + (z - Center.z)); } public TerrainSample CreateTerrainSample(float x, float z) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) float num = DistanceFromCenter(x, z); float sourceWeight = 1f; if (TerrainEdgeFalloffWidth > 0f && num > Radius) { float num2 = Mathf.Clamp01((num - Radius) / TerrainEdgeFalloffWidth); sourceWeight = 1f - Mathf.SmoothStep(0f, 1f, num2); } return new TerrainSample(MapToSource(x, z), sourceWeight, TerrainEdgeFloorHeight); } public bool ContainsVisual(float x, float z) { //IL_0013: 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 (UseTerrainSource) { return ContainsTerrain(x, z); } float num = x - Center.x; float num2 = z - Center.z; return num * num + num2 * num2 <= VisualRadiusSquared; } public bool HasSameState(OverrideZone other) { //IL_0001: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_005d: Unknown result type (might be due to invalid IL or missing references) if (Approximately(Center, other.Center) && Mathf.Approximately(Radius, other.Radius) && Biome == other.Biome && SuppressSpawns == other.SuppressSpawns && UseTerrainSource == other.UseTerrainSource && Approximately(TerrainSourceCenter, other.TerrainSourceCenter) && Mathf.Approximately(TerrainPatchHalfSize, other.TerrainPatchHalfSize) && Mathf.Approximately(TerrainEdgeFalloffWidth, other.TerrainEdgeFalloffWidth) && Mathf.Approximately(TerrainEdgeFloorHeight, other.TerrainEdgeFloorHeight)) { return SuppressVegetationDrops == other.SuppressVegetationDrops; } return false; } private float DistanceFromCenter(float x, float z) { //IL_0002: 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) float num = x - Center.x; float num2 = z - Center.z; return Mathf.Sqrt(num * num + num2 * num2); } private static bool Approximately(Vector3 left, Vector3 right) { //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_0013: 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_0026: 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 (Mathf.Approximately(left.x, right.x) && Mathf.Approximately(left.y, right.y)) { return Mathf.Approximately(left.z, right.z); } return false; } } private readonly struct TerrainSample { public Vector2 Source { get; } public float SourceWeight { get; } public float FloorHeight { get; } public TerrainSample(Vector2 source, float sourceWeight, float floorHeight) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Source = source; SourceWeight = sourceWeight; FloorHeight = floorHeight; } public float ApplyHeight(float sourceHeight) { return Mathf.Lerp(FloorHeight, sourceHeight, SourceWeight); } } [HarmonyPatch(typeof(WorldGenerator), "GetBiome", new Type[] { typeof(float), typeof(float), typeof(float), typeof(bool) })] private static class WorldGeneratorGetBiomePatch { private static bool Prefix(float wx, float wy, ref Biome __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if (!TryGetBiome(wx, wy, out var biome)) { return true; } __result = (Biome)(int)biome; return false; } } [HarmonyPatch(typeof(Heightmap), "GetBiome", new Type[] { typeof(Vector3), typeof(float), typeof(bool) })] private static class HeightmapGetBiomePatch { private static bool Prefix(Vector3 point, ref Biome __result) { //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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected I4, but got Unknown if (!TryGetBiome(point.x, point.z, out var biome)) { return true; } __result = (Biome)(int)biome; return false; } } [HarmonyPatch(typeof(WorldGenerator), "GetBiomeHeight")] private static class WorldGeneratorGetBiomeHeightPatch { private static bool Prefix(ref Biome biome, float wx, float wy, ref Color mask, bool preGeneration, ref float __result) { //IL_0022: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected I4, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!TryGetTerrainSample(wx, wy, out var sample) || WorldGenerator.instance == null) { return true; } _samplingSourceTerrain = true; try { biome = (Biome)(int)WorldGenerator.instance.GetBiome(sample.Source.x, sample.Source.y, 0.02f, false); __result = sample.ApplyHeight(WorldGenerator.instance.GetBiomeHeight(biome, sample.Source.x, sample.Source.y, ref mask, preGeneration)); } finally { _samplingSourceTerrain = false; } return false; } } [HarmonyPatch(typeof(WorldGenerator), "GetHeight", new Type[] { typeof(float), typeof(float) })] private static class WorldGeneratorGetHeightPatch { private static bool Prefix(float wx, float wy, ref float __result) { //IL_0024: 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) if (!TryGetTerrainSample(wx, wy, out var sample) || WorldGenerator.instance == null) { return true; } _samplingSourceTerrain = true; try { __result = sample.ApplyHeight(WorldGenerator.instance.GetHeight(sample.Source.x, sample.Source.y)); } finally { _samplingSourceTerrain = false; } return false; } } [HarmonyPatch] private static class WorldGeneratorGetHeightWithMaskPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(WorldGenerator), "GetHeight", new Type[3] { typeof(float), typeof(float), typeof(Color).MakeByRefType() }, (Type[])null); } private static bool Prefix(float wx, float wy, ref Color mask, ref float __result) { //IL_0024: 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) if (!TryGetTerrainSample(wx, wy, out var sample) || WorldGenerator.instance == null) { return true; } _samplingSourceTerrain = true; try { __result = sample.ApplyHeight(WorldGenerator.instance.GetHeight(sample.Source.x, sample.Source.y, ref mask)); } finally { _samplingSourceTerrain = false; } return false; } } [HarmonyPatch(typeof(Heightmap), "GetBiomeColor", new Type[] { typeof(float), typeof(float) })] private static class HeightmapGetBiomeColorPatch { private static bool Prefix(Heightmap __instance, float ix, float iy, ref Color __result) { //IL_0026: 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_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: 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_0071: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return true; } float num = (float)__instance.m_width * __instance.m_scale * 0.5f; Vector3 position = ((Component)__instance).transform.position; float x = position.x + (ix - 0.5f) * num * 2f; float z = position.z + (iy - 0.5f) * num * 2f; if (!TryGetVisualBiome(x, z, out var biome)) { return true; } __result = Color32.op_Implicit(Heightmap.GetBiomeColor(biome)); return false; } } [HarmonyPatch(typeof(WorldGenerator), "IsAshlands")] private static class WorldGeneratorIsAshlandsPatch { private static bool Prefix(float x, float y, ref bool __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if (!TryGetBiome(x, y, out var biome)) { return true; } __result = (int)biome == 32; return false; } } [HarmonyPatch(typeof(WorldGenerator), "IsDeepnorth")] private static class WorldGeneratorIsDeepnorthPatch { private static bool Prefix(float x, float y, ref bool __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if (!TryGetBiome(x, y, out var biome)) { return true; } __result = (int)biome == 64; return false; } } [HarmonyPatch(typeof(SpawnSystem), "IsSpawnPointGood")] private static class SpawnSystemIsSpawnPointGoodPatch { private static bool Prefix(ref Vector3 spawnPoint, ref bool __result) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!ContainsSpawnBlockedZone(spawnPoint)) { return true; } __result = false; return false; } } private const int ProtocolVersion = 5; private const string ZdoVegetationMarker = "valheimCreative.vegetation"; private const string ZdoVegetationSlotId = "valheimCreative.vegetationSlot"; private const float VisualBiomeMargin = 16f; private static readonly Dictionary Zones = new Dictionary(); private static readonly FieldInfo? HeightmapBuildDataField = AccessTools.Field(typeof(Heightmap), "m_buildData"); [ThreadStatic] private static bool _samplingSourceTerrain; public static void OnOverride(long sender, ZPackage pkg) { //IL_0079: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } try { int num = pkg.ReadInt(); if (num < 1 || num > 5) { PraetorisClientPlugin.Log.LogWarning((object)$"Ignoring creative biome override version {num}; expected 1-{5}."); return; } int num2 = pkg.ReadInt(); if (num2 <= 0) { ClearAll(); return; } for (int i = 0; i < num2; i++) { string text = pkg.ReadString(); bool num3 = pkg.ReadBool(); Vector3 center = pkg.ReadVector3(); float num4 = pkg.ReadSingle(); Biome val = (Biome)pkg.ReadInt(); bool suppressSpawns = ((num2 == 1 && pkg.GetPos() < pkg.Size()) ? pkg.ReadBool() : (!text.StartsWith("siege_", StringComparison.OrdinalIgnoreCase))); bool useTerrainSource = false; Vector3 terrainSourceCenter = Vector3.zero; if (num >= 2 && pkg.GetPos() < pkg.Size()) { useTerrainSource = pkg.ReadBool(); terrainSourceCenter = pkg.ReadVector3(); } float terrainPatchHalfSize = CalculateTerrainPatchHalfSize(num4); if (num >= 3 && pkg.GetPos() < pkg.Size()) { float num5 = pkg.ReadSingle(); if (num5 > 0f) { terrainPatchHalfSize = num5; } } float terrainEdgeFalloffWidth = 0f; float terrainEdgeFloorHeight = 0f; if (num >= 4 && pkg.GetPos() < pkg.Size()) { terrainEdgeFalloffWidth = Mathf.Max(0f, pkg.ReadSingle()); } if (num >= 4 && pkg.GetPos() < pkg.Size()) { terrainEdgeFloorHeight = pkg.ReadSingle(); } bool suppressVegetationDrops = !text.StartsWith("siege_", StringComparison.OrdinalIgnoreCase); if (num >= 5 && pkg.GetPos() < pkg.Size()) { suppressVegetationDrops = pkg.ReadBool(); } if (!num3 || (int)val == 0 || num4 <= 0f) { Remove(text); } else { Set(text, center, num4, val, suppressSpawns, useTerrainSource, terrainSourceCenter, terrainPatchHalfSize, terrainEdgeFalloffWidth, terrainEdgeFloorHeight, suppressVegetationDrops); } } } catch (Exception arg) { PraetorisClientPlugin.Log.LogWarning((object)$"Failed to apply creative biome override: {arg}"); } } public static bool TryGetBiome(float x, float z, out Biome biome) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected I4, but got Unknown //IL_0042: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected I4, but got Unknown if (_samplingSourceTerrain) { biome = (Biome)0; return false; } foreach (OverrideZone value in Zones.Values) { if (!value.ContainsTerrain(x, z)) { continue; } if (value.UseTerrainSource && WorldGenerator.instance != null) { Vector2 val = value.MapToSource(x, z); _samplingSourceTerrain = true; try { biome = (Biome)(int)WorldGenerator.instance.GetBiome(val.x, val.y, 0.02f, false); } finally { _samplingSourceTerrain = false; } return true; } if (!value.UseTerrainSource) { biome = (Biome)(int)value.Biome; return true; } } biome = (Biome)0; return false; } private static bool TryGetVisualBiome(float x, float z, out Biome biome) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected I4, but got Unknown foreach (OverrideZone value in Zones.Values) { if (value.ContainsVisual(x, z)) { biome = (Biome)(int)value.Biome; return true; } } biome = (Biome)0; return false; } private static bool TryGetTerrainSample(float x, float z, out TerrainSample sample) { sample = default(TerrainSample); if (_samplingSourceTerrain) { return false; } foreach (OverrideZone value in Zones.Values) { if (value.UseTerrainSource && value.ContainsTerrain(x, z)) { sample = value.CreateTerrainSample(x, z); return true; } } return false; } public static bool ContainsSpawnBlockedZone(Vector3 point) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) foreach (OverrideZone value in Zones.Values) { if (value.SuppressSpawns && value.Contains(point.x, point.z)) { return true; } } return false; } public static bool ShouldSuppressVegetationDrops(Component component) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)component == (Object)null) { return false; } return ShouldSuppressVegetationDrops(component, component.transform.position); } public static bool ShouldSuppressVegetationDrops(Component component, Vector3 point) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)component == (Object)null) { return false; } foreach (OverrideZone value in Zones.Values) { if (value.SuppressVegetationDrops && value.Contains(point.x, point.z)) { ZNetView val = component.GetComponent() ?? component.GetComponentInParent(); ZDO val2 = (((Object)(object)val != (Object)null) ? val.GetZDO() : null); if (val2 != null && IsMarkedCreativeVegetation(val2)) { return true; } return (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null || (Object)(object)component.GetComponent() != (Object)null; } } return false; } public static bool ContainsTerrainOverride(Vector3 point) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) foreach (OverrideZone value in Zones.Values) { if (value.UseTerrainSource && value.ContainsTerrain(point.x, point.z)) { return true; } } return false; } private static bool IsMarkedCreativeVegetation(ZDO zdo) { if (!zdo.GetBool("valheimCreative.vegetation", false)) { return !string.IsNullOrWhiteSpace(zdo.GetString("valheimCreative.vegetationSlot", "")); } return true; } private static void Set(string zoneId, Vector3 center, float radius, Biome biome, bool suppressSpawns, bool useTerrainSource, Vector3 terrainSourceCenter, float terrainPatchHalfSize, float terrainEdgeFalloffWidth, float terrainEdgeFloorHeight, bool suppressVegetationDrops) { //IL_0016: 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) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) zoneId = NormalizeZoneId(zoneId); OverrideZone value; bool flag = Zones.TryGetValue(zoneId, out value); OverrideZone overrideZone = new OverrideZone(center, radius, biome, suppressSpawns, useTerrainSource, terrainSourceCenter, terrainPatchHalfSize, terrainEdgeFalloffWidth, terrainEdgeFloorHeight, suppressVegetationDrops); if (!flag || !value.HasSameState(overrideZone)) { Zones[zoneId] = overrideZone; if (flag) { RefreshTerrain(value); } RefreshTerrain(overrideZone); string text = (useTerrainSource ? $", terrainSource={terrainSourceCenter.x:0.##},{terrainSourceCenter.z:0.##}" : string.Empty); PraetorisClientPlugin.Log.LogInfo((object)$"Creative biome override {zoneId}: {biome} at {center.x:0.##},{center.z:0.##} radius {radius:0.##}, suppressSpawns={suppressSpawns}, suppressVegetationDrops={suppressVegetationDrops}{text}."); } } private static void Remove(string zoneId) { zoneId = NormalizeZoneId(zoneId); if (Zones.TryGetValue(zoneId, out OverrideZone value)) { Zones.Remove(zoneId); RefreshTerrain(value); PraetorisClientPlugin.Log.LogInfo((object)("Removed creative biome override " + zoneId + ".")); } } private static void ClearAll() { if (Zones.Count == 0) { return; } List list = new List(Zones.Values); Zones.Clear(); foreach (OverrideZone item in list) { RefreshTerrain(item); } PraetorisClientPlugin.Log.LogInfo((object)"Cleared creative biome overrides."); } private static void RefreshTerrain(OverrideZone zone) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) Heightmap[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Heightmap val in array) { if (!((Object)(object)val == (Object)null) && Intersects(val, zone)) { HeightmapBuildDataField?.SetValue(val, null); val.Poke(false); } } if ((Object)(object)ClutterSystem.instance != (Object)null) { ClutterSystem.instance.ResetGrass(zone.Center, zone.GrassResetRadius); } } private static bool Intersects(Heightmap heightmap, OverrideZone zone) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)heightmap).transform.position; if (zone.UseTerrainSource) { return zone.IntersectsTerrain(heightmap); } float num = (float)heightmap.m_width * heightmap.m_scale * 0.5f; float num2 = Math.Max(Math.Abs(position.x - zone.Center.x) - num, 0f); float num3 = Math.Max(Math.Abs(position.z - zone.Center.z) - num, 0f); return num2 * num2 + num3 * num3 <= zone.RadiusSquared; } private static float CalculateTerrainPatchHalfSize(float radius) { float num = Mathf.Max(1f, radius); float num2 = Mathf.Ceil(Mathf.Max(0f, num - 32f) / 64f); return 32f + num2 * 64f; } private static string NormalizeZoneId(string zoneId) { if (!string.IsNullOrWhiteSpace(zoneId)) { return zoneId.Trim(); } return "creative"; } } internal static class CreativeCommandZoneState { private const int ProtocolVersion = 1; private const string DeniedMessage = "Creative commands can only be used inside your creative zone."; private static bool _active; private static Vector3 _center; private static float _radius; private static long _ownerPlayerId; private static long _playerId; private static string _slotId = string.Empty; private static bool _guardEnabled; private static string _protectedCommandPrefixes = string.Empty; internal static void OnState(long sender, ZPackage package) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } try { int num = package.ReadInt(); if (num != 1) { PraetorisClientPlugin.Log.LogWarning((object)$"Ignoring creative command zone state protocol {num}; expected {1}."); return; } bool flag = package.ReadBool(); Vector3 val = package.ReadVector3(); float num2 = package.ReadSingle(); long num3 = package.ReadLong(); long num4 = package.ReadLong(); string text = package.ReadString(); bool flag2 = package.GetPos() < package.Size() && package.ReadBool(); string text2 = ((package.GetPos() < package.Size()) ? package.ReadString() : string.Empty); if (!flag || !(num2 > 0f) || num4 == 0L || !_active || !Approximately(_center, val) || !Mathf.Approximately(_radius, num2) || _ownerPlayerId != num3 || _playerId != num4 || !string.Equals(_slotId, text ?? string.Empty, StringComparison.Ordinal) || _guardEnabled != flag2 || !string.Equals(_protectedCommandPrefixes, text2 ?? string.Empty, StringComparison.Ordinal)) { _guardEnabled = flag2; _protectedCommandPrefixes = text2 ?? string.Empty; if (!flag || num2 <= 0f || num4 == 0L) { Clear(); return; } _active = true; _center = val; _radius = num2; _ownerPlayerId = num3; _playerId = num4; _slotId = text ?? string.Empty; PraetorisClientPlugin.Log.LogInfo((object)$"Creative command zone active: {_slotId} at {_center.x:0.##},{_center.z:0.##} radius {_radius:0.##}."); } } catch (Exception arg) { Clear(); PraetorisClientPlugin.Log.LogWarning((object)$"Failed to apply creative command zone state: {arg}"); } } internal static void Clear() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) _active = false; _center = Vector3.zero; _radius = 0f; _ownerPlayerId = 0L; _playerId = 0L; _slotId = string.Empty; } internal static bool CanRunCommand(ConsoleEventArgs args) { if (args == null || (Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || !IsProtectedCommand(args.FullLine)) { return true; } if (IsLocalPlayerInsideActiveZone()) { return true; } object obj = ((object)args.Context) ?? ((object)Console.instance); if (obj != null) { ((Terminal)obj).AddString("Creative commands can only be used inside your creative zone."); } return false; } private static bool IsProtectedCommand(string rawCommand) { if (!_guardEnabled) { return false; } string text = NormalizeCommand(rawCommand); if (text.Length == 0) { return false; } foreach (string protectedCommandPrefix in GetProtectedCommandPrefixes()) { if (text.StartsWith(protectedCommandPrefix, StringComparison.Ordinal)) { return true; } } return false; } internal static bool IsLocalPlayerInsideActiveZone() { //IL_0040: 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) Player localPlayer = Player.m_localPlayer; if (!_active || (Object)(object)localPlayer == (Object)null || _radius <= 0f) { return false; } if (_playerId != 0L && localPlayer.GetPlayerID() != _playerId) { return false; } Vector3 position = ((Component)localPlayer).transform.position; float num = position.x - _center.x; float num2 = position.z - _center.z; return num * num + num2 * num2 <= _radius * _radius; } private static IEnumerable GetProtectedCommandPrefixes() { return from value in _protectedCommandPrefixes.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select value.Trim().ToLowerInvariant() into value where value.Length > 0 select value; } private static string NormalizeCommand(string rawCommand) { if (string.IsNullOrWhiteSpace(rawCommand)) { return string.Empty; } string[] array = rawCommand.Trim().Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 0) { return array[0].ToLowerInvariant(); } return string.Empty; } private static bool Approximately(Vector3 left, Vector3 right) { //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_0013: 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_0026: 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 (Mathf.Approximately(left.x, right.x) && Mathf.Approximately(left.y, right.y)) { return Mathf.Approximately(left.z, right.z); } return false; } } internal static class CreativeInventoryRpc { private sealed class CreativeInventorySnapshot { public bool Available { get; set; } public string Error { get; set; } = string.Empty; public long PlayerId { get; set; } public string PlayerName { get; set; } = string.Empty; public int PlayerInventoryCount { get; set; } public bool ExtraSlotsLoaded { get; set; } public bool ExtraSlotsAvailable { get; set; } public int ExtraSlotsCount { get; set; } public int TotalUniqueCount { get; set; } public List Items { get; } = new List(); public static CreativeInventorySnapshot Unavailable(string error) { return new CreativeInventorySnapshot { Available = false, Error = error }; } } private sealed class CreativeInventoryItem { public string Source { get; set; } = string.Empty; public string PrefabName { get; set; } = string.Empty; public string SharedName { get; set; } = string.Empty; public int Stack { get; set; } public int Quality { get; set; } public bool Equipped { get; set; } public int GridX { get; set; } public int GridY { get; set; } public static CreativeInventoryItem FromItem(string source, ItemData item) { return new CreativeInventoryItem { Source = source, PrefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : string.Empty), SharedName = (item.m_shared?.m_name ?? string.Empty), Stack = item.m_stack, Quality = item.m_quality, Equipped = item.m_equipped, GridX = item.m_gridPos.x, GridY = item.m_gridPos.y }; } } private sealed class ExtraSlotsReadResult { public bool ModLoaded { get; private set; } public bool Available { get; private set; } public string Error { get; private set; } = string.Empty; public List Items { get; } = new List(); public static ExtraSlotsReadResult NotLoaded() { return new ExtraSlotsReadResult { ModLoaded = false, Available = true }; } public static ExtraSlotsReadResult Loaded(IEnumerable items) { ExtraSlotsReadResult extraSlotsReadResult = new ExtraSlotsReadResult(); extraSlotsReadResult.ModLoaded = true; extraSlotsReadResult.Available = true; extraSlotsReadResult.Items.AddRange(items); return extraSlotsReadResult; } public static ExtraSlotsReadResult Failed(string error) { return new ExtraSlotsReadResult { ModLoaded = true, Available = false, Error = error }; } } private const int ProtocolVersion = 1; private const string ExtraSlotsApiTypeName = "ExtraSlots.API"; public static void OnRequest(long sender, ZPackage pkg) { //IL_0092: 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_0025: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0035: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { return; } string text = string.Empty; ZDOID val = ZDOID.None; bool flag = true; try { int num = pkg.ReadInt(); if (num != 1) { SendUnavailable(sender, text, val, $"Unsupported creative inventory protocol version {num}."); return; } text = pkg.ReadString(); val = pkg.ReadZDOID(); flag = pkg.ReadBool(); CreativeInventorySnapshot snapshot = BuildSnapshot(val, flag); SendResponse(sender, text, val, snapshot); } catch (Exception arg) { PraetorisClientPlugin.Log.LogWarning((object)$"Failed to answer creative inventory request {text}: {arg}"); SendUnavailable(sender, text, val, "Failed to read client inventory."); } } private static CreativeInventorySnapshot BuildSnapshot(ZDOID expectedCharacterId, bool includeItems) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).m_nview == (Object)null || !((Character)localPlayer).m_nview.IsValid()) { return CreativeInventorySnapshot.Unavailable("Local player is not available."); } ZDO zDO = ((Character)localPlayer).m_nview.GetZDO(); if (zDO == null) { return CreativeInventorySnapshot.Unavailable("Local player character is not available."); } if (!((ZDOID)(ref expectedCharacterId)).IsNone() && zDO.m_uid != expectedCharacterId) { return CreativeInventorySnapshot.Unavailable("Local player character did not match the requested character."); } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory == null) { return CreativeInventorySnapshot.Unavailable("Local player inventory is not available."); } List list = (from item in inventory.GetAllItems() where item != null select item).ToList(); ExtraSlotsReadResult extraSlotsReadResult = ReadExtraSlotsItems(); if (!extraSlotsReadResult.Available && extraSlotsReadResult.ModLoaded) { return CreativeInventorySnapshot.Unavailable(extraSlotsReadResult.Error); } List list2 = new List(); AddUnique(list2, list); AddUnique(list2, extraSlotsReadResult.Items); CreativeInventorySnapshot creativeInventorySnapshot = new CreativeInventorySnapshot { Available = true, Error = string.Empty, PlayerId = localPlayer.GetPlayerID(), PlayerName = localPlayer.GetPlayerName(), PlayerInventoryCount = list.Count, ExtraSlotsAvailable = extraSlotsReadResult.Available, ExtraSlotsLoaded = extraSlotsReadResult.ModLoaded, ExtraSlotsCount = extraSlotsReadResult.Items.Count, TotalUniqueCount = list2.Count }; if (includeItems) { creativeInventorySnapshot.Items.AddRange(list.Select((ItemData item) => CreativeInventoryItem.FromItem("player", item))); creativeInventorySnapshot.Items.AddRange(extraSlotsReadResult.Items.Select((ItemData item) => CreativeInventoryItem.FromItem("extraSlots", item))); } return creativeInventorySnapshot; } private static ExtraSlotsReadResult ReadExtraSlotsItems() { Type type = (from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("ExtraSlots.API", throwOnError: false)).FirstOrDefault((Type type2) => type2 != null); if (type == null) { return ExtraSlotsReadResult.NotLoaded(); } MethodInfo method = type.GetMethod("GetAllExtraSlotsItems", BindingFlags.Static | BindingFlags.Public); if (method == null) { return ExtraSlotsReadResult.Failed("ExtraSlots API did not expose GetAllExtraSlotsItems."); } try { if (!(method.Invoke(null, Array.Empty()) is IEnumerable enumerable)) { return ExtraSlotsReadResult.Failed("ExtraSlots API returned no item list."); } List list = new List(); foreach (object item in enumerable) { ItemData val = (ItemData)((item is ItemData) ? item : null); if (val != null) { list.Add(val); } } return ExtraSlotsReadResult.Loaded(list); } catch (Exception ex) { return ExtraSlotsReadResult.Failed("ExtraSlots API read failed: " + ex.Message); } } private static void AddUnique(List target, IEnumerable items) { foreach (ItemData item in items) { if (!target.Contains(item)) { target.Add(item); } } } private static void SendUnavailable(long target, string requestId, ZDOID characterId, string error) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) SendResponse(target, requestId, characterId, CreativeInventorySnapshot.Unavailable(error)); } private static void SendResponse(long target, string requestId, ZDOID characterId, CreativeInventorySnapshot snapshot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0015: Unknown result type (might be due to invalid IL or missing references) ZPackage val = new ZPackage(); val.Write(1); val.Write(requestId); val.Write(characterId); val.Write(snapshot.Available); val.Write(snapshot.Error); val.Write(snapshot.PlayerId); val.Write(snapshot.PlayerName); val.Write(snapshot.PlayerInventoryCount); val.Write(snapshot.ExtraSlotsLoaded); val.Write(snapshot.ExtraSlotsAvailable); val.Write(snapshot.ExtraSlotsCount); val.Write(snapshot.TotalUniqueCount); val.Write(snapshot.Items.Count); foreach (CreativeInventoryItem item in snapshot.Items) { val.Write(item.Source); val.Write(item.PrefabName); val.Write(item.SharedName); val.Write(item.Stack); val.Write(item.Quality); val.Write(item.Equipped); val.Write(item.GridX); val.Write(item.GridY); } ZRoutedRpc.instance.InvokeRoutedRPC(target, "DiscordTools_CreativeInventoryResponse", new object[1] { val }); } } internal static class CreativeVegetationDropPatches { [HarmonyPatch] private static class TreeBaseRpcDamagePatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TreeBase), "RPC_Damage", (Type[])null, (Type[])null); } private static void Prefix(TreeBase __instance, ref DropPatchState? __state) { __state = ReplaceDropsIfSuppressed((Component)(object)__instance, __instance.m_dropWhenDestroyed); if (__state != null) { __instance.m_dropWhenDestroyed = EmptyDropTable; } } private static void Postfix(TreeBase __instance, DropPatchState? __state) { if (__state != null) { __instance.m_dropWhenDestroyed = __state.DropTable; } } } [HarmonyPatch] private static class TreeBaseSpawnLogPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TreeBase), "SpawnLog", (Type[])null, (Type[])null); } private static bool Prefix(TreeBase __instance) { return !CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance); } } [HarmonyPatch] private static class TreeLogDestroyPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(TreeLog), "Destroy", (Type[])null, (Type[])null); } private static void Prefix(TreeLog __instance, ref TreeLogPatchState? __state) { if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance)) { __state = null; return; } __state = new TreeLogPatchState(__instance.m_dropWhenDestroyed, __instance.m_subLogPrefab); __instance.m_dropWhenDestroyed = EmptyDropTable; __instance.m_subLogPrefab = null; } private static void Postfix(TreeLog __instance, TreeLogPatchState? __state) { if (__state != null) { __instance.m_dropWhenDestroyed = __state.DropTable; __instance.m_subLogPrefab = __state.SubLogPrefab; } } } [HarmonyPatch] private static class PickableRpcPickPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(Pickable), "RPC_Pick", (Type[])null, (Type[])null); } private static bool Prefix(Pickable __instance) { if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance)) { return true; } ZNetView component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null || !component.IsOwner()) { return true; } __instance.SetPicked(true); return false; } } [HarmonyPatch] private static class DropOnDestroyedPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(DropOnDestroyed), "OnDestroyed", (Type[])null, (Type[])null); } private static bool Prefix(DropOnDestroyed __instance) { return !CreativeBiomeOverride.ShouldSuppressVegetationDrops((Component)(object)__instance); } } [HarmonyPatch] private static class MineRockRpcHitPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(MineRock), "RPC_Hit", (Type[])null, (Type[])null); } private static void Prefix(MineRock __instance, HitData hit, ref DropPatchState? __state) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) __state = ReplaceDropsIfSuppressed((Component)(object)__instance, __instance.m_dropItems, hit.m_point); if (__state != null) { __instance.m_dropItems = EmptyDropTable; } } private static void Postfix(MineRock __instance, DropPatchState? __state) { if (__state != null) { __instance.m_dropItems = __state.DropTable; } } } [HarmonyPatch] private static class MineRock5DamageAreaPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(MineRock5), "DamageArea", (Type[])null, (Type[])null); } private static void Prefix(MineRock5 __instance, HitData hit, ref DropPatchState? __state) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) __state = ReplaceDropsIfSuppressed((Component)(object)__instance, __instance.m_dropItems, hit.m_point); if (__state != null) { __instance.m_dropItems = EmptyDropTable; } } private static void Postfix(MineRock5 __instance, DropPatchState? __state) { if (__state != null) { __instance.m_dropItems = __state.DropTable; } } } private sealed class DropPatchState { internal DropTable DropTable { get; } internal DropPatchState(DropTable dropTable) { DropTable = dropTable; } } private sealed class TreeLogPatchState { internal DropTable DropTable { get; } internal GameObject SubLogPrefab { get; } internal TreeLogPatchState(DropTable dropTable, GameObject subLogPrefab) { DropTable = dropTable; SubLogPrefab = subLogPrefab; } } private static readonly DropTable EmptyDropTable = new DropTable { m_drops = new List(), m_dropMin = 0, m_dropMax = 0, m_dropChance = 0f }; private static DropPatchState? ReplaceDropsIfSuppressed(Component component, DropTable dropTable) { if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops(component)) { return null; } return new DropPatchState(dropTable); } private static DropPatchState? ReplaceDropsIfSuppressed(Component component, DropTable dropTable, Vector3 point) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) if (!CreativeBiomeOverride.ShouldSuppressVegetationDrops(component, point)) { return null; } return new DropPatchState(dropTable); } } internal static class LinkCommandHandler { private static readonly Regex CodePattern = new Regex("^[A-Za-z0-9_-]{4,64}$", RegexOptions.Compiled); public static bool TryHandle(Chat chat) { string text = (((Object)(object)((Terminal)chat).m_input != (Object)null) ? ((TMP_InputField)((Terminal)chat).m_input).text : ""); if (string.IsNullOrWhiteSpace(text)) { return false; } string text2 = PraetorisClientPlugin.LinkCommand.Value.Trim(); if (string.IsNullOrWhiteSpace(text2)) { text2 = "!link"; } string text3 = text.Trim(); if (!text3.Equals(text2, StringComparison.OrdinalIgnoreCase) && !text3.StartsWith(text2 + " ", StringComparison.OrdinalIgnoreCase)) { return false; } string text4 = ((text3.Length > text2.Length) ? text3.Substring(text2.Length).Trim() : ""); if (!CodePattern.IsMatch(text4)) { ((Terminal)chat).AddString("Usage: " + text2 + " CODE"); ClearInput(chat); return true; } if (!LinkRpc.TrySendRequest(text4, out string message)) { ((Terminal)chat).AddString(message); ClearInput(chat); return true; } ((Terminal)chat).AddString("Sending Discord link code to the server."); ClearInput(chat); return true; } private static void ClearInput(Chat chat) { if (!((Object)(object)((Terminal)chat).m_input == (Object)null)) { ((TMP_InputField)((Terminal)chat).m_input).text = ""; ((Component)((Terminal)chat).m_input).gameObject.SetActive(false); } } } internal static class LinkRpc { public static bool TrySendRequest(string code, out string message) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown message = ""; if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null || ZNet.instance.IsServer() || (int)ZNet.GetConnectionStatus() != 2) { message = "You must be connected to a server before linking Discord."; return false; } string text = Guid.NewGuid().ToString("N"); ZPackage val = new ZPackage(); val.Write(text); val.Write(code); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DiscordTools_LinkRequest", new object[1] { val }); return true; } public static void OnRequest(long sender, ZPackage pkg) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } string requestId = pkg.ReadString(); string code = pkg.ReadString(); ZNetPeer val = PlayerResolver.FindPeerBySender(sender); if (val == null) { SendResult(sender, requestId, success: false, "The server could not identify your Valheim connection."); return; } PraetorisClientPlugin instance = PraetorisClientPlugin.Instance; if ((Object)(object)instance == (Object)null) { SendResult(sender, requestId, success: false, "PraetorisClient is not ready on the server."); return; } LinkRequest link = new LinkRequest { Sender = sender, RequestId = requestId, Code = code, PlayerId = PlayerResolver.StablePlayerId(val), PlayerName = (val.m_playerName ?? ""), Endpoint = PlayerResolver.SafeEndPoint(val), PlatformDisplayName = PlayerResolver.PlatformDisplayName(val), ReceivedAtUtc = DateTime.UtcNow }; PraetorisClientPlugin.Log.LogInfo((object)("Received Discord link code from " + PlayerResolver.DescribePeer(val) + ".")); ((MonoBehaviour)instance).StartCoroutine(BotApiClient.PostLinkRoutine(link, SendResult)); } public static void OnResult(long sender, ZPackage pkg) { if (!((Object)(object)ZNet.instance == (Object)null) && !ZNet.instance.IsServer()) { string text = pkg.ReadString(); bool flag = pkg.ReadBool(); string text2 = pkg.ReadString(); PraetorisClientPlugin.Log.LogInfo((object)("Discord link result " + text + ": " + text2)); Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(flag ? text2 : ("Discord link failed: " + text2)); } } } private static void SendResult(long target, string requestId, bool success, string message) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(requestId); val.Write(success); val.Write(message); ZRoutedRpc.instance.InvokeRoutedRPC(target, "DiscordTools_LinkResult", new object[1] { val }); } } internal sealed class LinkRequest { public long Sender; public string RequestId = ""; public string Code = ""; public string PlayerId = ""; public string PlayerName = ""; public string Endpoint = ""; public string PlatformDisplayName = ""; public DateTime ReceivedAtUtc; } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakePatch { private static void Postfix() { CreativeCommandZoneState.Clear(); PraetorisClientRpc.Register(); } } [HarmonyPatch(typeof(Chat), "SendInput")] internal static class ChatSendInputPatch { private static bool Prefix(Chat __instance) { return !LinkCommandHandler.TryHandle(__instance); } } [HarmonyPatch(typeof(Character), "ApplyDamage")] internal static class CharacterApplyDamageTelemetryPatch { private static void Prefix(Character __instance, HitData hit, ref DamageObservationState __state) { __state = ValheimEventsTelemetry.CaptureDamageBefore(__instance, hit); } private static void Postfix(Character __instance, HitData hit, DamageModifier mod, DamageObservationState __state) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) ValheimEventsTelemetry.LogDamageApplied(__instance, hit, mod, __state); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class PlayerDeathTelemetryPatch { private static void Prefix(Player __instance, ref DeathObservationState __state) { __state = ValheimEventsTelemetry.CaptureDeathBefore(__instance); } private static void Postfix(Player __instance, DeathObservationState __state) { ValheimEventsTelemetry.LogPlayerDied(__instance, __state); } } [HarmonyPatch(typeof(Minimap), "Explore", new Type[] { typeof(int), typeof(int) })] internal static class MinimapExploreTelemetryPatch { private static void Postfix(Minimap __instance, int x, int y, bool __result) { if (__result) { ValheimEventsTelemetry.RecordExploredCell(__instance, x, y); } } } [HarmonyPatch(typeof(Game), "Update")] internal static class GameUpdateTelemetryPatch { private static void Postfix() { ValheimEventsTelemetry.Update(); RpcTraceTelemetry.Update(); } } [HarmonyPatch(typeof(Game), "Logout")] internal static class GameLogoutRpcTracePatch { private static bool Prefix(Game __instance, bool save, bool changeToStartScene) { return RpcTraceTelemetry.ShouldAllowLogout(__instance, save, changeToStartScene); } } [HarmonyPatch(typeof(Game), "OnApplicationQuit")] internal static class GameApplicationQuitRpcTracePatch { private static void Prefix() { RpcTraceTelemetry.OnApplicationQuitFallback(); } } [HarmonyPatch(typeof(Menu), "QuitGame")] internal static class MenuQuitGameRpcTracePatch { private static bool Prefix() { return RpcTraceTelemetry.ShouldAllowMenuQuit(); } } [HarmonyPatch(typeof(Menu), "OnQuitYes")] internal static class MenuQuitYesRpcTracePatch { private static bool Prefix() { return RpcTraceTelemetry.ShouldAllowMenuQuit(); } } [HarmonyPatch(typeof(ConsoleCommand), "RunAction")] internal static class ConsoleCommandRunActionCreativeZoneGuardPatch { private static bool Prefix(ConsoleEventArgs args) { return CreativeCommandZoneState.CanRunCommand(args); } } [HarmonyPatch(typeof(Skills), "RaiseSkill")] internal static class SkillsRaiseSkillCreativeZonePatch { private static bool Prefix(Skills __instance) { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)__instance != (Object)(object)((Character)localPlayer).GetSkills())) { return !CreativeCommandZoneState.IsLocalPlayerInsideActiveZone(); } return true; } } [HarmonyPatch(typeof(Player), "EdgeOfWorldKill")] internal static class PlayerEdgeOfWorldKillCreativePatch { private static bool Prefix(Player __instance) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null)) { return !CreativeBiomeOverride.ContainsTerrainOverride(((Component)__instance).transform.position); } return true; } } internal static class PlayerResolver { public static List FindPeers(string query) { List list = new List(); if ((Object)(object)ZNet.instance == (Object)null) { return list; } string text = Normalize(query); foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers()) { if (connectedPeer.IsReady()) { string value = SafeHostName(connectedPeer); string value2 = StablePlayerId(connectedPeer); if (Normalize(connectedPeer.m_playerName) == text || Normalize(value) == text || Normalize(value2) == text || (DigitsOnly(value) == DigitsOnly(query) && DigitsOnly(query).Length > 0)) { list.Add(connectedPeer); } } } if (list.Count > 0) { return list; } foreach (ZNetPeer connectedPeer2 in ZNet.instance.GetConnectedPeers()) { if (connectedPeer2.IsReady() && Normalize(connectedPeer2.m_playerName).Contains(text)) { list.Add(connectedPeer2); } } return list; } public static ZNetPeer? FindPeerBySender(long sender) { if ((Object)(object)ZNet.instance == (Object)null) { return null; } return ((IEnumerable)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func)((ZNetPeer peer) => peer.m_uid == sender)); } public static string DescribePeer(ZNetPeer peer) { return peer.m_playerName + " (" + StablePlayerId(peer) + ")"; } public static string StablePlayerId(ZNetPeer peer) { string text = SafeHostName(peer); if (!string.IsNullOrWhiteSpace(text)) { return text; } return peer.m_uid.ToString(CultureInfo.InvariantCulture); } public static string SafeHostName(ZNetPeer peer) { try { ISocket socket = peer.m_socket; return ((socket != null) ? socket.GetHostName() : null) ?? ""; } catch { return ""; } } public static string SafeEndPoint(ZNetPeer peer) { try { ISocket socket = peer.m_socket; return ((socket != null) ? socket.GetEndPointString() : null) ?? ""; } catch { return ""; } } public static string PlatformDisplayName(ZNetPeer peer) { try { string value; return (peer.m_serverSyncedPlayerData != null && peer.m_serverSyncedPlayerData.TryGetValue("platformDisplayName", out value)) ? value : ""; } catch { return ""; } } private static string Normalize(string value) { return (value ?? "").Trim().ToLowerInvariant(); } private static string DigitsOnly(string value) { return new string((value ?? "").Where(char.IsDigit).ToArray()); } } [BepInPlugin("warpalicious.PraetorisClient", "PraetorisClient", "0.1.31")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class PraetorisClientPlugin : BaseUnityPlugin { private const string ModName = "PraetorisClient"; private const string ModVersion = "0.1.31"; private const string Author = "warpalicious"; private const string ModGUID = "warpalicious.PraetorisClient"; private const string LinkApiUrlEnv = "PRAETORISCLIENT_LINK_API_URL"; private const string BotApiKeyEnv = "PRAETORISCLIENT_BOT_API_KEY"; private readonly Harmony _harmony = new Harmony("warpalicious.PraetorisClient"); private DateTime _lastReloadTime; private FileSystemWatcher? _configWatcher; private const long ReloadDelayTicks = 10000000L; public static readonly ManualLogSource Log = Logger.CreateLogSource("PraetorisClient"); internal static ConfigEntry LinkApiUrl = null; internal static ConfigEntry BotApiKey = null; internal static ConfigEntry LinkCommand = null; internal static ConfigEntry ValheimEventsTelemetryEnabled = null; internal static ConfigEntry CombatTelemetryEnabled = null; internal static ConfigEntry ExplorationTelemetryEnabled = null; internal static ConfigEntry ExplorationFlushSeconds = null; internal static ConfigEntry RpcTraceEnabled = null; internal static ConfigEntry RpcTraceCaptureSendReceive = null; internal static ConfigEntry RpcTraceNameDenyList = null; internal static ConfigEntry RpcTraceHttpUploadPreferred = null; internal static ConfigEntry ZdoTraceEnabled = null; internal static ConfigEntry ZdoTracePrefabFilter = null; internal static ConfigEntry ZdoTraceZdoIdFilter = null; internal static ConfigEntry ZdoTraceSampleRate = null; internal static ConfigEntry ZdoTraceMaxEventsPerSecond = null; internal static string TraceModGuid => "warpalicious.PraetorisClient"; internal static string TraceModName => "PraetorisClient"; internal static string TraceModVersion => "0.1.31"; public static PraetorisClientPlugin? Instance { get; private set; } internal static string GetLinkApiUrl() { string environmentVariable = Environment.GetEnvironmentVariable("PRAETORISCLIENT_LINK_API_URL"); if (!string.IsNullOrWhiteSpace(environmentVariable)) { return environmentVariable.Trim(); } return LinkApiUrl.Value; } internal static string GetBotApiKey() { string environmentVariable = Environment.GetEnvironmentVariable("PRAETORISCLIENT_BOT_API_KEY"); if (!string.IsNullOrWhiteSpace(environmentVariable)) { return environmentVariable.Trim(); } return BotApiKey.Value; } public void Awake() { Instance = this; BindConfig(); SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized; SiegePortalTestCommand.Register(); RpcTraceTelemetry.Initialize(); _harmony.PatchAll(Assembly.GetExecutingAssembly()); SetupWatcher(); } private void OnDestroy() { SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized; try { _configWatcher?.Dispose(); _configWatcher = null; } catch (Exception ex) { Log.LogWarning((object)("Failed to dispose configuration watcher: " + ex.Message)); } try { RpcTraceTelemetry.Shutdown(); } catch (Exception ex2) { Log.LogWarning((object)("Failed to shut down RPC trace telemetry: " + ex2.Message)); } try { ((BaseUnityPlugin)this).Config.Save(); } catch (Exception ex3) { Log.LogWarning((object)("Failed to save configuration during shutdown: " + ex3.Message)); } try { _harmony.UnpatchSelf(); } catch (Exception ex4) { Log.LogWarning((object)("Failed to unpatch PraetorisClient during shutdown: " + ex4.Message)); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void BindConfig() { LinkApiUrl = ((BaseUnityPlugin)this).Config.Bind("BotApi", "LinkApiUrl", "", "Compatible bot Valheim link endpoint. Prefer the PRAETORISCLIENT_LINK_API_URL environment variable on dedicated servers."); BotApiKey = ((BaseUnityPlugin)this).Config.Bind("BotApi", "ApiKey", "", "API key sent to the bot in the X-API-Key header. Prefer the PRAETORISCLIENT_BOT_API_KEY environment variable on dedicated servers."); LinkCommand = ((BaseUnityPlugin)this).Config.Bind("Linking", "LinkCommand", "!link", "In-game chat command consumed before it is sent as chat."); ValheimEventsTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind("ValheimEvents", "Enabled", true, SyncedDescription("Sends client-observed telemetry to the server-side ValheimEvents mod.")); CombatTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind("ValheimEvents", "CombatTelemetry", true, SyncedDescription("Sends client-observed combat and death telemetry.")); ExplorationTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind("ValheimEvents", "ExplorationTelemetry", true, SyncedDescription("Sends client-observed minimap exploration telemetry.")); ExplorationFlushSeconds = ((BaseUnityPlugin)this).Config.Bind("ValheimEvents", "ExplorationFlushSeconds", 2f, SyncedDescription("How long newly explored minimap cells are batched before sending.")); RpcTraceEnabled = ((BaseUnityPlugin)this).Config.Bind("RpcTrace", "Enabled", true, SyncedDescription("Sends client-observed routed RPC trace rows to the server-side ValheimTracer receiver.")); RpcTraceCaptureSendReceive = ((BaseUnityPlugin)this).Config.Bind("RpcTrace", "CaptureSendReceive", true, SyncedDescription("Captures raw routed RPC send and receive points in addition to handled RPC points.")); RpcTraceNameDenyList = ((BaseUnityPlugin)this).Config.Bind("RpcTrace", "RpcNameDenyList", "", SyncedDescription("Comma-separated routed RPC names to exclude from client trace capture.")); RpcTraceHttpUploadPreferred = ((BaseUnityPlugin)this).Config.Bind("RpcTrace", "HttpUploadPreferred", true, SyncedDescription("Uses ValheimTracer-issued HTTP upload tokens for trace batches when the server supports it.")); ZdoTraceEnabled = ((BaseUnityPlugin)this).Config.Bind("ZdoTrace", "Enabled", true, "Enables ZDOData package and selected ZDO revision tracing."); ZdoTracePrefabFilter = ((BaseUnityPlugin)this).Config.Bind("ZdoTrace", "PrefabFilter", "", "Comma-separated prefab names or prefab hashes to trace. Empty means no prefab filter."); ZdoTraceZdoIdFilter = ((BaseUnityPlugin)this).Config.Bind("ZdoTrace", "ZdoIdFilter", "", "Comma-separated ZDO ids to trace in user:id format. Empty means no ZDO id filter."); ZdoTraceSampleRate = ((BaseUnityPlugin)this).Config.Bind("ZdoTrace", "SampleRate", 1f, "Deterministic sample rate for ZDO revisions not matched by filters. 0 disables sampling, 1 captures all revisions."); ZdoTraceMaxEventsPerSecond = ((BaseUnityPlugin)this).Config.Bind("ZdoTrace", "MaxEventsPerSecond", 0, "Maximum non-forced ZDO trace events per second. Set to 0 for no limit."); } private static ConfigDescription SyncedDescription(string description) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; return new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { val }); } private static void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (args.UpdatedPluginGUIDs != null && args.UpdatedPluginGUIDs.Contains("warpalicious.PraetorisClient")) { string text = (args.InitialSynchronization ? "initial" : "updated"); Log.LogInfo((object)("Jotunn synchronized PraetorisClient configuration (" + text + ").")); } } private void SetupWatcher() { try { _lastReloadTime = DateTime.Now; _configWatcher?.Dispose(); _configWatcher = new FileSystemWatcher(Paths.ConfigPath, "warpalicious.PraetorisClient.cfg"); _configWatcher.Changed += ReadConfigValues; _configWatcher.Created += ReadConfigValues; _configWatcher.Renamed += ReadConfigValues; _configWatcher.IncludeSubdirectories = true; _configWatcher.EnableRaisingEvents = true; } catch (Exception ex) { Log.LogWarning((object)("Failed to start configuration watcher: " + ex.Message)); } } private void ReadConfigValues(object sender, FileSystemEventArgs e) { DateTime now = DateTime.Now; long num = now.Ticks - _lastReloadTime.Ticks; if (File.Exists(Path.Combine(Paths.ConfigPath, "warpalicious.PraetorisClient.cfg")) && num >= 10000000) { try { Log.LogInfo((object)"Reloading configuration."); ((BaseUnityPlugin)this).Config.Reload(); } catch (Exception ex) { Log.LogError((object)("Failed to reload configuration: " + ex.Message)); } _lastReloadTime = now; } } } internal static class PraetorisClientRpc { private static ZRoutedRpc? _registeredRpc; public static void Register() { if (ZRoutedRpc.instance != null && _registeredRpc != ZRoutedRpc.instance) { _registeredRpc = ZRoutedRpc.instance; ZRoutedRpc.instance.Register("DiscordTools_LinkRequest", (Action)LinkRpc.OnRequest); ZRoutedRpc.instance.Register("DiscordTools_LinkResult", (Action)LinkRpc.OnResult); ZRoutedRpc.instance.Register("DiscordTools_CreativeInventoryRequest", (Action)CreativeInventoryRpc.OnRequest); ZRoutedRpc.instance.Register("DiscordTools_CreativeBiomeOverride", (Action)CreativeBiomeOverride.OnOverride); ZRoutedRpc.instance.Register("PraetorisClient_CreativeCommandZoneState", (Action)CreativeCommandZoneState.OnState); ZRoutedRpc.instance.Register("PraetorisClient_RpcTraceClockResponse", (Action)RpcTraceTelemetry.OnClockResponse); ZRoutedRpc.instance.Register("PraetorisClient_RpcTraceUploadTokenResponse", (Action)RpcTraceUploadTokenClient.OnTokenResponse); PraetorisClientPlugin.Log.LogInfo((object)"Registered PraetorisClient RPC handlers."); } } } internal static class RpcNames { public const string LinkRequest = "DiscordTools_LinkRequest"; public const string LinkResult = "DiscordTools_LinkResult"; public const string CreativeInventoryRequest = "DiscordTools_CreativeInventoryRequest"; public const string CreativeInventoryResponse = "DiscordTools_CreativeInventoryResponse"; public const string CreativeBiomeOverride = "DiscordTools_CreativeBiomeOverride"; public const string CreativeCommandZoneState = "PraetorisClient_CreativeCommandZoneState"; public const string SiegePortalEnter = "DiscordTools_SiegePortalEnter"; public const string ValheimEventsTelemetry = "PraetorisClient_ValheimEventsTelemetry"; public const string RpcTraceClockRequest = "PraetorisClient_RpcTraceClockRequest"; public const string RpcTraceClockResponse = "PraetorisClient_RpcTraceClockResponse"; public const string RpcTraceUploadTokenRequest = "PraetorisClient_RpcTraceUploadTokenRequest"; public const string RpcTraceUploadTokenResponse = "PraetorisClient_RpcTraceUploadTokenResponse"; } internal static class RpcTraceFlushCoordinator { private const float UploadUnavailableLogIntervalSeconds = 30f; private static bool _allowQuit; private static float _nextUploadUnavailableLogTime; internal static void Initialize() { Application.wantsToQuit -= OnWantsToQuit; Application.wantsToQuit += OnWantsToQuit; _allowQuit = false; _nextUploadUnavailableLogTime = 0f; } internal static void Shutdown() { Application.wantsToQuit -= OnWantsToQuit; } internal static void RequestFlush(string reason) { if (RpcTraceTelemetry.IsTracingEnabled()) { if (RpcTraceHttpUploadCoordinator.CanAcceptFlushRequest()) { RpcTraceHttpUploadCoordinator.RequestFlush(reason); } else { LogHttpUnavailable(reason); } } } internal static void Update() { if (RpcTraceTelemetry.IsTracingEnabled() && !RpcTraceHttpUploadCoordinator.IsActive() && !RpcTraceHttpUploadCoordinator.CanAcceptFlushRequest() && RpcTraceLocalStore.HasPendingFiles()) { LogHttpUnavailable("background"); } } internal static bool ShouldAllowLogout(Game game, bool save, bool changeToStartScene) { if (!RpcTraceTelemetry.IsTracingEnabled()) { return true; } if (RpcTraceHttpUploadCoordinator.CanAcceptFlushRequest()) { RpcTraceHttpUploadCoordinator.RequestFlush("logout"); } else if (RpcTraceLocalStore.HasPendingFiles()) { LogHttpUnavailable("logout"); } RpcTraceTelemetry.SuppressCaptureUntilDisconnected(); return true; } internal static bool ShouldAllowMenuQuit() { if (!RpcTraceTelemetry.IsTracingEnabled()) { return true; } if (RpcTraceHttpUploadCoordinator.CanAcceptFlushRequest()) { RpcTraceHttpUploadCoordinator.RequestFlush("quit"); } else if (RpcTraceLocalStore.HasPendingFiles()) { LogHttpUnavailable("quit"); } RpcTraceTelemetry.DisableCaptureForShutdown(); return true; } private static bool OnWantsToQuit() { if (_allowQuit) { return true; } if (!RpcTraceTelemetry.IsTracingEnabled()) { return true; } if (RpcTraceHttpUploadCoordinator.CanAcceptFlushRequest()) { RpcTraceHttpUploadCoordinator.RequestFlush("quit"); } else if (RpcTraceLocalStore.HasPendingFiles()) { LogHttpUnavailable("quit"); } RpcTraceTelemetry.DisableCaptureForShutdown(); _allowQuit = true; return true; } private static void LogHttpUnavailable(string reason) { if (!(Time.realtimeSinceStartup < _nextUploadUnavailableLogTime)) { _nextUploadUnavailableLogTime = Time.realtimeSinceStartup + 30f; PraetorisClientPlugin.Log.LogWarning((object)("RPC trace HTTP upload is unavailable during " + (string.IsNullOrWhiteSpace(reason) ? "flush" : reason) + "; keeping local trace files for later HTTP retry.")); } } } internal static class RpcTraceHttpUploadContract { internal const string ContentType = "application/gzip"; internal const string UserAgentSuffix = "ValheimTracerHttpUpload"; internal static Dictionary BuildHeaders(string token, string batchId, string runtimeId, string fileId, int batchIndex, bool finalBatch, string flushReason, string modVersion) { return new Dictionary { { "Authorization", "Bearer " + token }, { "Content-Type", "application/gzip" }, { "X-Trace-Batch-Id", batchId }, { "X-Trace-Runtime-Id", runtimeId }, { "X-Trace-File-Id", fileId }, { "X-Trace-Batch-Index", batchIndex.ToString() }, { "X-Trace-Final-Batch", finalBatch ? "true" : "false" }, { "X-Trace-Flush-Reason", flushReason ?? "" }, { "User-Agent", BuildUserAgent(modVersion) } }; } internal static string BuildUserAgent(string modVersion) { string text = (string.IsNullOrWhiteSpace(modVersion) ? "unknown" : modVersion); return "PraetorisClient/" + text + " ValheimTracerHttpUpload"; } } internal static class RpcTraceHttpUploadCoordinator { private sealed class PreparedUploadBatch { internal byte[] Body { get; } internal int ConsumedRows { get; } internal bool FinalBatch { get; } internal bool EndOfFile { get; } internal bool SkippedOversizedRow { get; } internal double PreparationMilliseconds { get; } private PreparedUploadBatch(byte[] body, int consumedRows, bool finalBatch, bool endOfFile, bool skippedOversizedRow, double preparationMilliseconds) { Body = body; ConsumedRows = consumedRows; FinalBatch = finalBatch; EndOfFile = endOfFile; SkippedOversizedRow = skippedOversizedRow; PreparationMilliseconds = preparationMilliseconds; } internal static PreparedUploadBatch Ready(byte[] body, int consumedRows, bool finalBatch, double preparationMilliseconds) { return new PreparedUploadBatch(body, consumedRows, finalBatch, endOfFile: false, skippedOversizedRow: false, preparationMilliseconds); } internal static PreparedUploadBatch End() { return new PreparedUploadBatch(Array.Empty(), 0, finalBatch: true, endOfFile: true, skippedOversizedRow: false, 0.0); } internal static PreparedUploadBatch SkipOversizedRow() { return new PreparedUploadBatch(Array.Empty(), 0, finalBatch: false, endOfFile: false, skippedOversizedRow: true, 0.0); } } private sealed class UploadResult { internal bool Success { get; } internal long ResponseCode { get; } internal string Message { get; } internal UploadResult(bool success, long responseCode, string message) { Success = success; ResponseCode = responseCode; Message = message ?? ""; } } private const int MaxHttpRowsPerBatch = 1000; private const int FirstRetryRowsPerBatch = 250; private const int MinHttpRowsPerBatch = 100; private const float InitialFailureRetrySeconds = 15f; private const float MaxFailureRetrySeconds = 300f; private const float FailureLogIntervalSeconds = 60f; private const float UploadFrameSummaryIntervalSeconds = 30f; private const float UploadFrameWarningThresholdMs = 150f; private const float WorldReadyUploadDelaySeconds = 20f; private const int HttpUploadTimeoutMilliseconds = 30000; private static readonly object Sync = new object(); private static readonly Queue PendingFiles = new Queue(); private static bool _flushRequested; private static string _flushReason = "background"; private static bool _uploading; private static float _nextUploadTime; private static int _consecutiveFailures; private static int _maxRowsPerUploadBatch = 1000; private static float _nextFailureLogTime; private static float _worldReadyUploadTime; private static float _uploadFrameWindowActiveUntil; private static float _nextUploadFrameSummaryTime; private static int _uploadFrameSamples; private static int _longUploadFrames; private static float _maxUploadFrameMs; internal static void Initialize() { lock (Sync) { PendingFiles.Clear(); _flushRequested = false; _flushReason = "background"; _uploading = false; _nextUploadTime = 0f; _consecutiveFailures = 0; _maxRowsPerUploadBatch = 1000; _nextFailureLogTime = 0f; _worldReadyUploadTime = 0f; ResetUploadFrameStatsLocked(); } } internal static void Shutdown() { lock (Sync) { PendingFiles.Clear(); _uploading = false; _flushRequested = false; _consecutiveFailures = 0; _maxRowsPerUploadBatch = 1000; _nextFailureLogTime = 0f; _worldReadyUploadTime = 0f; ResetUploadFrameStatsLocked(); } } internal static bool IsActive() { if (!HasUploadConfiguration()) { _worldReadyUploadTime = 0f; return false; } return CanUpload(); } internal static bool CanAcceptFlushRequest() { if (!HasUploadConfiguration()) { _worldReadyUploadTime = 0f; return false; } return HasConnectedClient(); } internal static void RequestFlush(string reason) { lock (Sync) { _flushRequested = true; _flushReason = (string.IsNullOrWhiteSpace(reason) ? "manual" : reason); _nextUploadTime = 0f; } } internal static void Update() { RecordUploadFrame(); if (!IsActive()) { return; } lock (Sync) { if (_uploading || (!_flushRequested && Time.realtimeSinceStartup < _nextUploadTime)) { return; } } PrepareFilesIfNeeded(); StartNextUploadIfReady(); } private static void PrepareFilesIfNeeded() { lock (Sync) { if (PendingFiles.Count > 0 || _uploading) { return; } foreach (string flushableFile in RpcTraceLocalStore.GetFlushableFiles()) { if (new FileInfo(flushableFile).Length == 0L) { RpcTraceLocalStore.DeleteFile(flushableFile); } else { PendingFiles.Enqueue(flushableFile); } } _flushRequested = false; _nextUploadTime = Time.realtimeSinceStartup + RpcTraceUploadTokenClient.FlushIntervalSeconds; } } private static void StartNextUploadIfReady() { string path; string flushReason; lock (Sync) { if (_uploading || PendingFiles.Count == 0) { return; } path = PendingFiles.Dequeue(); flushReason = _flushReason; _uploading = true; _flushRequested = false; _uploadFrameWindowActiveUntil = Time.realtimeSinceStartup + 30f; } if ((Object)(object)PraetorisClientPlugin.Instance != (Object)null) { ((MonoBehaviour)PraetorisClientPlugin.Instance).StartCoroutine(UploadFile(path, flushReason)); } else { RequeueUpload(path); } } private static IEnumerator UploadFile(string path, string flushReason) { string fileId = RpcTraceLocalStore.BuildFileId(path); int startLine = 0; int batchIndex = 0; while (IsActive() && File.Exists(path)) { int maxRows = GetMaxRowsPerUploadBatch(); Task prepareTask = Task.Run(() => PrepareUploadBatch(path, startLine, maxRows)); while (!prepareTask.IsCompleted) { yield return null; } if (prepareTask.IsFaulted) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to prepare HTTP RPC trace batch for " + fileId + ": " + (prepareTask.Exception?.GetBaseException().Message ?? "unknown error"))); RequeueUpload(path); yield break; } PreparedUploadBatch batch = prepareTask.Result; if (batch.EndOfFile) { RpcTraceLocalStore.DeleteFile(path); CompleteUpload(success: true); yield break; } if (batch.SkippedOversizedRow) { PraetorisClientPlugin.Log.LogWarning((object)("Skipping oversized HTTP RPC trace row in " + fileId + ".")); int num = startLine; startLine = num + 1; continue; } string batchId = fileId + "-" + batchIndex.ToString("D6"); PraetorisClientPlugin.Log.LogInfo((object)("Prepared HTTP RPC trace batch " + batchId + ": rows=" + batch.ConsumedRows + ", gzipBytes=" + batch.Body.Length + ", final=" + batch.FinalBatch + ", prepareMs=" + batch.PreparationMilliseconds.ToString("F1", CultureInfo.InvariantCulture) + " off main thread.")); Dictionary headers = RpcTraceHttpUploadContract.BuildHeaders(RpcTraceUploadTokenClient.Token, batchId, RpcTraceTelemetry.RuntimeId, fileId, batchIndex, batch.FinalBatch, flushReason, PraetorisClientPlugin.TraceModVersion); Task uploadTask = Task.Run(() => SendHttpUpload(RpcTraceUploadTokenClient.EndpointUrl, headers, batch.Body)); while (!uploadTask.IsCompleted) { yield return null; } if (uploadTask.IsFaulted) { string message = uploadTask.Exception?.GetBaseException().Message ?? "unknown error"; RegisterUploadFailure(batchId, 0L, message); RequeueUpload(path); yield break; } UploadResult result = uploadTask.Result; if (!result.Success) { if (!RpcTraceUploadTokenClient.ShouldRetryUpload(result.ResponseCode, result.Message)) { CompleteUpload(success: false); yield break; } RegisterUploadFailure(batchId, result.ResponseCode, result.Message); RequeueUpload(path); yield break; } RegisterUploadSuccess(); startLine += batch.ConsumedRows; batchIndex++; if (!batch.FinalBatch) { continue; } PraetorisClientPlugin.Log.LogInfo((object)("Uploaded RPC trace file " + fileId + " over HTTP; deleting local copy.")); RpcTraceLocalStore.DeleteFile(path); CompleteUpload(success: true); yield break; } RequeueUpload(path); } private static UploadResult SendHttpUpload(string endpointUrl, Dictionary headers, byte[] body) { if (!TryNormalizeEndpointUrl(endpointUrl, out string normalizedEndpointUrl, out string error)) { return new UploadResult(success: false, 0L, error); } try { Uri uri = new Uri(normalizedEndpointUrl); int port = ((uri.Port > 0) ? uri.Port : (string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) ? 443 : 80)); using TcpClient tcpClient = new TcpClient(); tcpClient.SendTimeout = 30000; tcpClient.ReceiveTimeout = 30000; ConnectWithTimeout(tcpClient, uri.Host, port); using Stream stream = CreateRequestStream(tcpClient, uri); WriteHttpRequest(stream, uri, port, headers, body); return ReadHttpResponse(stream); } catch (IOException ex) { return new UploadResult(success: false, 0L, ex.Message); } catch (SocketException ex2) { return new UploadResult(success: false, 0L, ex2.Message); } catch (AuthenticationException ex3) { return new UploadResult(success: false, 0L, ex3.Message); } } private static void ConnectWithTimeout(TcpClient client, string host, int port) { IAsyncResult asyncResult = client.BeginConnect(host, port, null, null); try { if (!asyncResult.AsyncWaitHandle.WaitOne(30000)) { throw new IOException("Request timeout"); } client.EndConnect(asyncResult); } finally { asyncResult.AsyncWaitHandle.Close(); } } private static Stream CreateRequestStream(TcpClient client, Uri uri) { NetworkStream stream = client.GetStream(); if (!string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { return stream; } SslStream sslStream = new SslStream(stream, leaveInnerStreamOpen: false); sslStream.AuthenticateAsClient(uri.Host); return sslStream; } private static void WriteHttpRequest(Stream stream, Uri uri, int port, Dictionary headers, byte[] body) { StringBuilder stringBuilder = new StringBuilder(); string value = (string.IsNullOrWhiteSpace(uri.PathAndQuery) ? "/" : uri.PathAndQuery); stringBuilder.Append("POST ").Append(value).Append(" HTTP/1.1\r\n"); stringBuilder.Append("Host: ").Append(BuildHostHeader(uri, port)).Append("\r\n"); stringBuilder.Append("Connection: close\r\n"); stringBuilder.Append("Content-Length: ").Append(body.Length.ToString(CultureInfo.InvariantCulture)).Append("\r\n"); foreach (KeyValuePair header in headers) { if (!string.Equals(header.Key, "Host", StringComparison.OrdinalIgnoreCase) && !string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase) && !string.Equals(header.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { stringBuilder.Append(header.Key).Append(": ").Append(header.Value ?? "") .Append("\r\n"); } } stringBuilder.Append("\r\n"); byte[] bytes = Encoding.ASCII.GetBytes(stringBuilder.ToString()); stream.Write(bytes, 0, bytes.Length); stream.Write(body, 0, body.Length); stream.Flush(); } private static string BuildHostHeader(Uri uri, int port) { if ((!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) || port != 80) && (!string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) || port != 443)) { return uri.Host + ":" + port.ToString(CultureInfo.InvariantCulture); } return uri.Host; } private static UploadResult ReadHttpResponse(Stream stream) { using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; int count; while ((count = stream.Read(array, 0, array.Length)) > 0) { memoryStream.Write(array, 0, count); } string text = Encoding.UTF8.GetString(memoryStream.ToArray()); int num = text.IndexOf("\r\n\r\n", StringComparison.Ordinal); string obj = ((num >= 0) ? text.Substring(0, num) : text); string message = ((num >= 0) ? text.Substring(num + 4) : ""); string[] array2 = obj.Split(new string[1] { "\r\n" }, StringSplitOptions.None)[0].Split(new char[1] { ' ' }); if (array2.Length < 2 || !long.TryParse(array2[1], out var result)) { return new UploadResult(success: false, 0L, "Invalid HTTP response"); } return new UploadResult(result >= 200 && result < 300, result, message); } private static bool TryNormalizeEndpointUrl(string endpointUrl, out string normalizedEndpointUrl, out string error) { normalizedEndpointUrl = (endpointUrl ?? "").Trim(); error = ""; if (string.IsNullOrWhiteSpace(normalizedEndpointUrl)) { error = "Empty upload endpoint URL"; return false; } if (normalizedEndpointUrl.StartsWith("//", StringComparison.Ordinal)) { normalizedEndpointUrl = "http:" + normalizedEndpointUrl; } else if (!normalizedEndpointUrl.Contains("://")) { normalizedEndpointUrl = "http://" + normalizedEndpointUrl; } if (!Uri.TryCreate(normalizedEndpointUrl, UriKind.Absolute, out Uri result)) { error = "Invalid upload endpoint URL"; return false; } if (!string.Equals(result.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && !string.Equals(result.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { error = "Unsupported upload endpoint scheme"; return false; } normalizedEndpointUrl = result.AbsoluteUri; return true; } private static PreparedUploadBatch PrepareUploadBatch(string path, int startLine, int maxRows) { bool reachedEnd; List list = RpcTraceLocalStore.ReadBatch(path, startLine, maxRows, out reachedEnd); if (list.Count == 0 && reachedEnd) { return PreparedUploadBatch.End(); } Stopwatch stopwatch = Stopwatch.StartNew(); int num = Math.Max(4096, RpcTraceUploadTokenClient.MaxBatchBytes); while (list.Count > 0) { byte[] array = GzipRows(list); if (array.Length <= num) { stopwatch.Stop(); return PreparedUploadBatch.Ready(array, list.Count, reachedEnd, stopwatch.Elapsed.TotalMilliseconds); } if (list.Count == 1) { return PreparedUploadBatch.SkipOversizedRow(); } list.RemoveAt(list.Count - 1); } return PreparedUploadBatch.SkipOversizedRow(); } private static byte[] GzipRows(IReadOnlyList rows) { using MemoryStream memoryStream = new MemoryStream(); using (GZipStream stream = new GZipStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true)) { using StreamWriter streamWriter = new StreamWriter(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); foreach (string row in rows) { streamWriter.WriteLine(row); } } return memoryStream.ToArray(); } private static void RequeueUpload(string path) { lock (Sync) { if (File.Exists(path)) { PendingFiles.Enqueue(path); } _uploading = false; _flushRequested = false; _nextUploadTime = Time.realtimeSinceStartup + GetFailureRetryDelaySeconds(); _uploadFrameWindowActiveUntil = Time.realtimeSinceStartup + 2f; } } private static void CompleteUpload(bool success) { lock (Sync) { _uploading = false; if (success) { _consecutiveFailures = 0; } if (success && PendingFiles.Count > 0) { _nextUploadTime = 0f; } else { _nextUploadTime = Time.realtimeSinceStartup + (success ? RpcTraceUploadTokenClient.FlushIntervalSeconds : GetFailureRetryDelaySeconds()); } _uploadFrameWindowActiveUntil = Time.realtimeSinceStartup + 2f; } } private static void RegisterUploadFailure(string batchId, long responseCode, string message) { int maxRowsPerUploadBatch; float failureRetryDelaySeconds; bool flag; lock (Sync) { _consecutiveFailures++; AdjustBatchSizeAfterFailure(responseCode, message); maxRowsPerUploadBatch = _maxRowsPerUploadBatch; failureRetryDelaySeconds = GetFailureRetryDelaySeconds(); flag = Time.realtimeSinceStartup >= _nextFailureLogTime; if (flag) { _nextFailureLogTime = Time.realtimeSinceStartup + 60f; } } if (flag) { PraetorisClientPlugin.Log.LogWarning((object)("HTTP RPC trace upload failed for " + batchId + ": HTTP " + responseCode + " " + message + ". Retrying in " + Math.Ceiling(failureRetryDelaySeconds) + "s with maxRows=" + maxRowsPerUploadBatch + ".")); } } private static void RegisterUploadSuccess() { lock (Sync) { _consecutiveFailures = 0; _nextFailureLogTime = 0f; } } private static int GetMaxRowsPerUploadBatch() { lock (Sync) { return Math.Max(100, _maxRowsPerUploadBatch); } } private static void AdjustBatchSizeAfterFailure(long responseCode, string message) { if (IsReceiverTimeoutFailure(responseCode, message)) { if (_maxRowsPerUploadBatch > 250) { _maxRowsPerUploadBatch = 250; } else { _maxRowsPerUploadBatch = 100; } } } private static bool IsReceiverTimeoutFailure(long responseCode, string message) { if (responseCode == 504 || responseCode == 408) { return true; } return (message ?? "").IndexOf("timeout", StringComparison.OrdinalIgnoreCase) >= 0; } private static float GetFailureRetryDelaySeconds() { int num = Math.Max(1, _consecutiveFailures); double num2 = Math.Pow(2.0, Math.Min(5, num - 1)); return Math.Min(300f, 15f * (float)num2); } private static bool CanUpload() { if (!HasConnectedClient()) { _worldReadyUploadTime = 0f; return false; } if (_worldReadyUploadTime <= 0f) { _worldReadyUploadTime = Time.realtimeSinceStartup + 20f; } return Time.realtimeSinceStartup >= _worldReadyUploadTime; } private static bool HasUploadConfiguration() { if (PraetorisClientPlugin.RpcTraceHttpUploadPreferred.Value) { return RpcTraceUploadTokenClient.HasUsableToken(); } return false; } private static bool HasConnectedClient() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 if ((Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer() && (int)ZNet.GetConnectionStatus() == 2 && (Object)(object)Game.instance != (Object)null) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } private static void RecordUploadFrame() { bool flag = false; int num = 0; int num2 = 0; float num3 = 0f; float realtimeSinceStartup = Time.realtimeSinceStartup; float num4 = Time.unscaledDeltaTime * 1000f; lock (Sync) { if (!_uploading && realtimeSinceStartup > _uploadFrameWindowActiveUntil) { return; } _uploadFrameSamples++; if (num4 > _maxUploadFrameMs) { _maxUploadFrameMs = num4; } if (num4 >= 150f) { _longUploadFrames++; } if (_nextUploadFrameSummaryTime <= 0f) { _nextUploadFrameSummaryTime = realtimeSinceStartup + 30f; } if (realtimeSinceStartup >= _nextUploadFrameSummaryTime) { flag = _uploadFrameSamples > 0; num = _uploadFrameSamples; num2 = _longUploadFrames; num3 = _maxUploadFrameMs; ResetUploadFrameStatsLocked(); _nextUploadFrameSummaryTime = realtimeSinceStartup + 30f; } } if (flag) { PraetorisClientPlugin.Log.LogInfo((object)("HTTP RPC trace upload frame summary: samples=" + num + ", longFramesOver" + 150f.ToString("F0", CultureInfo.InvariantCulture) + "ms=" + num2 + ", maxFrameMs=" + num3.ToString("F1", CultureInfo.InvariantCulture) + ".")); } } private static void ResetUploadFrameStatsLocked() { _uploadFrameWindowActiveUntil = 0f; _nextUploadFrameSummaryTime = 0f; _uploadFrameSamples = 0; _longUploadFrames = 0; _maxUploadFrameMs = 0f; } } internal static class RpcTraceLocalStore { private const double FlushIntervalSeconds = 1.0; private const int FileBufferBytes = 65536; private static readonly object Sync = new object(); private static string _pendingDirectory = ""; private static string? _currentPath; private static StreamWriter? _writer; private static bool _hasUnflushedRows; private static double _nextFlushTime; internal static void Initialize() { _pendingDirectory = Path.Combine(Paths.BepInExRootPath, "logs", "PraetorisClient", "RpcTrace", "pending"); Directory.CreateDirectory(_pendingDirectory); } internal static void Append(string line, long localPeerId) { if (string.IsNullOrWhiteSpace(line)) { return; } lock (Sync) { EnsureWriterLocked(localPeerId); if (_writer != null) { _writer.WriteLine(line); _hasUnflushedRows = true; } } } internal static void FlushIfDue(double realtime) { lock (Sync) { if (_hasUnflushedRows && _writer != null && !(realtime < _nextFlushTime)) { FlushCurrentFileLocked(realtime); } } } internal static void CloseCurrentFile() { lock (Sync) { FlushCurrentFileLocked(0.0); _writer?.Dispose(); _writer = null; _currentPath = null; _hasUnflushedRows = false; } } internal static List GetFlushableFiles() { lock (Sync) { CloseCurrentFile(); List list = new List(Directory.GetFiles(_pendingDirectory, "*.jsonl")); list.Sort(StringComparer.Ordinal); return list; } } internal static List ReadBatch(string path, int startLine, int maxRows, out bool reachedEnd) { List list = new List(Math.Max(1, maxRows)); reachedEnd = true; using StreamReader streamReader = new StreamReader(path, Encoding.UTF8); int num = 0; while (true) { string text = streamReader.ReadLine(); if (text == null) { break; } if (num++ >= startLine) { if (list.Count >= maxRows) { reachedEnd = false; break; } if (text.Length > 0) { list.Add(text); } } } return list; } internal static void DeleteFile(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to delete uploaded RPC trace file " + path + ": " + ex.Message)); } } internal static bool HasPendingFiles() { lock (Sync) { if (_writer != null) { return true; } return Directory.Exists(_pendingDirectory) && Directory.GetFiles(_pendingDirectory, "*.jsonl").Length != 0; } } internal static string BuildFileId(string path) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); if (!string.IsNullOrEmpty(fileNameWithoutExtension)) { return fileNameWithoutExtension; } return Guid.NewGuid().ToString("N"); } private static void EnsureWriterLocked(long localPeerId) { if (_writer == null) { Directory.CreateDirectory(_pendingDirectory); long num = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); string text = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss_fff", CultureInfo.InvariantCulture); string path = "rpc_trace_" + text + "_world_" + num.ToString(CultureInfo.InvariantCulture) + "_peer_" + localPeerId.ToString(CultureInfo.InvariantCulture) + "_" + Guid.NewGuid().ToString("N") + ".jsonl"; _currentPath = Path.Combine(_pendingDirectory, path); _writer = new StreamWriter(new FileStream(_currentPath, FileMode.Append, FileAccess.Write, FileShare.Read, 65536, FileOptions.SequentialScan), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), 65536) { AutoFlush = false }; _hasUnflushedRows = false; _nextFlushTime = Time.realtimeSinceStartupAsDouble + 1.0; } } private static void FlushCurrentFileLocked(double realtime) { _writer?.Flush(); _hasUnflushedRows = false; _nextFlushTime = realtime + 1.0; } } [HarmonyPatch] internal static class RpcTraceRoutedRpcRegisterNamePatch { private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(ZRoutedRpc).GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "Register" && !methodInfo.IsGenericMethodDefinition && parameters.Length >= 1 && parameters[0].ParameterType == typeof(string)) { yield return methodInfo; } } } private static void Prefix(string name, Delegate f) { RpcTraceTelemetry.RegisterRpcName(name, f); } } [HarmonyPatch] internal static class RpcTraceZNetViewRegisterNamePatch { private static IEnumerable TargetMethods() { MethodInfo[] methods = typeof(ZNetView).GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { ParameterInfo[] parameters = methodInfo.GetParameters(); if (methodInfo.Name == "Register" && !methodInfo.IsGenericMethodDefinition && parameters.Length >= 1 && parameters[0].ParameterType == typeof(string)) { yield return methodInfo; } } } private static void Prefix(string name, Delegate f) { RpcTraceTelemetry.RegisterRpcName(name, f); } } [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(long), typeof(ZDOID), typeof(string), typeof(object[]) })] internal static class RpcTraceInvokeNamePatch { private static void Prefix(string methodName) { RpcTraceTelemetry.RegisterRpcName(methodName); } } [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(long), typeof(string), typeof(object[]) })] internal static class RpcTracePeerInvokeNamePatch { private static void Prefix(string methodName) { RpcTraceTelemetry.RegisterRpcName(methodName); } } [HarmonyPatch(typeof(ZRoutedRpc), "InvokeRoutedRPC", new Type[] { typeof(string), typeof(object[]) })] internal static class RpcTraceServerInvokeNamePatch { private static void Prefix(string methodName) { RpcTraceTelemetry.RegisterRpcName(methodName); } } [HarmonyPatch(typeof(ZRpc), "Invoke")] internal static class RpcTraceSendPatch { private static void Prefix(ZRpc __instance, string method, object[] parameters) { if (method == "ZDOData" && parameters != null && parameters.Length != 0) { object obj = parameters[0]; ZPackage val = (ZPackage)((obj is ZPackage) ? obj : null); if (val != null) { ZdoTraceTelemetry.TracePackageSend(__instance, val); return; } } if (!(method != "RoutedRPC") && parameters != null && parameters.Length != 0) { object obj2 = parameters[0]; ZPackage val2 = (ZPackage)((obj2 is ZPackage) ? obj2 : null); if (val2 != null) { RoutedRPCData data = RpcTraceTelemetry.TryReadRoutedRpcData(val2); RpcTraceTelemetry.TraceRoutedRpc("rpc_send", data, RpcTraceTelemetry.GetPeerIdForRpc(__instance)); } } } } [HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")] internal static class RpcTraceReceivePatch { private static void Prefix(ZPackage pkg) { RoutedRPCData data = RpcTraceTelemetry.TryReadRoutedRpcData(pkg); RpcTraceTelemetry.TraceRoutedRpc("rpc_receive", data, (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0); } } [HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")] internal static class RpcTraceHandlePatch { private static void Prefix(RoutedRPCData data) { RpcTraceTelemetry.TraceRoutedRpc("rpc_handle", data, (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0); } } [HarmonyPatch(typeof(ZDOMan), "RPC_ZDOData")] internal static class RpcTraceZdoDataReceivePatch { private static void Prefix(ZRpc __0, ZPackage __1) { ZdoTraceTelemetry.BeginReceive(__0, __1); } private static void Finalizer() { ZdoTraceTelemetry.EndReceive(); } } [HarmonyPatch(typeof(ZRpc), "HandlePackage")] internal static class RpcTraceZdoDataHandlePackagePatch { private static void Prefix(ZRpc __instance, ZPackage __0) { ZdoTraceTelemetry.BeginReceiveFromRpcPackage(__instance, __0); } private static void Finalizer() { ZdoTraceTelemetry.EndReceive(); } } [HarmonyPatch(typeof(ZDO), "Deserialize")] internal static class RpcTraceZdoDeserializePatch { private static void Postfix(ZDO __instance) { ZdoTraceTelemetry.OnDeserializeComplete(__instance); } } internal readonly struct RpcTracePlayerIdentity { internal string TracePlayerId { get; } internal string SteamId { get; } internal string PlatformUserId { get; } internal string PlayerName { get; } internal RpcTracePlayerIdentity(string tracePlayerId, string steamId, string platformUserId, string playerName) { TracePlayerId = tracePlayerId; SteamId = steamId; PlatformUserId = platformUserId; PlayerName = playerName; } internal unsafe static RpcTracePlayerIdentity Create(long localPeerId) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_0052: Unknown result type (might be due to invalid IL or missing references) string text = ""; string text2 = ""; string text3 = ""; try { PlatformUserID platformUserID = ((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID; text = ((object)(*(PlatformUserID*)(&platformUserID))/*cast due to .constrained prefix*/).ToString(); Platform platform = platformUserID.m_platform; if (string.Equals(((object)(*(Platform*)(&platform))/*cast due to .constrained prefix*/).ToString(), "Steam", StringComparison.OrdinalIgnoreCase)) { text2 = platformUserID.m_userID ?? ""; } } catch { } if (string.IsNullOrWhiteSpace(text2) && text.StartsWith("Steam_", StringComparison.OrdinalIgnoreCase)) { text2 = text.Substring("Steam_".Length); } try { if ((Object)(object)Player.m_localPlayer != (Object)null) { text3 = Player.m_localPlayer.GetPlayerName(); } } catch { } if (IsMissingPlayerName(text3)) { try { if ((Object)(object)Game.instance != (Object)null && Game.instance.GetPlayerProfile() != null) { text3 = Game.instance.GetPlayerProfile().GetName(); } } catch { } } return new RpcTracePlayerIdentity((!string.IsNullOrWhiteSpace(text2)) ? ("steam:" + text2) : ((!string.IsNullOrWhiteSpace(text)) ? text : ("peer:" + localPeerId)), text2, text, text3 ?? ""); } private static bool IsMissingPlayerName(string playerName) { if (!string.IsNullOrWhiteSpace(playerName)) { return string.Equals(playerName, "...", StringComparison.Ordinal); } return true; } } internal static class RpcTraceTelemetry { private readonly struct ClockSample { internal double OffsetMs { get; } internal double RoundTripMs { get; } internal double Realtime { get; } internal ClockSample(double offsetMs, double roundTripMs, double realtime) { OffsetMs = offsetMs; RoundTripMs = roundTripMs; Realtime = realtime; } } internal const int ProtocolVersion = 2; internal const string Schema = "valheim.trace.rpc.v1"; private const float ClockSyncIntervalSeconds = 10f; private const int ClockSampleWindow = 9; private const double MaxClockRoundTripMs = 2000.0; private const double MaxClockOffsetJumpMs = 250.0; private static readonly object Sync = new object(); private static readonly Dictionary RpcNamesByHash = new Dictionary(); private static readonly List ClockSamples = new List(); private static HashSet _denyList = new HashSet(StringComparer.OrdinalIgnoreCase); private static string _lastDenyListConfig = ""; private static float _nextClockSyncTime; private static long _sequence; private static int _clockSequence; private static double _lastServerMinusClientOffsetMs; private static double _selectedClockRoundTripMs; private static double _lastGoodClockSyncRealtime; private static int _validClockSampleCount; private static bool _hasClockOffset; private static bool _shutdownCapture; private static bool _suppressCaptureUntilDisconnected; private static bool _runtimeStartSubmitted; private static string _runtimeId = ""; internal static string RuntimeId => _runtimeId; internal static void Initialize() { _shutdownCapture = false; _suppressCaptureUntilDisconnected = false; _runtimeStartSubmitted = false; _runtimeId = TraceRuntimeMetadata.BuildRuntimeId("client"); RpcTraceLocalStore.Initialize(); RpcTraceUploadTokenClient.Initialize(); RpcTraceHttpUploadCoordinator.Initialize(); RpcTraceFlushCoordinator.Initialize(); } internal static void Shutdown() { _shutdownCapture = true; RpcTraceHttpUploadCoordinator.Shutdown(); RpcTraceFlushCoordinator.Shutdown(); RpcTraceLocalStore.CloseCurrentFile(); } internal static void DisableCaptureForShutdown() { _shutdownCapture = true; RpcTraceLocalStore.CloseCurrentFile(); } internal static void SuppressCaptureUntilDisconnected() { _suppressCaptureUntilDisconnected = true; RpcTraceLocalStore.CloseCurrentFile(); } internal static void RegisterRpcName(string methodName, Delegate? callback = null) { if (string.IsNullOrEmpty(methodName)) { return; } bool flag = false; lock (Sync) { int stableHashCode = StringExtensionMethods.GetStableHashCode(methodName); if (!RpcNamesByHash.TryGetValue(stableHashCode, out string value) || value != methodName) { RpcNamesByHash[stableHashCode] = methodName; flag = true; } } if (flag) { WriteObservedRpcMethod(methodName, callback); } } internal static void TraceRoutedRpc(string eventType, RoutedRPCData? data, long receiverPeerId) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) if (!CanCapture() || data == null || IsTraceRpc(data) || (!PraetorisClientPlugin.RpcTraceCaptureSendReceive.Value && eventType != "rpc_handle")) { return; } try { RefreshDenyList(); string rpcName = GetRpcName(data.m_methodHash); if (!IsDenied(rpcName)) { long localPeerId = GetLocalPeerId(); TelemetryJson telemetryJson = ObjectWithEnvelope(eventType, localPeerId); telemetryJson.Prop("role", "client"); telemetryJson.Prop("rpcTraceId", BuildRpcTraceId(data.m_senderPeerID, data.m_msgID)); telemetryJson.Prop("msgId", data.m_msgID); telemetryJson.Prop("senderPeerId", data.m_senderPeerID); telemetryJson.Prop("receiverPeerId", receiverPeerId); telemetryJson.Prop("targetPeerId", data.m_targetPeerID); telemetryJson.Prop("methodHash", data.m_methodHash); telemetryJson.Prop("rpcName", rpcName); telemetryJson.Prop("targetZdo", ClientZdoSnapshot.FormatId(data.m_targetZDO)); telemetryJson.Prop("payloadBytes", (data.m_parameters != null) ? data.m_parameters.Size() : 0); telemetryJson.Prop("serverMinusClientOffsetMs", _lastServerMinusClientOffsetMs); telemetryJson.Prop("clockRoundTripMs", _selectedClockRoundTripMs); telemetryJson.Prop("clockOffsetQuality", GetClockOffsetQuality()); telemetryJson.Prop("clockOffsetAgeMs", GetClockOffsetAgeMs()); telemetryJson.Prop("clockSampleCount", _validClockSampleCount); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to capture RPC trace row: " + ex.GetType().Name + ": " + ex.Message)); } } internal static RoutedRPCData? TryReadRoutedRpcData(ZPackage? package) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //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_001f: Expected O, but got Unknown if (package == null) { return null; } try { ZPackage val = new ZPackage(package.GetArray()); RoutedRPCData val2 = new RoutedRPCData(); val2.Deserialize(val); return val2; } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to read routed RPC trace package: " + ex.GetType().Name + ": " + ex.Message)); return null; } } internal static long GetPeerIdForRpc(ZRpc rpc) { if ((Object)(object)ZNet.instance == (Object)null) { return 0L; } foreach (ZNetPeer peer in ZNet.instance.GetPeers()) { if (peer != null && peer.m_rpc == rpc) { return peer.m_uid; } } if (!ZNet.instance.IsServer()) { ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_rpc == rpc) { return serverPeer.m_uid; } } return 0L; } internal static void Update() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (!IsTracingEnabled()) { RpcTraceLocalStore.CloseCurrentFile(); return; } if (_suppressCaptureUntilDisconnected && (int)ZNet.GetConnectionStatus() != 2) { _suppressCaptureUntilDisconnected = false; } MaybeWriteRuntimeStart(); MaybeSendClockSyncRequest(); RpcTraceLocalStore.FlushIfDue(Time.realtimeSinceStartupAsDouble); RpcTraceUploadTokenClient.Update(); RpcTraceHttpUploadCoordinator.Update(); RpcTraceFlushCoordinator.Update(); } internal static void OnClockResponse(long sender, ZPackage package) { if (!IsTracingEnabled()) { return; } long ticks = DateTime.UtcNow.Ticks; double realtimeSinceStartupAsDouble = Time.realtimeSinceStartupAsDouble; try { int num = package.ReadInt(); int value = package.ReadInt(); long value2 = package.ReadLong(); long value3 = package.ReadLong(); long num2 = package.ReadLong(); double num3 = package.ReadDouble(); long num4 = package.ReadLong(); double num5 = package.ReadDouble(); long num6 = package.ReadLong(); double num7 = package.ReadDouble(); if (num == 2) { double num8 = (realtimeSinceStartupAsDouble - num3) * 1000.0; double num9 = (num7 - num5) * 1000.0; double num10 = num8 - num9; double num11 = (TicksToMilliseconds(num4 - num2) + TicksToMilliseconds(num6 - ticks)) / 2.0; bool value4 = UpdateClockOffset(num11, num10, realtimeSinceStartupAsDouble); long localPeerId = GetLocalPeerId(); TelemetryJson telemetryJson = ObjectWithEnvelope("clock_sync_sample", localPeerId); telemetryJson.Prop("role", "client"); telemetryJson.Prop("senderPeerId", sender); telemetryJson.Prop("clientPeerId", value2); telemetryJson.Prop("serverPeerId", value3); telemetryJson.Prop("clockSequence", value); telemetryJson.Prop("clientSendUtcTicks", num2); telemetryJson.Prop("serverReceiveUtcTicks", num4); telemetryJson.Prop("serverSendUtcTicks", num6); telemetryJson.Prop("clientReceiveUtcTicks", ticks); telemetryJson.Prop("clientSendRealtime", num3); telemetryJson.Prop("serverReceiveRealtime", num5); telemetryJson.Prop("serverSendRealtime", num7); telemetryJson.Prop("clientReceiveRealtime", realtimeSinceStartupAsDouble); telemetryJson.Prop("roundTripMs", num10); telemetryJson.Prop("serverProcessingMs", num9); telemetryJson.Prop("sampleServerMinusClientOffsetMs", num11); telemetryJson.Prop("serverMinusClientOffsetMs", _lastServerMinusClientOffsetMs); telemetryJson.Prop("clockSampleAccepted", value4); telemetryJson.Prop("clockOffsetQuality", GetClockOffsetQuality()); telemetryJson.Prop("clockOffsetAgeMs", GetClockOffsetAgeMs()); telemetryJson.Prop("clockSampleCount", _validClockSampleCount); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to process RPC trace clock response: " + ex.GetType().Name + ": " + ex.Message)); } } internal static bool ShouldAllowLogout(Game game, bool save, bool changeToStartScene) { return RpcTraceFlushCoordinator.ShouldAllowLogout(game, save, changeToStartScene); } internal static bool ShouldAllowMenuQuit() { return RpcTraceFlushCoordinator.ShouldAllowMenuQuit(); } internal static void OnApplicationQuitFallback() { RpcTraceFlushCoordinator.RequestFlush("application_quit"); } internal static bool IsTracingEnabled() { return PraetorisClientPlugin.RpcTraceEnabled.Value; } internal static TelemetryJson ObjectWithEnvelope(string eventType, long localPeerId) { DateTime utcNow = DateTime.UtcNow; long value = ++_sequence; RpcTracePlayerIdentity rpcTracePlayerIdentity = RpcTracePlayerIdentity.Create(localPeerId); TelemetryJson telemetryJson = TelemetryJson.Object(); telemetryJson.Prop("schema", "valheim.trace.rpc.v1"); telemetryJson.Prop("eventType", eventType); telemetryJson.Prop("traceId", "client:" + localPeerId.ToString(CultureInfo.InvariantCulture) + ":" + value.ToString(CultureInfo.InvariantCulture)); telemetryJson.Prop("tracePlayerId", rpcTracePlayerIdentity.TracePlayerId); telemetryJson.Prop("steamId", rpcTracePlayerIdentity.SteamId); telemetryJson.Prop("platformUserId", rpcTracePlayerIdentity.PlatformUserId); telemetryJson.Prop("playerName", rpcTracePlayerIdentity.PlayerName); telemetryJson.Prop("sequence", value); telemetryJson.Prop("localPeerId", localPeerId); telemetryJson.Prop("timeUtc", utcNow.ToString("o", CultureInfo.InvariantCulture)); telemetryJson.Prop("timeUtcTicks", utcNow.Ticks); telemetryJson.Prop("realtime", Time.realtimeSinceStartupAsDouble); telemetryJson.Prop("worldName", (ZNet.m_world != null) ? ZNet.m_world.m_name : ""); telemetryJson.Prop("worldUid", (ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); return telemetryJson; } private static void WriteObservedRpcMethod(string rpcName, Delegate? callback) { if (!IsTracingEnabled() || _shutdownCapture) { return; } try { long localPeerId = GetLocalPeerId(); TraceMethodSource methodSource = TraceRuntimeMetadata.GetMethodSource(callback); TelemetryJson telemetryJson = ObjectWithEnvelope("rpc_method_observed", localPeerId); telemetryJson.Prop("role", "client"); telemetryJson.Prop("runtimeId", _runtimeId); telemetryJson.Prop("methodHash", StringExtensionMethods.GetStableHashCode(rpcName)); telemetryJson.Prop("rpcName", rpcName); telemetryJson.Prop("gameVersion", TraceRuntimeMetadata.GetGameVersion()); telemetryJson.Prop("pluginGuid", methodSource.PluginGuid); telemetryJson.Prop("pluginName", methodSource.PluginName); telemetryJson.Prop("pluginVersion", methodSource.PluginVersion); telemetryJson.Prop("assemblyName", methodSource.AssemblyName); telemetryJson.Prop("isVanilla", methodSource.IsVanilla); telemetryJson.Prop("isModded", methodSource.IsModded); telemetryJson.Prop("sourceKind", "registered"); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to capture RPC method registration: " + ex.GetType().Name + ": " + ex.Message)); } } private static void MaybeWriteRuntimeStart() { if (!_runtimeStartSubmitted && CanCapture()) { _runtimeStartSubmitted = true; long localPeerId = GetLocalPeerId(); TelemetryJson telemetryJson = ObjectWithEnvelope("runtime_start", localPeerId); telemetryJson.Prop("role", "client"); telemetryJson.Prop("runtimeId", _runtimeId); telemetryJson.Prop("gameVersion", TraceRuntimeMetadata.GetGameVersion()); telemetryJson.Prop("traceSource", "PraetorisClient"); telemetryJson.Prop("traceModGuid", PraetorisClientPlugin.TraceModGuid); telemetryJson.Prop("traceModName", PraetorisClientPlugin.TraceModName); telemetryJson.Prop("traceModVersion", PraetorisClientPlugin.TraceModVersion); TraceRuntimeMetadata.WritePlugins(telemetryJson); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); } } private static bool CanCapture() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 if (IsTracingEnabled() && !_shutdownCapture && !_suppressCaptureUntilDisconnected && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } internal static bool CanCaptureZdoTrace() { if (IsTracingEnabled() && !_shutdownCapture && !_suppressCaptureUntilDisconnected && (Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsServer(); } return false; } internal static void AddClockFields(TelemetryJson json) { json.Prop("serverMinusClientOffsetMs", _lastServerMinusClientOffsetMs); json.Prop("clockRoundTripMs", _selectedClockRoundTripMs); json.Prop("clockOffsetQuality", GetClockOffsetQuality()); json.Prop("clockOffsetAgeMs", GetClockOffsetAgeMs()); json.Prop("clockSampleCount", _validClockSampleCount); } private static void MaybeSendClockSyncRequest() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown if (CanCapture() && !(Time.realtimeSinceStartup < _nextClockSyncTime) && ZRoutedRpc.instance != null) { _nextClockSyncTime = Time.realtimeSinceStartup + 10f; int num = ++_clockSequence; ZPackage val = new ZPackage(); val.Write(2); val.Write(num); val.Write(GetLocalPeerId()); val.Write(DateTime.UtcNow.Ticks); val.Write(Time.realtimeSinceStartupAsDouble); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_RpcTraceClockRequest", new object[1] { val }); } } private static bool IsTraceRpc(RoutedRPCData data) { if (data.m_methodHash != StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceClockRequest") && data.m_methodHash != StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceClockResponse") && data.m_methodHash != StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceUploadTokenRequest")) { return data.m_methodHash == StringExtensionMethods.GetStableHashCode("PraetorisClient_RpcTraceUploadTokenResponse"); } return true; } private static string GetRpcName(int methodHash) { lock (Sync) { string value; return RpcNamesByHash.TryGetValue(methodHash, out value) ? value : ""; } } private static bool IsDenied(string rpcName) { if (!string.IsNullOrEmpty(rpcName)) { return _denyList.Contains(rpcName); } return false; } private static void RefreshDenyList() { string text = PraetorisClientPlugin.RpcTraceNameDenyList.Value ?? ""; if (string.Equals(text, _lastDenyListConfig, StringComparison.Ordinal)) { return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length > 0) { hashSet.Add(text2); } } _denyList = hashSet; _lastDenyListConfig = text; } internal static long GetLocalPeerId() { try { return (ZDOMan.instance != null) ? ZDOMan.GetSessionID() : 0; } catch { return 0L; } } private static double TicksToMilliseconds(long ticks) { return (double)ticks / 10000.0; } private static string BuildRpcTraceId(long senderPeerId, long msgId) { long num = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); return num.ToString(CultureInfo.InvariantCulture) + ":" + senderPeerId.ToString(CultureInfo.InvariantCulture) + ":" + msgId.ToString(CultureInfo.InvariantCulture); } private static bool UpdateClockOffset(double offsetMs, double roundTripMs, double realtime) { if (roundTripMs < 0.0 || roundTripMs > 2000.0 || double.IsNaN(offsetMs) || double.IsInfinity(offsetMs)) { return false; } if (ClockSamples.Count >= 3) { double medianOffset = GetMedianOffset(); if (Math.Abs(offsetMs - medianOffset) > 250.0) { return false; } } ClockSamples.Add(new ClockSample(offsetMs, roundTripMs, realtime)); while (ClockSamples.Count > 9) { ClockSamples.RemoveAt(0); } List list = new List(ClockSamples); list.Sort((ClockSample left, ClockSample right) => left.RoundTripMs.CompareTo(right.RoundTripMs)); int num = Math.Min(5, list.Count); double num2 = 0.0; for (int num3 = 0; num3 < num; num3++) { num2 += list[num3].OffsetMs; } _lastServerMinusClientOffsetMs = num2 / (double)num; _selectedClockRoundTripMs = list[0].RoundTripMs; _lastGoodClockSyncRealtime = realtime; _validClockSampleCount++; _hasClockOffset = true; return true; } private static double GetMedianOffset() { List list = new List(ClockSamples.Count); foreach (ClockSample clockSample in ClockSamples) { list.Add(clockSample.OffsetMs); } list.Sort(); int num = list.Count / 2; if (list.Count % 2 == 1) { return list[num]; } return (list[num - 1] + list[num]) / 2.0; } private static string GetClockOffsetQuality() { if (!_hasClockOffset) { return "none"; } if (GetClockOffsetAgeMs() > 30000.0) { return "stale"; } if (ClockSamples.Count < 3) { return "warming"; } return "good"; } private static double GetClockOffsetAgeMs() { if (!_hasClockOffset) { return 0.0; } return Math.Max(0.0, (Time.realtimeSinceStartupAsDouble - _lastGoodClockSyncRealtime) * 1000.0); } } internal static class RpcTraceUploadRetryPolicy { internal static bool ShouldRetryUpload(long responseCode, string responseText) { return !IsPermanentReceiverConfigurationFailure(responseCode, responseText); } internal static bool IsPermanentReceiverConfigurationFailure(long responseCode, string responseText) { string text = responseText ?? ""; if (responseCode != 403 && text.IndexOf("missing relay key", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("missing token signing secret", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("unauthorized relay", StringComparison.OrdinalIgnoreCase) < 0) { return text.IndexOf("invalid token", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } } internal static class RpcTraceUploadTokenClient { private const float RequestRetrySeconds = 30f; private const float ConfigurationRetrySeconds = 300f; private const long RefreshBeforeExpirySeconds = 60L; private static string _sessionId = ""; private static float _nextRequestTime; private static float _requestDeadlineTime; private static bool _requestPending; internal static bool UploadEnabled { get; private set; } internal static string EndpointUrl { get; private set; } = ""; internal static string Token { get; private set; } = ""; internal static int MaxBatchBytes { get; private set; } = 131072; internal static float FlushIntervalSeconds { get; private set; } = 10f; internal static long TokenExpiresUnixSeconds { get; private set; } internal static string SessionId { get { EnsureSessionId(); return _sessionId; } } internal static void Initialize() { EnsureSessionId(); UploadEnabled = false; EndpointUrl = ""; Token = ""; TokenExpiresUnixSeconds = 0L; _requestPending = false; _nextRequestTime = 0f; } internal static void Update() { if (!PraetorisClientPlugin.RpcTraceHttpUploadPreferred.Value || !RpcTraceTelemetry.IsTracingEnabled()) { return; } if (!CanRequestToken()) { if (_requestPending && Time.realtimeSinceStartup > _requestDeadlineTime) { _requestPending = false; } } else if (!HasUsableToken() && !(Time.realtimeSinceStartup < _nextRequestTime)) { RequestToken(); } } internal static bool HasUsableToken() { long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (UploadEnabled && !string.IsNullOrWhiteSpace(EndpointUrl) && !string.IsNullOrWhiteSpace(Token)) { return TokenExpiresUnixSeconds - num > 60; } return false; } internal static bool ShouldRetryUpload(long responseCode, string responseText) { if (RpcTraceUploadRetryPolicy.ShouldRetryUpload(responseCode, responseText)) { return true; } UploadEnabled = false; EndpointUrl = ""; Token = ""; TokenExpiresUnixSeconds = 0L; _requestPending = false; _nextRequestTime = Time.realtimeSinceStartup + 300f; PraetorisClientPlugin.Log.LogWarning((object)("HTTP RPC trace upload rejected by receiver configuration: " + (string.IsNullOrWhiteSpace(responseText) ? ("HTTP " + responseCode) : responseText) + ". Keeping local trace files and pausing token requests.")); return false; } internal static void OnTokenResponse(long sender, ZPackage package) { try { int num = package.ReadInt(); bool flag = package.ReadBool(); string text = package.ReadString(); string endpointUrl = package.ReadString(); string token = package.ReadString(); string text2 = package.ReadString(); int val = package.ReadInt(); float val2 = package.ReadSingle(); long tokenExpiresUnixSeconds = package.ReadLong(); if (num != 2) { return; } _requestPending = false; _nextRequestTime = Time.realtimeSinceStartup + 30f; if (!flag) { UploadEnabled = false; EndpointUrl = ""; Token = ""; TokenExpiresUnixSeconds = 0L; if (!string.IsNullOrWhiteSpace(text)) { PraetorisClientPlugin.Log.LogInfo((object)("HTTP RPC trace upload unavailable: " + text)); } return; } if (!string.IsNullOrWhiteSpace(text2)) { _sessionId = text2; } UploadEnabled = true; EndpointUrl = endpointUrl; Token = token; MaxBatchBytes = Math.Max(4096, val); FlushIntervalSeconds = Math.Max(1f, val2); TokenExpiresUnixSeconds = tokenExpiresUnixSeconds; PraetorisClientPlugin.Log.LogInfo((object)("Received HTTP RPC trace upload token for session " + _sessionId + " expiring at " + tokenExpiresUnixSeconds.ToString(CultureInfo.InvariantCulture) + ".")); } catch (Exception ex) { _requestPending = false; PraetorisClientPlugin.Log.LogWarning((object)$"Failed to process RPC trace upload token response from peer {sender}: {ex.Message}"); } } private static void RequestToken() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (ZRoutedRpc.instance == null) { return; } try { EnsureSessionId(); RpcTracePlayerIdentity rpcTracePlayerIdentity = RpcTracePlayerIdentity.Create(RpcTraceTelemetry.GetLocalPeerId()); ZPackage val = new ZPackage(); val.Write(2); val.Write(_sessionId); val.Write(rpcTracePlayerIdentity.TracePlayerId); val.Write(rpcTracePlayerIdentity.SteamId); val.Write(rpcTracePlayerIdentity.PlatformUserId); val.Write(rpcTracePlayerIdentity.PlayerName); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_RpcTraceUploadTokenRequest", new object[1] { val }); _requestPending = true; _nextRequestTime = Time.realtimeSinceStartup + 30f; _requestDeadlineTime = Time.realtimeSinceStartup + 30f; } catch (Exception ex) { _requestPending = false; _nextRequestTime = Time.realtimeSinceStartup + 30f; PraetorisClientPlugin.Log.LogWarning((object)("Failed to request RPC trace upload token: " + ex.Message)); } } private static bool CanRequestToken() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (!_requestPending && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } private static void EnsureSessionId() { if (string.IsNullOrWhiteSpace(_sessionId)) { _sessionId = Guid.NewGuid().ToString("N"); } } } internal static class SiegePortalBridge { private const int ProtocolVersion = 1; internal const string SiegeIdZdoKey = "valheimCreativeSiegeId"; internal const string SiegeTagPrefix = "siege:"; internal static bool TryHandle(TeleportWorld portal, Player player) { //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || ZRoutedRpc.instance == null) { return false; } if ((Object)(object)player == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return false; } ZNetView component = ((Component)portal).GetComponent(); ZDO val = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val == null || !TryGetSiegeId(val, out string siegeId)) { return false; } if ((Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsValid()) { ((Character)player).Message((MessageType)2, "Siege portal failed: character was not ready.", 0, (Sprite)null); return true; } ZDO zDO = ((Character)player).m_nview.GetZDO(); if (zDO == null) { ((Character)player).Message((MessageType)2, "Siege portal failed: character was not ready.", 0, (Sprite)null); return true; } ZPackage val2 = new ZPackage(); val2.Write(1); val2.Write(zDO.m_uid); val2.Write(siegeId); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "DiscordTools_SiegePortalEnter", new object[1] { val2 }); ((Character)player).Message((MessageType)2, "Entering siege: " + siegeId, 0, (Sprite)null); PraetorisClientPlugin.Log.LogInfo((object)("Requested siege portal enter for " + siegeId + ".")); return true; } private static bool TryGetSiegeId(ZDO portalZdo, out string siegeId) { siegeId = portalZdo.GetString("valheimCreativeSiegeId", "").Trim(); if (!string.IsNullOrWhiteSpace(siegeId)) { return true; } string text = portalZdo.GetString(ZDOVars.s_tag, "").Trim(); if (text.StartsWith("siege:", StringComparison.OrdinalIgnoreCase)) { siegeId = text.Substring("siege:".Length).Trim(); return !string.IsNullOrWhiteSpace(siegeId); } return false; } } [HarmonyPatch(typeof(TeleportWorld), "Teleport")] internal static class TeleportWorldSiegePortalPatch { private static bool Prefix(TeleportWorld __instance, Player player) { return !SiegePortalBridge.TryHandle(__instance, player); } } internal static class SiegePortalTestCommand { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; public static ConsoleEvent <>9__1_1; internal void b__1_0(ConsoleEventArgs args) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2) { args.Context.AddString("Usage: dt_mark_siege_portal [radius]"); return; } string text = args[1].Trim(); if (string.IsNullOrWhiteSpace(text)) { args.Context.AddString("siegeId is required."); return; } float result = 12f; if (args.Length >= 3) { float.TryParse(args[2], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); return; } float distance; TeleportWorld val = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); return; } ZNetView component = ((Component)val).GetComponent(); ZDO val2 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val2 == null) { args.Context.AddString("Nearest portal has no valid ZDO."); return; } val2.Set("valheimCreativeSiegeId", text); val2.Set(ZDOVars.s_tag, "siege:" + text); args.Context.AddString($"Marked nearest portal {val2.m_uid} as siege {text} at {distance:0.#}m."); } internal void b__1_1(ConsoleEventArgs args) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) float result = 12f; if (args.Length >= 2) { float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); return; } float distance; TeleportWorld val = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); return; } bool flag = SiegePortalBridge.TryHandle(val, localPlayer); args.Context.AddString($"Nearest siege portal handled={flag} distance={distance:0.#}m."); } } private static bool _registered; internal static void Register() { //IL_0040: 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) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown //IL_0078: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown if (_registered) { return; } _registered = true; object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (args.Length < 2) { args.Context.AddString("Usage: dt_mark_siege_portal [radius]"); } else { string text = args[1].Trim(); if (string.IsNullOrWhiteSpace(text)) { args.Context.AddString("siegeId is required."); } else { float result = 12f; if (args.Length >= 3) { float.TryParse(args[2], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); } else { float distance; TeleportWorld val3 = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val3 == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); } else { ZNetView component = ((Component)val3).GetComponent(); ZDO val4 = (((Object)(object)component != (Object)null && component.IsValid()) ? component.GetZDO() : null); if (val4 == null) { args.Context.AddString("Nearest portal has no valid ZDO."); } else { val4.Set("valheimCreativeSiegeId", text); val4.Set(ZDOVars.s_tag, "siege:" + text); args.Context.AddString($"Marked nearest portal {val4.m_uid} as siege {text} at {distance:0.#}m."); } } } } } }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("dt_mark_siege_portal", "Mark the nearest portal as a valheimCreative siege portal. Usage: dt_mark_siege_portal [radius]", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__1_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) float result = 12f; if (args.Length >= 2) { float.TryParse(args[1], NumberStyles.Float, CultureInfo.InvariantCulture, out result); } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { args.Context.AddString("No local player found."); } else { float distance; TeleportWorld val3 = FindNearestPortal(((Component)localPlayer).transform.position, Mathf.Clamp(result, 1f, 50f), out distance); if ((Object)(object)val3 == (Object)null) { args.Context.AddString($"No portal found within {result:0.#}m."); } else { bool flag = SiegePortalBridge.TryHandle(val3, localPlayer); args.Context.AddString($"Nearest siege portal handled={flag} distance={distance:0.#}m."); } } }; <>c.<>9__1_1 = val2; obj2 = (object)val2; } new ConsoleCommand("dt_enter_nearest_siege_portal", "Enter the nearest marked valheimCreative siege portal. Usage: dt_enter_nearest_siege_portal [radius]", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static TeleportWorld? FindNearestPortal(Vector3 center, float radius, out float distance) { //IL_0009: 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_002e: Unknown result type (might be due to invalid IL or missing references) distance = float.MaxValue; TeleportWorld result = null; Collider[] array = Physics.OverlapSphere(center, radius); for (int i = 0; i < array.Length; i++) { TeleportWorld componentInParent = ((Component)array[i]).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null)) { float num = Vector3.Distance(center, ((Component)componentInParent).transform.position); if (num < distance) { distance = num; result = componentInParent; } } } return result; } } internal sealed class TelemetryJson { private readonly StringBuilder _builder = new StringBuilder(); private readonly Stack _needsComma = new Stack(); private TelemetryJson() { } internal static TelemetryJson Object() { TelemetryJson telemetryJson = new TelemetryJson(); telemetryJson._builder.Append('{'); telemetryJson._needsComma.Push(item: false); return telemetryJson; } internal void End() { _builder.Append('}'); if (_needsComma.Count > 0) { _needsComma.Pop(); } } internal void BeginObject(string name) { Name(name); _builder.Append('{'); _needsComma.Push(item: false); } internal void EndObject() { End(); } internal void BeginArray(string name) { Name(name); _builder.Append('['); _needsComma.Push(item: false); } internal void EndArray() { _builder.Append(']'); if (_needsComma.Count > 0) { _needsComma.Pop(); } } internal void BeginArrayObject() { ArrayPrefix(); _builder.Append('{'); _needsComma.Push(item: false); } internal void EndArrayObject() { End(); } internal void ArrayString(string value) { ArrayPrefix(); WriteString(value); } internal void Prop(string name, string value) { Name(name); WriteString(value); } internal void Prop(string name, bool value) { Name(name); _builder.Append(value ? "true" : "false"); } internal void Prop(string name, int value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, uint value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, short value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, ushort value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, long value) { Name(name); _builder.Append(value.ToString(CultureInfo.InvariantCulture)); } internal void Prop(string name, float value) { Name(name); if (float.IsNaN(value) || float.IsInfinity(value)) { _builder.Append("null"); } else { _builder.Append(value.ToString("R", CultureInfo.InvariantCulture)); } } internal void Prop(string name, double value) { Name(name); if (double.IsNaN(value) || double.IsInfinity(value)) { _builder.Append("null"); } else { _builder.Append(value.ToString("R", CultureInfo.InvariantCulture)); } } internal void Prop(string name, Vector3 value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) BeginObject(name); Prop("x", value.x); Prop("y", value.y); Prop("z", value.z); EndObject(); } internal void Prop(string name, Quaternion value) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) BeginObject(name); Prop("x", value.x); Prop("y", value.y); Prop("z", value.z); Prop("w", value.w); EndObject(); } public override string ToString() { return _builder.ToString(); } private void Name(string name) { if (_needsComma.Count == 0) { throw new InvalidOperationException("JSON object is not open."); } if (_needsComma.Peek()) { _builder.Append(','); } _needsComma.Pop(); _needsComma.Push(item: true); WriteString(name); _builder.Append(':'); } private void ArrayPrefix() { if (_needsComma.Count == 0) { throw new InvalidOperationException("JSON array is not open."); } if (_needsComma.Peek()) { _builder.Append(','); } _needsComma.Pop(); _needsComma.Push(item: true); } private void WriteString(string value) { _builder.Append('"'); string text = value ?? ""; foreach (char c in text) { switch (c) { case '"': _builder.Append("\\\""); continue; case '\\': _builder.Append("\\\\"); continue; case '\b': _builder.Append("\\b"); continue; case '\f': _builder.Append("\\f"); continue; case '\n': _builder.Append("\\n"); continue; case '\r': _builder.Append("\\r"); continue; case '\t': _builder.Append("\\t"); continue; } if (char.IsControl(c)) { StringBuilder stringBuilder = _builder.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { _builder.Append(c); } } _builder.Append('"'); } } internal readonly struct TraceMethodSource { internal string PluginGuid { get; } internal string PluginName { get; } internal string PluginVersion { get; } internal string AssemblyName { get; } internal bool IsVanilla { get; } internal bool IsModded { get; } internal TraceMethodSource(string pluginGuid, string pluginName, string pluginVersion, string assemblyName, bool isVanilla, bool isModded) { PluginGuid = pluginGuid; PluginName = pluginName; PluginVersion = pluginVersion; AssemblyName = assemblyName; IsVanilla = isVanilla; IsModded = isModded; } } internal static class TraceRuntimeMetadata { private static readonly TraceMethodSource UnknownSource = new TraceMethodSource("", "", "", "", isVanilla: false, isModded: false); internal static string GetGameVersion() { try { return Version.GetVersionString(false); } catch { return Application.version ?? ""; } } internal static string BuildRuntimeId(string role) { return role + ":" + DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + ":" + Guid.NewGuid().ToString("N"); } internal static void WritePlugins(TelemetryJson json) { List runtimePlugins = GetRuntimePlugins(); json.BeginArray("plugins"); foreach (TraceMethodSource item in runtimePlugins) { json.BeginArrayObject(); json.Prop("pluginGuid", item.PluginGuid); json.Prop("pluginName", item.PluginName); json.Prop("pluginVersion", item.PluginVersion); json.Prop("assemblyName", item.AssemblyName); json.Prop("isVanilla", item.IsVanilla); json.EndArrayObject(); } json.EndArray(); } internal static TraceMethodSource GetMethodSource(Delegate? callback) { Assembly assembly = callback?.Method?.DeclaringType?.Assembly; if (assembly == null) { return UnknownSource; } string assemblyName = assembly.GetName().Name ?? ""; if (IsVanillaAssembly(assemblyName)) { return new TraceMethodSource("vanilla", "Valheim", GetGameVersion(), assemblyName, isVanilla: true, isModded: false); } foreach (PluginInfo value in Chainloader.PluginInfos.Values) { Assembly assembly2 = (((Object)(object)value.Instance != (Object)null) ? ((object)value.Instance).GetType().Assembly : null); if (!(assembly2 == null) && (object)assembly2 == assembly) { return new TraceMethodSource(value.Metadata.GUID ?? "", value.Metadata.Name ?? "", value.Metadata.Version?.ToString() ?? "", assemblyName, isVanilla: false, isModded: true); } } return new TraceMethodSource("", "", "", assemblyName, isVanilla: false, isModded: false); } private static List GetRuntimePlugins() { List list = new List { new TraceMethodSource("vanilla", "Valheim", GetGameVersion(), "assembly_valheim", isVanilla: true, isModded: false) }; foreach (PluginInfo value in Chainloader.PluginInfos.Values) { string assemblyName = (((Object)(object)value.Instance != (Object)null) ? (((object)value.Instance).GetType().Assembly.GetName().Name ?? "") : ""); list.Add(new TraceMethodSource(value.Metadata.GUID ?? "", value.Metadata.Name ?? "", value.Metadata.Version?.ToString() ?? "", assemblyName, isVanilla: false, isModded: true)); } list.Sort((TraceMethodSource left, TraceMethodSource right) => string.Compare(left.PluginGuid, right.PluginGuid, StringComparison.OrdinalIgnoreCase)); return list; } private static bool IsVanillaAssembly(string assemblyName) { if (!string.Equals(assemblyName, "assembly_valheim", StringComparison.OrdinalIgnoreCase)) { return string.Equals(assemblyName, "Valheim", StringComparison.OrdinalIgnoreCase); } return true; } } internal readonly struct ClientZdoSnapshot { private readonly struct PlayerFields { internal static readonly PlayerFields Empty = new PlayerFields(0L, "", 0f, 0f, 0f, 0f, 0f, dead: false, pvp: false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", "", "", "", "", "", "", "", "", "", ""); internal readonly long PlayerId; internal readonly string PlayerName; private readonly float _health; private readonly float _maxHealth; private readonly float _stamina; private readonly float _eitr; private readonly float _adrenaline; private readonly bool _dead; private readonly bool _pvp; private readonly int _rightItem; private readonly int _leftItem; private readonly int _chestItem; private readonly int _legItem; private readonly int _helmetItem; private readonly int _shoulderItem; private readonly int _utilityItem; private readonly int _trinketItem; private readonly int _leftBackItem; private readonly int _rightBackItem; private readonly int _hairItem; private readonly int _beardItem; private readonly string _rightItemName; private readonly string _leftItemName; private readonly string _chestItemName; private readonly string _legItemName; private readonly string _helmetItemName; private readonly string _shoulderItemName; private readonly string _utilityItemName; private readonly string _trinketItemName; private readonly string _leftBackItemName; private readonly string _rightBackItemName; private readonly string _hairItemName; private readonly string _beardItemName; private PlayerFields(long playerId, string playerName, float health, float maxHealth, float stamina, float eitr, float adrenaline, bool dead, bool pvp, int rightItem, int leftItem, int chestItem, int legItem, int helmetItem, int shoulderItem, int utilityItem, int trinketItem, int leftBackItem, int rightBackItem, int hairItem, int beardItem, string rightItemName, string leftItemName, string chestItemName, string legItemName, string helmetItemName, string shoulderItemName, string utilityItemName, string trinketItemName, string leftBackItemName, string rightBackItemName, string hairItemName, string beardItemName) { PlayerId = playerId; PlayerName = playerName; _health = health; _maxHealth = maxHealth; _stamina = stamina; _eitr = eitr; _adrenaline = adrenaline; _dead = dead; _pvp = pvp; _rightItem = rightItem; _leftItem = leftItem; _chestItem = chestItem; _legItem = legItem; _helmetItem = helmetItem; _shoulderItem = shoulderItem; _utilityItem = utilityItem; _trinketItem = trinketItem; _leftBackItem = leftBackItem; _rightBackItem = rightBackItem; _hairItem = hairItem; _beardItem = beardItem; _rightItemName = rightItemName; _leftItemName = leftItemName; _chestItemName = chestItemName; _legItemName = legItemName; _helmetItemName = helmetItemName; _shoulderItemName = shoulderItemName; _utilityItemName = utilityItemName; _trinketItemName = trinketItemName; _leftBackItemName = leftBackItemName; _rightBackItemName = rightBackItemName; _hairItemName = hairItemName; _beardItemName = beardItemName; } internal static PlayerFields Capture(ZDO zdo) { int num = zdo.GetInt(ZDOVars.s_rightItem, 0); int num2 = zdo.GetInt(ZDOVars.s_leftItem, 0); int num3 = zdo.GetInt(ZDOVars.s_chestItem, 0); int num4 = zdo.GetInt(ZDOVars.s_legItem, 0); int num5 = zdo.GetInt(ZDOVars.s_helmetItem, 0); int num6 = zdo.GetInt(ZDOVars.s_shoulderItem, 0); int num7 = zdo.GetInt(ZDOVars.s_utilityItem, 0); int num8 = zdo.GetInt(ZDOVars.s_trinketItem, 0); int num9 = zdo.GetInt(ZDOVars.s_leftBackItem, 0); int num10 = zdo.GetInt(ZDOVars.s_rightBackItem, 0); int num11 = zdo.GetInt(ZDOVars.s_hairItem, 0); int num12 = zdo.GetInt(ZDOVars.s_beardItem, 0); return new PlayerFields(zdo.GetLong(ZDOVars.s_playerID, 0L), zdo.GetString(ZDOVars.s_playerName, ""), zdo.GetFloat(ZDOVars.s_health, 0f), zdo.GetFloat(ZDOVars.s_maxHealth, 0f), zdo.GetFloat(ZDOVars.s_stamina, 0f), zdo.GetFloat(ZDOVars.s_eitr, 0f), zdo.GetFloat(ZDOVars.s_adrenaline, 0f), zdo.GetBool(ZDOVars.s_dead, false), zdo.GetBool(ZDOVars.s_pvp, false), num, num2, num3, num4, num5, num6, num7, num8, num9, num10, num11, num12, ResolveItemName(num), ResolveItemName(num2), ResolveItemName(num3), ResolveItemName(num4), ResolveItemName(num5), ResolveItemName(num6), ResolveItemName(num7), ResolveItemName(num8), ResolveItemName(num9), ResolveItemName(num10), ResolveItemName(num11), ResolveItemName(num12)); } internal void Write(TelemetryJson json, string propertyName) { json.BeginObject(propertyName); json.Prop("exists", value: true); json.Prop("playerId", PlayerId); json.Prop("playerName", PlayerName); json.Prop("health", _health); json.Prop("maxHealth", _maxHealth); json.Prop("stamina", _stamina); json.Prop("eitr", _eitr); json.Prop("adrenaline", _adrenaline); json.Prop("dead", _dead); json.Prop("pvp", _pvp); WriteItem(json, "rightItem", _rightItem, _rightItemName); WriteItem(json, "leftItem", _leftItem, _leftItemName); WriteItem(json, "chestItem", _chestItem, _chestItemName); WriteItem(json, "legItem", _legItem, _legItemName); WriteItem(json, "helmetItem", _helmetItem, _helmetItemName); WriteItem(json, "shoulderItem", _shoulderItem, _shoulderItemName); WriteItem(json, "utilityItem", _utilityItem, _utilityItemName); WriteItem(json, "trinketItem", _trinketItem, _trinketItemName); WriteItem(json, "leftBackItem", _leftBackItem, _leftBackItemName); WriteItem(json, "rightBackItem", _rightBackItem, _rightBackItemName); WriteItem(json, "hairItem", _hairItem, _hairItemName); WriteItem(json, "beardItem", _beardItem, _beardItemName); json.EndObject(); } private static void WriteItem(TelemetryJson json, string prefix, int hash, string name) { json.Prop(prefix + "Hash", hash); json.Prop(prefix + "Name", name); } } internal static readonly ClientZdoSnapshot Empty = new ClientZdoSnapshot(exists: false, ZDOID.None, 0, "", Vector3.zero, Quaternion.identity, 0L, 0u, isPlayer: false, isCharacter: false, isMonster: false, isPiece: false, isWearNTear: false, isContainer: false, isPickable: false, PlayerFields.Empty, ClientEquipmentSnapshot.Empty, ClientFoodSlotsSnapshot.Empty, ClientResistanceSnapshot.Empty); internal readonly bool Exists; internal readonly ZDOID Id; internal readonly long PlayerId; internal readonly string PlayerName; private readonly int _prefabHash; private readonly string _prefabName; private readonly Vector3 _position; private readonly Quaternion _rotation; private readonly long _owner; private readonly uint _dataRevision; private readonly bool _isPlayer; private readonly bool _isCharacter; private readonly bool _isMonster; private readonly bool _isPiece; private readonly bool _isWearNTear; private readonly bool _isContainer; private readonly bool _isPickable; private readonly PlayerFields _fields; private readonly ClientEquipmentSnapshot _equipment; private readonly ClientFoodSlotsSnapshot _foodSlots; private readonly ClientResistanceSnapshot _resistances; private ClientZdoSnapshot(bool exists, ZDOID id, int prefabHash, string prefabName, Vector3 position, Quaternion rotation, long owner, uint dataRevision, bool isPlayer, bool isCharacter, bool isMonster, bool isPiece, bool isWearNTear, bool isContainer, bool isPickable, PlayerFields fields, ClientEquipmentSnapshot equipment, ClientFoodSlotsSnapshot foodSlots, ClientResistanceSnapshot resistances) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: 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) //IL_0042: Unknown result type (might be due to invalid IL or missing references) Exists = exists; Id = id; PlayerId = fields.PlayerId; PlayerName = fields.PlayerName; _prefabHash = prefabHash; _prefabName = prefabName; _position = position; _rotation = rotation; _owner = owner; _dataRevision = dataRevision; _isPlayer = isPlayer; _isCharacter = isCharacter; _isMonster = isMonster; _isPiece = isPiece; _isWearNTear = isWearNTear; _isContainer = isContainer; _isPickable = isPickable; _fields = fields; _equipment = equipment; _foodSlots = foodSlots; _resistances = resistances; } internal static ClientZdoSnapshot Capture(Character? character) { if ((Object)(object)character == (Object)null || (Object)(object)character.m_nview == (Object)null) { return Empty; } ZDO zDO = character.m_nview.GetZDO(); if (zDO != null && !((ZDOID)(ref zDO.m_uid)).IsNone()) { return Capture(zDO, ((Component)character).gameObject); } return Empty; } internal static ClientZdoSnapshot Capture(ZDOID id) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref id)).IsNone() || ZDOMan.instance == null) { return Empty; } return Capture(ZDOMan.instance.GetZDO(id)); } internal static ClientZdoSnapshot Capture(ZDO zdo) { if (zdo == null || ((ZDOID)(ref zdo.m_uid)).IsNone()) { return Empty; } GameObject go = null; if ((Object)(object)ZNetScene.instance != (Object)null) { ZNetView val = ZNetScene.instance.FindInstance(zdo); if ((Object)(object)val != (Object)null) { go = ((Component)val).gameObject; } } return Capture(zdo, go); } private static ClientZdoSnapshot Capture(ZDO zdo, GameObject? go) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) int prefab = zdo.GetPrefab(); string prefabName = ""; if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab2 = ZNetScene.instance.GetPrefab(prefab); if ((Object)(object)prefab2 != (Object)null) { prefabName = ((Object)prefab2).name; } } Player val = (((Object)(object)go != (Object)null) ? go.GetComponent() : null); Character val2 = (((Object)(object)go != (Object)null) ? go.GetComponent() : null); PlayerFields fields = PlayerFields.Capture(zdo); return new ClientZdoSnapshot(exists: true, zdo.m_uid, prefab, prefabName, zdo.GetPosition(), zdo.GetRotation(), zdo.GetOwner(), zdo.DataRevision, (Object)(object)val != (Object)null, (Object)(object)val2 != (Object)null, (Object)(object)go != (Object)null && (Object)(object)go.GetComponent() != (Object)null, (Object)(object)go != (Object)null && (Object)(object)go.GetComponent() != (Object)null, (Object)(object)go != (Object)null && (Object)(object)go.GetComponent() != (Object)null, (Object)(object)go != (Object)null && (Object)(object)go.GetComponent() != (Object)null, (Object)(object)go != (Object)null && (Object)(object)go.GetComponent() != (Object)null, fields, ClientEquipmentSnapshot.Capture(val, zdo), ClientFoodSlotsSnapshot.Capture(val), ClientResistanceSnapshot.Capture(val2)); } internal void Write(TelemetryJson json, string propertyName) { //IL_001f: 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_0068: 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) json.BeginObject(propertyName); json.Prop("exists", Exists); json.Prop("id", FormatId(Id)); json.Prop("prefabHash", _prefabHash); json.Prop("prefabName", _prefabName); json.Prop("position", _position); json.Prop("rotation", _rotation); WriteWorldContext(json, _position); json.Prop("ownerPeerId", _owner); json.Prop("dataRevision", _dataRevision); json.BeginArray("components"); if (_isPlayer) { json.ArrayString("Player"); } if (_isCharacter) { json.ArrayString("Character"); } if (_isMonster) { json.ArrayString("MonsterAI"); } if (_isPiece) { json.ArrayString("Piece"); } if (_isWearNTear) { json.ArrayString("WearNTear"); } if (_isContainer) { json.ArrayString("Container"); } if (_isPickable) { json.ArrayString("Pickable"); } json.EndArray(); _fields.Write(json, "fields"); _equipment.Write(json, "equipment"); _foodSlots.Write(json, "foodSlots"); _resistances.Write(json, "resistances"); json.EndObject(); } internal unsafe static string FormatId(ZDOID id) { if (!((ZDOID)(ref id)).IsNone()) { return ((object)(*(ZDOID*)(&id))/*cast due to .constrained prefix*/).ToString(); } return ""; } internal static void WriteWorldContext(TelemetryJson json, Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //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_001a: 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_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) Vector2i zone = ZoneSystem.GetZone(position); string value = ((WorldGenerator.instance != null) ? ((object)WorldGenerator.instance.GetBiome(position)/*cast due to .constrained prefix*/).ToString() : ""); json.BeginObject("worldContext"); json.Prop("exists", value: true); json.Prop("biome", value); json.Prop("zoneX", zone.x); json.Prop("zoneY", zone.y); json.Prop("distanceFromCenter", Utils.DistanceXZ(Vector3.zero, position)); json.EndObject(); } internal bool IsPlayerSnapshot() { if (Exists) { if (!_isPlayer && PlayerId == 0L && string.IsNullOrEmpty(PlayerName)) { return _prefabName == "Player"; } return true; } return false; } private static string ResolveItemName(int itemHash) { if (itemHash == 0 || (Object)(object)ObjectDB.instance == (Object)null) { return ""; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(itemHash); if (!((Object)(object)itemPrefab != (Object)null)) { return ""; } return ((Object)itemPrefab).name; } } internal readonly struct ClientEquipmentSnapshot { internal static readonly ClientEquipmentSnapshot Empty = new ClientEquipmentSnapshot(exists: false, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty, ClientEquipmentItemSnapshot.Empty); private readonly bool _exists; private readonly ClientEquipmentItemSnapshot _rightHand; private readonly ClientEquipmentItemSnapshot _leftHand; private readonly ClientEquipmentItemSnapshot _head; private readonly ClientEquipmentItemSnapshot _chest; private readonly ClientEquipmentItemSnapshot _legs; private readonly ClientEquipmentItemSnapshot _shoulder; private readonly ClientEquipmentItemSnapshot _utility; private readonly ClientEquipmentItemSnapshot _trinket; private ClientEquipmentSnapshot(bool exists, ClientEquipmentItemSnapshot rightHand, ClientEquipmentItemSnapshot leftHand, ClientEquipmentItemSnapshot head, ClientEquipmentItemSnapshot chest, ClientEquipmentItemSnapshot legs, ClientEquipmentItemSnapshot shoulder, ClientEquipmentItemSnapshot utility, ClientEquipmentItemSnapshot trinket) { _exists = exists; _rightHand = rightHand; _leftHand = leftHand; _head = head; _chest = chest; _legs = legs; _shoulder = shoulder; _utility = utility; _trinket = trinket; } internal static ClientEquipmentSnapshot Capture(Player? player, ZDO zdo) { if ((Object)(object)player != (Object)null) { return new ClientEquipmentSnapshot(exists: true, ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_rightItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_leftItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_helmetItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_chestItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_legItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_shoulderItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_utilityItem), ClientEquipmentItemSnapshot.Capture(((Humanoid)player).m_trinketItem)); } return new ClientEquipmentSnapshot(exists: true, ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_rightItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_leftItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_helmetItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_chestItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_legItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_shoulderItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_utilityItem, 0)), ClientEquipmentItemSnapshot.Capture(zdo.GetInt(ZDOVars.s_trinketItem, 0))); } internal void Write(TelemetryJson json, string propertyName) { json.BeginObject(propertyName); json.Prop("exists", _exists); _rightHand.Write(json, "rightHand"); _leftHand.Write(json, "leftHand"); _head.Write(json, "head"); _chest.Write(json, "chest"); _legs.Write(json, "legs"); _shoulder.Write(json, "shoulder"); _utility.Write(json, "utility"); _trinket.Write(json, "trinket"); json.EndObject(); } } internal readonly struct ClientEquipmentItemSnapshot { internal static readonly ClientEquipmentItemSnapshot Empty = new ClientEquipmentItemSnapshot(exists: false, 0, "", "", 0f, 0f, 0f); private readonly bool _exists; private readonly int _itemHash; private readonly string _prefabName; private readonly string _itemName; private readonly float _armor; private readonly float _durability; private readonly float _maxDurability; private ClientEquipmentItemSnapshot(bool exists, int itemHash, string prefabName, string itemName, float armor, float durability, float maxDurability) { _exists = exists; _itemHash = itemHash; _prefabName = prefabName; _itemName = itemName; _armor = armor; _durability = durability; _maxDurability = maxDurability; } internal static ClientEquipmentItemSnapshot Capture(ItemData? item) { if (item == null) { return Empty; } string text = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name); return new ClientEquipmentItemSnapshot(exists: true, StringExtensionMethods.GetStableHashCode(text), text, item.m_shared.m_name, item.GetArmor(), item.m_durability, item.GetMaxDurability()); } internal static ClientEquipmentItemSnapshot Capture(int itemHash) { if (itemHash == 0) { return Empty; } string text = ""; if ((Object)(object)ObjectDB.instance != (Object)null) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(itemHash); text = (((Object)(object)itemPrefab != (Object)null) ? ((Object)itemPrefab).name : ""); } return new ClientEquipmentItemSnapshot(exists: true, itemHash, text, text, 0f, 0f, 0f); } internal void Write(TelemetryJson json, string propertyName) { json.BeginObject(propertyName); json.Prop("exists", _exists); json.Prop("itemHash", _itemHash); json.Prop("prefabName", _prefabName); json.Prop("itemName", _itemName); json.Prop("armor", _armor); json.Prop("durability", _durability); json.Prop("maxDurability", _maxDurability); json.EndObject(); } } internal readonly struct ClientFoodSlotsSnapshot { internal static readonly ClientFoodSlotsSnapshot Empty = new ClientFoodSlotsSnapshot(exists: false, new List()); private readonly bool _exists; private readonly List _slots; private ClientFoodSlotsSnapshot(bool exists, List slots) { _exists = exists; _slots = slots; } internal static ClientFoodSlotsSnapshot Capture(Player? player) { if ((Object)(object)player == (Object)null) { return Empty; } List list = new List(); IReadOnlyList foods = player.GetFoods(); for (int i = 0; i < foods.Count; i++) { list.Add(ClientFoodSlotSnapshot.Capture(i, foods[i])); } return new ClientFoodSlotsSnapshot(exists: true, list); } internal void Write(TelemetryJson json, string propertyName) { json.BeginObject(propertyName); json.Prop("exists", _exists); json.BeginArray("slots"); foreach (ClientFoodSlotSnapshot slot in _slots) { slot.Write(json); } json.EndArray(); json.EndObject(); } } internal readonly struct ClientFoodSlotSnapshot { private readonly int _slot; private readonly bool _exists; private readonly string _prefabName; private readonly string _itemName; private readonly float _remainingTime; private readonly float _duration; private readonly float _health; private readonly float _stamina; private readonly float _eitr; private readonly float _baseHealth; private readonly float _baseStamina; private readonly float _baseEitr; private readonly float _regen; private readonly bool _canEatAgain; private ClientFoodSlotSnapshot(int slot, bool exists, string prefabName, string itemName, float remainingTime, float duration, float health, float stamina, float eitr, float baseHealth, float baseStamina, float baseEitr, float regen, bool canEatAgain) { _slot = slot; _exists = exists; _prefabName = prefabName; _itemName = itemName; _remainingTime = remainingTime; _duration = duration; _health = health; _stamina = stamina; _eitr = eitr; _baseHealth = baseHealth; _baseStamina = baseStamina; _baseEitr = baseEitr; _regen = regen; _canEatAgain = canEatAgain; } internal static ClientFoodSlotSnapshot Capture(int slot, Food food) { ItemData item = food.m_item; SharedData val = item?.m_shared; string prefabName = (((Object)(object)item?.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : food.m_name); return new ClientFoodSlotSnapshot(slot, exists: true, prefabName, val?.m_name ?? food.m_name, food.m_time, val?.m_foodBurnTime ?? 0f, food.m_health, food.m_stamina, food.m_eitr, val?.m_food ?? 0f, val?.m_foodStamina ?? 0f, val?.m_foodEitr ?? 0f, val?.m_foodRegen ?? 0f, item != null && food.CanEatAgain()); } internal void Write(TelemetryJson json) { json.BeginArrayObject(); json.Prop("slot", _slot); json.Prop("exists", _exists); json.Prop("prefabName", _prefabName); json.Prop("itemName", _itemName); json.Prop("remainingTime", _remainingTime); json.Prop("duration", _duration); json.Prop("health", _health); json.Prop("stamina", _stamina); json.Prop("eitr", _eitr); json.Prop("baseHealth", _baseHealth); json.Prop("baseStamina", _baseStamina); json.Prop("baseEitr", _baseEitr); json.Prop("regen", _regen); json.Prop("canEatAgain", _canEatAgain); json.EndArrayObject(); } } internal readonly struct ClientResistanceSnapshot { internal static readonly ClientResistanceSnapshot Empty = new ClientResistanceSnapshot(exists: false, default(DamageModifiers)); private readonly bool _exists; private readonly DamageModifiers _modifiers; private ClientResistanceSnapshot(bool exists, DamageModifiers modifiers) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) _exists = exists; _modifiers = modifiers; } internal static ClientResistanceSnapshot Capture(Character? character) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)character == (Object)null)) { return new ClientResistanceSnapshot(exists: true, character.GetDamageModifiers((WeakSpot)null)); } return Empty; } internal void Write(TelemetryJson json, string propertyName) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) json.BeginObject(propertyName); json.Prop("exists", _exists); json.BeginObject("modifiers"); json.Prop("blunt", Format(_modifiers.m_blunt)); json.Prop("slash", Format(_modifiers.m_slash)); json.Prop("pierce", Format(_modifiers.m_pierce)); json.Prop("chop", Format(_modifiers.m_chop)); json.Prop("pickaxe", Format(_modifiers.m_pickaxe)); json.Prop("fire", Format(_modifiers.m_fire)); json.Prop("frost", Format(_modifiers.m_frost)); json.Prop("lightning", Format(_modifiers.m_lightning)); json.Prop("poison", Format(_modifiers.m_poison)); json.Prop("spirit", Format(_modifiers.m_spirit)); json.EndObject(); json.EndObject(); } private static string Format(DamageModifier modifier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown return (modifier - 1) switch { 0 => "resistant", 1 => "weak", 2 => "immune", 3 => "ignore", 4 => "very_resistant", 5 => "very_weak", 6 => "slightly_resistant", 7 => "slightly_weak", _ => "normal", }; } } internal static class ValheimEventsTelemetry { private readonly struct ExploredCell { internal readonly int X; internal readonly int Y; internal ExploredCell(int x, int y) { X = x; Y = y; } } private const int ProtocolVersion = 1; private static readonly List PendingExplorationCells = new List(); private static float _nextExplorationFlushTime; private static long _sequence; internal static DamageObservationState CaptureDamageBefore(Character? target, HitData? hit) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: 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_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) if (!CanCaptureCombat() || (Object)(object)target == (Object)null || hit == null || (Object)(object)target.m_nview == (Object)null || !target.m_nview.IsOwner()) { return DamageObservationState.Empty; } try { ZDOID characterZdoId = GetCharacterZdoId(target); ZDOID attacker = hit.m_attacker; Character attacker2 = hit.GetAttacker(); ClientZdoSnapshot targetBefore = ClientZdoSnapshot.Capture(target); ClientZdoSnapshot attackerBefore = CaptureCharacterOrZdo(attacker2, attacker); if (!targetBefore.IsPlayerSnapshot() && !attackerBefore.IsPlayerSnapshot()) { return DamageObservationState.Empty; } return new DamageObservationState { Exists = true, TargetZdo = characterZdoId, AttackerZdo = attacker, HealthBefore = target.GetHealth(), MaxHealthBefore = target.GetMaxHealth(), TargetBefore = targetBefore, AttackerBefore = attackerBefore }; } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to capture damage telemetry before state: " + ex.GetType().Name + ": " + ex.Message)); return DamageObservationState.Empty; } } internal static void LogDamageApplied(Character? target, HitData? hit, DamageModifier modifier, DamageObservationState before) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0060: 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_007d: 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) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) if (!CanCaptureCombat() || !before.Exists || (Object)(object)target == (Object)null || hit == null) { return; } try { float health = target.GetHealth(); float maxHealth = target.GetMaxHealth(); float num = Math.Max(0f, before.HealthBefore - health); if (!(num <= 0f)) { ZDOID characterZdoId = GetCharacterZdoId(target); ZDOID id = (((ZDOID)(ref hit.m_attacker)).IsNone() ? before.AttackerZdo : hit.m_attacker); Character attacker = hit.GetAttacker(); ClientZdoSnapshot clientZdoSnapshot = ClientZdoSnapshot.Capture(target); ClientZdoSnapshot clientZdoSnapshot2 = CaptureCharacterOrZdo(attacker, id); ClientHitSummary clientHitSummary = ClientHitSummary.From(hit); TelemetryJson telemetryJson = ObjectWithEnvelope("damage_applied"); telemetryJson.Prop("stage", "praetoris_client_apply_damage"); telemetryJson.Prop("method", "ApplyDamage"); telemetryJson.Prop("methodKnown", value: true); telemetryJson.Prop("sourceClass", "Character"); telemetryJson.Prop("sourceMod", "PraetorisClient"); telemetryJson.Prop("targetZdo", ClientZdoSnapshot.FormatId(characterZdoId)); telemetryJson.Prop("attackerZdo", ClientZdoSnapshot.FormatId(id)); telemetryJson.Prop("damageApplied", num); telemetryJson.Prop("healthBefore", before.HealthBefore); telemetryJson.Prop("healthAfter", health); telemetryJson.Prop("maxHealthBefore", before.MaxHealthBefore); telemetryJson.Prop("maxHealthAfter", maxHealth); telemetryJson.Prop("damageModifier", FormatDamageModifier(modifier)); clientHitSummary.Write(telemetryJson, "hit"); before.TargetBefore.Write(telemetryJson, "targetBefore"); clientZdoSnapshot.Write(telemetryJson, "targetAfter"); before.AttackerBefore.Write(telemetryJson, "attackerBefore"); clientZdoSnapshot2.Write(telemetryJson, "attackerAfter"); telemetryJson.End(); Send(telemetryJson.ToString()); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to send damage telemetry: " + ex.GetType().Name + ": " + ex.Message)); } } internal static DeathObservationState CaptureDeathBefore(Player? player) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: 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) //IL_0077: 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) if (!CanCaptureCombat() || (Object)(object)player == (Object)null || (Object)(object)((Character)player).m_nview == (Object)null || !((Character)player).m_nview.IsOwner()) { return DeathObservationState.Empty; } try { HitData lastHit = ((Character)player).m_lastHit; ZDOID val = lastHit?.m_attacker ?? ZDOID.None; Character character = ((lastHit != null) ? lastHit.GetAttacker() : null); return new DeathObservationState { Exists = true, PlayerZdo = GetCharacterZdoId((Character)(object)player), AttackerZdo = val, HealthBefore = ((Character)player).GetHealth(), MaxHealthBefore = ((Character)player).GetMaxHealth(), HasLastHit = (lastHit != null), LastHit = ((lastHit != null) ? ClientHitSummary.From(lastHit) : ClientHitSummary.Empty), PlayerBefore = ClientZdoSnapshot.Capture((Character?)(object)player), AttackerBefore = CaptureCharacterOrZdo(character, val) }; } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to capture death telemetry before state: " + ex.GetType().Name + ": " + ex.Message)); return DeathObservationState.Empty; } } internal static void LogPlayerDied(Player? player, DeathObservationState before) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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) if (!CanCaptureCombat() || !before.Exists || (Object)(object)player == (Object)null) { return; } try { float health = ((Character)player).GetHealth(); float maxHealth = ((Character)player).GetMaxHealth(); HitData lastHit = ((Character)player).m_lastHit; ZDOID id = ((lastHit != null && !((ZDOID)(ref lastHit.m_attacker)).IsNone()) ? lastHit.m_attacker : before.AttackerZdo); Character character = ((lastHit != null) ? lastHit.GetAttacker() : null); ClientZdoSnapshot clientZdoSnapshot = ClientZdoSnapshot.Capture((Character?)(object)player); ClientZdoSnapshot clientZdoSnapshot2 = CaptureCharacterOrZdo(character, id); TelemetryJson telemetryJson = ObjectWithEnvelope("player_died"); telemetryJson.Prop("stage", "praetoris_client_player_on_death"); telemetryJson.Prop("method", "OnDeath"); telemetryJson.Prop("methodKnown", value: true); telemetryJson.Prop("sourceClass", "Player"); telemetryJson.Prop("sourceMod", "PraetorisClient"); telemetryJson.Prop("playerZdo", ClientZdoSnapshot.FormatId(before.PlayerZdo)); telemetryJson.Prop("attackerZdo", ClientZdoSnapshot.FormatId(id)); telemetryJson.Prop("deathDetectedBy", "praetoris_client_player_on_death"); telemetryJson.Prop("damageApplied", Math.Max(0f, before.HealthBefore - health)); telemetryJson.Prop("healthBefore", before.HealthBefore); telemetryJson.Prop("healthAfter", health); telemetryJson.Prop("maxHealthBefore", before.MaxHealthBefore); telemetryJson.Prop("maxHealthAfter", maxHealth); if (before.HasLastHit) { before.LastHit.Write(telemetryJson, "hit"); } before.PlayerBefore.Write(telemetryJson, "playerBefore"); clientZdoSnapshot.Write(telemetryJson, "playerAfter"); before.AttackerBefore.Write(telemetryJson, "attackerBefore"); clientZdoSnapshot2.Write(telemetryJson, "attackerAfter"); telemetryJson.End(); Send(telemetryJson.ToString()); } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to send death telemetry: " + ex.GetType().Name + ": " + ex.Message)); } } internal static void RecordExploredCell(Minimap minimap, int x, int y) { if (CanSendTelemetry() && PraetorisClientPlugin.ExplorationTelemetryEnabled.Value && !((Object)(object)minimap == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { PendingExplorationCells.Add(new ExploredCell(x, y)); if (Time.time >= _nextExplorationFlushTime) { FlushExploration(minimap, "interval"); } } } internal static void Update() { if (PendingExplorationCells.Count != 0 && !((Object)(object)Minimap.instance == (Object)null) && !(Time.time < _nextExplorationFlushTime)) { FlushExploration(Minimap.instance, "timer"); } } private static void FlushExploration(Minimap minimap, string reason) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_002c: 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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_012b: 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_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) if (PendingExplorationCells.Count == 0 || (Object)(object)Player.m_localPlayer == (Object)null) { return; } try { Vector3 position = ((Component)Player.m_localPlayer).transform.position; Vector2i zone = ZoneSystem.GetZone(position); string value = ((WorldGenerator.instance != null) ? ((object)WorldGenerator.instance.GetBiome(position)/*cast due to .constrained prefix*/).ToString() : ""); int count = PendingExplorationCells.Count; TelemetryJson telemetryJson = ObjectWithEnvelope("player_exploration"); telemetryJson.Prop("stage", "praetoris_client_minimap_explore"); telemetryJson.Prop("method", "Minimap.Explore"); telemetryJson.Prop("methodKnown", value: true); telemetryJson.Prop("sourceClass", "Minimap"); telemetryJson.Prop("sourceMod", "PraetorisClient"); telemetryJson.Prop("reason", reason); telemetryJson.Prop("playerZdo", ((Object)(object)((Character)Player.m_localPlayer).m_nview != (Object)null) ? ClientZdoSnapshot.FormatId(((Character)Player.m_localPlayer).m_nview.GetZDO().m_uid) : ""); telemetryJson.Prop("playerName", Player.m_localPlayer.GetPlayerName()); telemetryJson.Prop("position", position); telemetryJson.Prop("biome", value); telemetryJson.Prop("zoneX", zone.x); telemetryJson.Prop("zoneY", zone.y); telemetryJson.Prop("distanceFromCenter", Utils.DistanceXZ(Vector3.zero, position)); telemetryJson.Prop("newExploredCellCount", count); telemetryJson.Prop("mapTextureSize", minimap.m_textureSize); telemetryJson.Prop("mapPixelSize", minimap.m_pixelSize); telemetryJson.BeginArray("cells"); int num = Math.Min(count, 128); for (int i = 0; i < num; i++) { ExploredCell exploredCell = PendingExplorationCells[i]; telemetryJson.BeginArrayObject(); telemetryJson.Prop("x", exploredCell.X); telemetryJson.Prop("y", exploredCell.Y); telemetryJson.EndArrayObject(); } telemetryJson.EndArray(); telemetryJson.End(); PendingExplorationCells.Clear(); _nextExplorationFlushTime = Time.time + Math.Max(0.5f, PraetorisClientPlugin.ExplorationFlushSeconds.Value); Send(telemetryJson.ToString()); } catch (Exception ex) { PendingExplorationCells.Clear(); PraetorisClientPlugin.Log.LogWarning((object)("Failed to send exploration telemetry: " + ex.GetType().Name + ": " + ex.Message)); } } private static TelemetryJson ObjectWithEnvelope(string eventType) { TelemetryJson telemetryJson = TelemetryJson.Object(); telemetryJson.Prop("schema", "valheim.events.client.v1"); telemetryJson.Prop("eventType", eventType); telemetryJson.Prop("sequence", ++_sequence); telemetryJson.Prop("clientTimeUtc", DateTime.UtcNow.ToString("o")); telemetryJson.Prop("timeUtc", DateTime.UtcNow.ToString("o")); telemetryJson.Prop("worldTime", ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTime().ToString("o") : ""); telemetryJson.Prop("worldName", (ZNet.m_world != null) ? ZNet.m_world.m_name : ""); telemetryJson.Prop("worldUid", (ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); return telemetryJson; } private static bool CanCaptureCombat() { if (CanSendTelemetry()) { return PraetorisClientPlugin.CombatTelemetryEnabled.Value; } return false; } private static bool CanSendTelemetry() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 if (PraetorisClientPlugin.ValheimEventsTelemetryEnabled.Value && (Object)(object)ZNet.instance != (Object)null && ZRoutedRpc.instance != null && !ZNet.instance.IsServer()) { return (int)ZNet.GetConnectionStatus() == 2; } return false; } private static void Send(string payload) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown if (CanSendTelemetry()) { ZPackage val = new ZPackage(); val.Write(1); val.Write(payload); ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.instance.GetServerPeerID(), "PraetorisClient_ValheimEventsTelemetry", new object[1] { val }); } } private static ZDOID GetCharacterZdoId(Character character) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)character.m_nview == (Object)null) { return ZDOID.None; } return character.m_nview.GetZDO()?.m_uid ?? ZDOID.None; } private static ClientZdoSnapshot CaptureCharacterOrZdo(Character? character, ZDOID id) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) ClientZdoSnapshot result = ClientZdoSnapshot.Capture(character); if (!result.Exists) { return ClientZdoSnapshot.Capture(id); } return result; } private static string FormatDamageModifier(DamageModifier modifier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown return (modifier - 1) switch { 0 => "resistant", 1 => "weak", 2 => "immune", 3 => "ignore", 4 => "very_resistant", 5 => "very_weak", 6 => "slightly_resistant", 7 => "slightly_weak", _ => "normal", }; } } internal struct DamageObservationState { internal static readonly DamageObservationState Empty = new DamageObservationState { Exists = false, TargetZdo = ZDOID.None, AttackerZdo = ZDOID.None, TargetBefore = ClientZdoSnapshot.Empty, AttackerBefore = ClientZdoSnapshot.Empty }; internal bool Exists; internal ZDOID TargetZdo; internal ZDOID AttackerZdo; internal float HealthBefore; internal float MaxHealthBefore; internal ClientZdoSnapshot TargetBefore; internal ClientZdoSnapshot AttackerBefore; } internal struct DeathObservationState { internal static readonly DeathObservationState Empty = new DeathObservationState { Exists = false, PlayerZdo = ZDOID.None, AttackerZdo = ZDOID.None, LastHit = ClientHitSummary.Empty, PlayerBefore = ClientZdoSnapshot.Empty, AttackerBefore = ClientZdoSnapshot.Empty }; internal bool Exists; internal ZDOID PlayerZdo; internal ZDOID AttackerZdo; internal float HealthBefore; internal float MaxHealthBefore; internal bool HasLastHit; internal ClientHitSummary LastHit; internal ClientZdoSnapshot PlayerBefore; internal ClientZdoSnapshot AttackerBefore; } internal readonly struct ClientHitSummary { internal static readonly ClientHitSummary Empty = new ClientHitSummary(exists: false, ZDOID.None, "", "", 0f, default(DamageTypes), dodgeable: false, blockable: false, ranged: false, ignorePvp: false, 0, 0f, 1f, 1f, Vector3.zero, Vector3.zero, 0, "", 0f, 0f, 0, 0, -1, 0f, 0f); private readonly bool _exists; private readonly ZDOID _attacker; private readonly string _hitType; private readonly string _skill; private readonly float _totalDamage; private readonly DamageTypes _damage; private readonly bool _dodgeable; private readonly bool _blockable; private readonly bool _ranged; private readonly bool _ignorePvp; private readonly short _toolTier; private readonly float _pushForce; private readonly float _backstabBonus; private readonly float _staggerMultiplier; private readonly Vector3 _point; private readonly Vector3 _dir; private readonly int _statusEffectHash; private readonly string _statusEffectName; private readonly float _skillRaiseAmount; private readonly float _skillLevel; private readonly short _itemLevel; private readonly byte _itemWorldLevel; private readonly short _weakSpot; private readonly float _healthReturn; private readonly float _radius; private ClientHitSummary(bool exists, ZDOID attacker, string hitType, string skill, float totalDamage, DamageTypes damage, bool dodgeable, bool blockable, bool ranged, bool ignorePvp, short toolTier, float pushForce, float backstabBonus, float staggerMultiplier, Vector3 point, Vector3 dir, int statusEffectHash, string statusEffectName, float skillRaiseAmount, float skillLevel, short itemLevel, byte itemWorldLevel, short weakSpot, float healthReturn, float radius) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: 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) //IL_0078: Unknown result type (might be due to invalid IL or missing references) _exists = exists; _attacker = attacker; _hitType = hitType; _skill = skill; _totalDamage = totalDamage; _damage = damage; _dodgeable = dodgeable; _blockable = blockable; _ranged = ranged; _ignorePvp = ignorePvp; _toolTier = toolTier; _pushForce = pushForce; _backstabBonus = backstabBonus; _staggerMultiplier = staggerMultiplier; _point = point; _dir = dir; _statusEffectHash = statusEffectHash; _statusEffectName = statusEffectName; _skillRaiseAmount = skillRaiseAmount; _skillLevel = skillLevel; _itemLevel = itemLevel; _itemWorldLevel = itemWorldLevel; _weakSpot = weakSpot; _healthReturn = healthReturn; _radius = radius; } internal static ClientHitSummary From(HitData hit) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) return new ClientHitSummary(exists: true, hit.m_attacker, ((object)Unsafe.As(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString(), ((object)Unsafe.As(ref hit.m_skill)/*cast due to .constrained prefix*/).ToString(), ((DamageTypes)(ref hit.m_damage)).GetTotalDamage(), hit.m_damage, hit.m_dodgeable, hit.m_blockable, hit.m_ranged, hit.m_ignorePVP, hit.m_toolTier, hit.m_pushForce, hit.m_backstabBonus, hit.m_staggerMultiplier, hit.m_point, hit.m_dir, hit.m_statusEffectHash, (hit.m_statusEffectHash != 0) ? hit.m_statusEffectHash.ToString() : "", hit.m_skillRaiseAmount, hit.m_skillLevel, hit.m_itemLevel, hit.m_itemWorldLevel, hit.m_weakSpot, hit.m_healthReturn, hit.m_radius); } internal void Write(TelemetryJson json, string propertyName) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) if (_exists) { json.BeginObject(propertyName); json.Prop("attackerZdo", ClientZdoSnapshot.FormatId(_attacker)); json.Prop("hitType", _hitType); json.Prop("skill", _skill); json.Prop("totalDamage", _totalDamage); json.Prop("damage", _damage.m_damage); json.Prop("blunt", _damage.m_blunt); json.Prop("slash", _damage.m_slash); json.Prop("pierce", _damage.m_pierce); json.Prop("chop", _damage.m_chop); json.Prop("pickaxe", _damage.m_pickaxe); json.Prop("fire", _damage.m_fire); json.Prop("frost", _damage.m_frost); json.Prop("lightning", _damage.m_lightning); json.Prop("poison", _damage.m_poison); json.Prop("spirit", _damage.m_spirit); json.Prop("dodgeable", _dodgeable); json.Prop("blockable", _blockable); json.Prop("ranged", _ranged); json.Prop("ignorePvp", _ignorePvp); json.Prop("toolTier", _toolTier); json.Prop("pushForce", _pushForce); json.Prop("backstabBonus", _backstabBonus); json.Prop("staggerMultiplier", _staggerMultiplier); json.Prop("point", _point); json.Prop("direction", _dir); json.Prop("statusEffectHash", _statusEffectHash); json.Prop("statusEffectName", _statusEffectName); json.Prop("skillRaiseAmount", _skillRaiseAmount); json.Prop("skillLevel", _skillLevel); json.Prop("itemLevel", _itemLevel); json.Prop("itemWorldLevel", (short)_itemWorldLevel); json.Prop("weakSpot", _weakSpot); json.Prop("healthReturn", _healthReturn); json.Prop("radius", _radius); json.EndObject(); } } } internal static class ZdoTraceTelemetry { private sealed class ReceiveContext { internal ZdoPackageTrace Trace { get; } internal List ApplyItems { get; } = new List(); internal ReceiveContext(ZdoPackageTrace trace) { Trace = trace; } internal ZdoTraceItem? TakeNextApplyItem(ZDOID zdoId) { //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) for (int i = 0; i < ApplyItems.Count; i++) { ZdoTraceItem zdoTraceItem = ApplyItems[i]; if (!(zdoTraceItem.ZdoId != zdoId)) { ApplyItems.RemoveAt(i); return zdoTraceItem; } } return null; } } private sealed class ZdoPackageTrace { internal string PackageId { get; } internal string PackageHash { get; } internal long SenderPeerId { get; } internal long ReceiverPeerId { get; } internal int PackageBytes { get; } internal int InvalidSectorCount { get; } internal List Items { get; } internal ZdoPackageTrace(string packageId, string packageHash, long senderPeerId, long receiverPeerId, int packageBytes, int invalidSectorCount, List items) { PackageId = packageId; PackageHash = packageHash; SenderPeerId = senderPeerId; ReceiverPeerId = receiverPeerId; PackageBytes = packageBytes; InvalidSectorCount = invalidSectorCount; Items = items; } internal bool Matches(ZdoPackageTrace other) { if (string.Equals(PackageHash, other.PackageHash, StringComparison.Ordinal) && SenderPeerId == other.SenderPeerId) { return ReceiverPeerId == other.ReceiverPeerId; } return false; } } private sealed class ZdoTraceItem { internal ZDOID ZdoId { get; } internal string ZdoIdText { get; } internal string ZdoTraceId { get; } internal ushort OwnerRevision { get; } internal uint DataRevision { get; } internal long OwnerPeerId { get; } internal Vector3 Position { get; } internal int ItemPayloadBytes { get; } internal int PayloadFlags { get; } internal int ExtraDataMask { get; } internal bool HasExtraData => ExtraDataMask != 0; internal int PrefabHash { get; } internal string PrefabName { get; } internal string ApplyOutcome { get; set; } = ""; internal bool ExistedBefore { get; set; } internal uint LocalDataRevision { get; set; } internal ushort LocalOwnerRevision { get; set; } internal bool Captured { get; set; } internal ZdoTraceItem(ZDOID zdoId, string zdoIdText, string zdoTraceId, ushort ownerRevision, uint dataRevision, long ownerPeerId, Vector3 position, int itemPayloadBytes, int payloadFlags, int extraDataMask, int prefabHash, string prefabName) { //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_003f: 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) ZdoId = zdoId; ZdoIdText = zdoIdText; ZdoTraceId = zdoTraceId; OwnerRevision = ownerRevision; DataRevision = dataRevision; OwnerPeerId = ownerPeerId; Position = position; ItemPayloadBytes = itemPayloadBytes; PayloadFlags = payloadFlags; ExtraDataMask = extraDataMask; PrefabHash = prefabHash; PrefabName = prefabName; } } private readonly struct ZdoPayloadHeader { internal int Flags { get; } internal int ExtraDataMask { get; } internal int PrefabHash { get; } internal ZdoPayloadHeader(int flags, int extraDataMask, int prefabHash) { Flags = flags; ExtraDataMask = extraDataMask; PrefabHash = prefabHash; } } [ThreadStatic] private static ReceiveContext? _activeReceiveContext; private static readonly object FilterSync = new object(); private static readonly HashSet PrefabFilter = new HashSet(); private static readonly HashSet ZdoIdFilter = new HashSet(StringComparer.Ordinal); private static string _lastPrefabFilter = ""; private static string _lastZdoIdFilter = ""; private static FieldInfo? _deadZdosField; private static int _rateSecond = -1; private static int _rateCount; internal static void TracePackageSend(ZRpc rpc, ZPackage package) { if (!CanCapture() || package == null) { return; } long peerIdForRpc = RpcTraceTelemetry.GetPeerIdForRpc(rpc); long localPeerId = RpcTraceTelemetry.GetLocalPeerId(); ZdoPackageTrace zdoPackageTrace = TryParsePackage(package, localPeerId, peerIdForRpc); if (zdoPackageTrace == null) { return; } WritePackageEvent("zdo_package_send", zdoPackageTrace); foreach (ZdoTraceItem item in zdoPackageTrace.Items) { if (ShouldCaptureItem(item)) { WriteRevisionEvent("zdo_revision_send", zdoPackageTrace, item, "sent", force: false); } } } internal static void BeginReceive(ZRpc rpc, ZPackage package) { if (!CanCapture() || package == null) { return; } long peerIdForRpc = RpcTraceTelemetry.GetPeerIdForRpc(rpc); long localPeerId = RpcTraceTelemetry.GetLocalPeerId(); ZdoPackageTrace zdoPackageTrace = TryParsePackage(package, peerIdForRpc, localPeerId); if (zdoPackageTrace == null || (_activeReceiveContext != null && _activeReceiveContext.Trace.Matches(zdoPackageTrace))) { return; } EndReceive(); ReceiveContext receiveContext = (_activeReceiveContext = new ReceiveContext(zdoPackageTrace)); WritePackageEvent("zdo_package_receive", zdoPackageTrace); foreach (ZdoTraceItem item in zdoPackageTrace.Items) { PopulateReceiveOutcome(item); if (ShouldCaptureItem(item)) { item.Captured = WriteRevisionEvent("zdo_revision_receive", zdoPackageTrace, item, item.ApplyOutcome, force: false); if (ShouldDeserialize(item)) { receiveContext.ApplyItems.Add(item); } else if (item.Captured) { WriteRevisionEvent("zdo_revision_skip", zdoPackageTrace, item, item.ApplyOutcome, force: true); } } } } internal static void BeginReceiveFromRpcPackage(ZRpc rpc, ZPackage package) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown if (!CanCapture() || package == null) { return; } try { ZPackage val = new ZPackage(package.GetArray()); if (val.ReadInt() == StringExtensionMethods.GetStableHashCode("ZDOData")) { BeginReceive(rpc, val.ReadPackage()); } } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to read inbound ZDOData RPC package: " + ex.GetType().Name + ": " + ex.Message)); } } internal static void EndReceive() { _activeReceiveContext = null; } internal static void OnDeserializeComplete(ZDO zdo) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) ReceiveContext activeReceiveContext = _activeReceiveContext; if (activeReceiveContext != null && zdo != null) { ZdoTraceItem zdoTraceItem = activeReceiveContext.TakeNextApplyItem(zdo.m_uid); if (zdoTraceItem != null && zdoTraceItem.Captured) { WriteRevisionEvent("zdo_revision_apply", activeReceiveContext.Trace, zdoTraceItem, zdoTraceItem.ApplyOutcome, force: true); } } } private static bool CanCapture() { if (PraetorisClientPlugin.ZdoTraceEnabled.Value) { return RpcTraceTelemetry.CanCaptureZdoTrace(); } return false; } private static ZdoPackageTrace? TryParsePackage(ZPackage package, long senderPeerId, long receiverPeerId) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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) try { byte[] array = package.GetArray(); string packageHash = HashBytes(array); string packageId = BuildPackageId(senderPeerId, receiverPeerId, packageHash); ZPackage val = new ZPackage(array); int num = val.ReadInt(); for (int i = 0; i < num; i++) { val.ReadZDOID(); } List list = new List(); while (val.GetPos() < val.Size()) { ZDOID val2 = val.ReadZDOID(); if (((ZDOID)(ref val2)).IsNone()) { break; } ushort ownerRevision = val.ReadUShort(); uint dataRevision = val.ReadUInt(); long ownerPeerId = val.ReadLong(); Vector3 position = val.ReadVector3(); ZPackage obj = val.ReadPackage(); int itemPayloadBytes = obj.Size(); ZdoPayloadHeader zdoPayloadHeader = ReadPayloadHeader(obj); string prefabName = ResolvePrefabName(zdoPayloadHeader.PrefabHash); string zdoIdText = FormatZdoId(val2); list.Add(new ZdoTraceItem(val2, zdoIdText, BuildZdoTraceId(val2, dataRevision), ownerRevision, dataRevision, ownerPeerId, position, itemPayloadBytes, zdoPayloadHeader.Flags, zdoPayloadHeader.ExtraDataMask, zdoPayloadHeader.PrefabHash, prefabName)); } return new ZdoPackageTrace(packageId, packageHash, senderPeerId, receiverPeerId, array.Length, num, list); } catch (Exception ex) { PraetorisClientPlugin.Log.LogWarning((object)("Failed to parse ZDOData trace package: " + ex.GetType().Name + ": " + ex.Message)); return null; } } private static void PopulateReceiveOutcome(ZdoTraceItem item) { //IL_0010: 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) ZDO val = ((ZDOMan.instance != null) ? ZDOMan.instance.GetZDO(item.ZdoId) : null); item.ExistedBefore = val != null; item.LocalDataRevision = ((val != null) ? val.DataRevision : 0u); item.LocalOwnerRevision = (ushort)((val != null) ? val.OwnerRevision : 0); if (val == null) { item.ApplyOutcome = (IsServerDeadZdo(item.ZdoId) ? "created_then_destroyed" : "created"); } else if (item.DataRevision > val.DataRevision) { item.ApplyOutcome = "updated"; } else { item.ApplyOutcome = ((item.OwnerRevision > val.OwnerRevision) ? "owner_only" : "stale"); } } private static bool ShouldDeserialize(ZdoTraceItem item) { if (!(item.ApplyOutcome == "created") && !(item.ApplyOutcome == "created_then_destroyed")) { return item.ApplyOutcome == "updated"; } return true; } private static bool ShouldCaptureItem(ZdoTraceItem item) { RefreshFilters(); lock (FilterSync) { if (item.PrefabHash != 0 && PrefabFilter.Contains(item.PrefabHash)) { return true; } if (ZdoIdFilter.Contains(item.ZdoIdText)) { return true; } } float num = Math.Max(0f, PraetorisClientPlugin.ZdoTraceSampleRate.Value); if (num >= 1f) { return true; } if (num <= 0f) { return false; } return (double)(uint)StringExtensionMethods.GetStableHashCode(item.ZdoTraceId) / 4294967295.0 < (double)num; } private static void RefreshFilters() { string text = PraetorisClientPlugin.ZdoTracePrefabFilter.Value ?? ""; string text2 = PraetorisClientPlugin.ZdoTraceZdoIdFilter.Value ?? ""; lock (FilterSync) { string[] array; if (!string.Equals(text, _lastPrefabFilter, StringComparison.Ordinal)) { PrefabFilter.Clear(); array = text.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text3 = array[i].Trim(); if (text3.Length != 0) { if (int.TryParse(text3, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { PrefabFilter.Add(result); } else { PrefabFilter.Add(StringExtensionMethods.GetStableHashCode(text3)); } } } _lastPrefabFilter = text; } if (string.Equals(text2, _lastZdoIdFilter, StringComparison.Ordinal)) { return; } ZdoIdFilter.Clear(); array = text2.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text4 = array[i].Trim(); if (text4.Length > 0) { ZdoIdFilter.Add(text4); } } _lastZdoIdFilter = text2; } } private static bool TryReserveEvent() { int value = PraetorisClientPlugin.ZdoTraceMaxEventsPerSecond.Value; if (value <= 0) { return true; } int num = Mathf.FloorToInt(Time.realtimeSinceStartup); lock (FilterSync) { if (num != _rateSecond) { _rateSecond = num; _rateCount = 0; } if (_rateCount >= value) { return false; } _rateCount++; return true; } } private static void WritePackageEvent(string eventName, ZdoPackageTrace trace) { if (TryReserveEvent()) { long localPeerId = RpcTraceTelemetry.GetLocalPeerId(); TelemetryJson telemetryJson = RpcTraceTelemetry.ObjectWithEnvelope(eventName, localPeerId); telemetryJson.Prop("role", "client"); AddPackageFields(telemetryJson, trace, localPeerId); RpcTraceTelemetry.AddClockFields(telemetryJson); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); } } private static bool WriteRevisionEvent(string eventName, ZdoPackageTrace trace, ZdoTraceItem item, string outcome, bool force) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) if (!force && !TryReserveEvent()) { return false; } long localPeerId = RpcTraceTelemetry.GetLocalPeerId(); TelemetryJson telemetryJson = RpcTraceTelemetry.ObjectWithEnvelope(eventName, localPeerId); telemetryJson.Prop("role", "client"); AddPackageFields(telemetryJson, trace, localPeerId); telemetryJson.Prop("zdoTraceId", item.ZdoTraceId); telemetryJson.Prop("zdoId", item.ZdoIdText); telemetryJson.Prop("targetZdo", item.ZdoIdText); ZDOID zdoId = item.ZdoId; telemetryJson.Prop("zdoUserId", ((ZDOID)(ref zdoId)).UserID); zdoId = item.ZdoId; telemetryJson.Prop("zdoLocalId", (long)((ZDOID)(ref zdoId)).ID); telemetryJson.Prop("zdoDataRevision", item.DataRevision); telemetryJson.Prop("zdoOwnerRevision", item.OwnerRevision); telemetryJson.Prop("zdoOwnerPeerId", item.OwnerPeerId); telemetryJson.Prop("zdoPrefabHash", item.PrefabHash); telemetryJson.Prop("zdoPrefabName", item.PrefabName); telemetryJson.Prop("zdoPayloadFlags", item.PayloadFlags); telemetryJson.Prop("zdoExtraDataMask", item.ExtraDataMask); telemetryJson.Prop("zdoHasExtraData", item.HasExtraData); telemetryJson.Prop("positionX", item.Position.x); telemetryJson.Prop("positionY", item.Position.y); telemetryJson.Prop("positionZ", item.Position.z); telemetryJson.Prop("itemPayloadBytes", item.ItemPayloadBytes); telemetryJson.Prop("payloadBytes", item.ItemPayloadBytes); telemetryJson.Prop("applyOutcome", outcome); telemetryJson.Prop("existedBefore", item.ExistedBefore); telemetryJson.Prop("localDataRevision", item.LocalDataRevision); telemetryJson.Prop("localOwnerRevision", item.LocalOwnerRevision); RpcTraceTelemetry.AddClockFields(telemetryJson); telemetryJson.End(); RpcTraceLocalStore.Append(telemetryJson.ToString(), localPeerId); return true; } private static void AddPackageFields(TelemetryJson json, ZdoPackageTrace trace, long localPeerId) { json.Prop("zdoPackageId", trace.PackageId); json.Prop("packageHash", trace.PackageHash); json.Prop("senderPeerId", trace.SenderPeerId); json.Prop("receiverPeerId", trace.ReceiverPeerId); json.Prop("localPeerId", localPeerId); json.Prop("payloadBytes", trace.PackageBytes); json.Prop("zdoCount", trace.Items.Count); json.Prop("invalidSectorCount", trace.InvalidSectorCount); } private static ZdoPayloadHeader ReadPayloadHeader(ZPackage payload) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) try { ZPackage val = new ZPackage(payload.GetArray()); int num = val.ReadUShort(); int prefabHash = val.ReadInt(); return new ZdoPayloadHeader(num, num & 0xFF, prefabHash); } catch { return new ZdoPayloadHeader(0, 0, 0); } } private static string ResolvePrefabName(int prefabHash) { if (prefabHash == 0 || (Object)(object)ZNetScene.instance == (Object)null) { return ""; } try { GameObject prefab = ZNetScene.instance.GetPrefab(prefabHash); return ((Object)(object)prefab != (Object)null) ? ((Object)prefab).name : ""; } catch { return ""; } } private static bool IsServerDeadZdo(ZDOID id) { //IL_0066: Unknown result type (might be due to invalid IL or missing references) try { if (ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return false; } if ((object)_deadZdosField == null) { _deadZdosField = typeof(ZDOMan).GetField("m_deadZDOs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } if (_deadZdosField?.GetValue(ZDOMan.instance) is Dictionary dictionary) { return dictionary.ContainsKey(id); } } catch { } return false; } private static string BuildPackageId(long senderPeerId, long receiverPeerId, string packageHash) { long num = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); return num.ToString(CultureInfo.InvariantCulture) + ":" + senderPeerId.ToString(CultureInfo.InvariantCulture) + ":" + receiverPeerId.ToString(CultureInfo.InvariantCulture) + ":" + packageHash; } private static string BuildZdoTraceId(ZDOID id, uint dataRevision) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) long num = ((ZNet.m_world != null) ? ZNet.m_world.m_uid : 0); return num.ToString(CultureInfo.InvariantCulture) + ":" + FormatZdoId(id) + ":" + dataRevision.ToString(CultureInfo.InvariantCulture); } private static string FormatZdoId(ZDOID id) { if (!((ZDOID)(ref id)).IsNone()) { return ((ZDOID)(ref id)).UserID.ToString(CultureInfo.InvariantCulture) + ":" + ((ZDOID)(ref id)).ID.ToString(CultureInfo.InvariantCulture); } return ""; } private static string HashBytes(byte[] bytes) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(bytes); StringBuilder stringBuilder = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } } }