Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PraetorisClient v0.1.31
PraetorisClient.dll
Decompiled 18 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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<long, string, bool, string> 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<string, OverrideZone> Zones = new Dictionary<string, OverrideZone>(); 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<ZNetView>() ?? component.GetComponentInParent<ZNetView>(); ZDO val2 = (((Object)(object)val != (Object)null) ? val.GetZDO() : null); if (val2 != null && IsMarkedCreativeVegetation(val2)) { return true; } return (Object)(object)component.GetComponent<TreeBase>() != (Object)null || (Object)(object)component.GetComponent<TreeLog>() != (Object)null || (Object)(object)component.GetComponent<Pickable>() != (Object)null || (Object)(object)component.GetComponent<MineRock>() != (Object)null || (Object)(object)component.GetComponent<MineRock5>() != (Object)null || (Object)(object)component.GetComponent<DropOnDestroyed>() != (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<OverrideZone> list = new List<OverrideZone>(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<Heightmap>((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<string> 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<CreativeInventoryItem> Items { get; } = new List<CreativeInventoryItem>(); 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<ItemData> Items { get; } = new List<ItemData>(); public static ExtraSlotsReadResult NotLoaded() { return new ExtraSlotsReadResult { ModLoaded = false, Available = true }; } public static ExtraSlotsReadResult Loaded(IEnumerable<ItemData> 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<ItemData> 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<ItemData> list2 = new List<ItemData>(); 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<object>()) is IEnumerable enumerable)) { return ExtraSlotsReadResult.Failed("ExtraSlots API returned no item list."); } List<ItemData> list = new List<ItemData>(); 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<ItemData> target, IEnumerable<ItemData> 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<ZNetView>(); 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<DropData>(), 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<ZNetPeer> FindPeers(string query) { List<ZNetPeer> list = new List<ZNetPeer>(); 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<ZNetPeer>)ZNet.instance.GetConnectedPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((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<string> LinkApiUrl = null; internal static ConfigEntry<string> BotApiKey = null; internal static ConfigEntry<string> LinkCommand = null; internal static ConfigEntry<bool> ValheimEventsTelemetryEnabled = null; internal static ConfigEntry<bool> CombatTelemetryEnabled = null; internal static ConfigEntry<bool> ExplorationTelemetryEnabled = null; internal static ConfigEntry<float> ExplorationFlushSeconds = null; internal static ConfigEntry<bool> RpcTraceEnabled = null; internal static ConfigEntry<bool> RpcTraceCaptureSendReceive = null; internal static ConfigEntry<string> RpcTraceNameDenyList = null; internal static ConfigEntry<bool> RpcTraceHttpUploadPreferred = null; internal static ConfigEntry<bool> ZdoTraceEnabled = null; internal static ConfigEntry<string> ZdoTracePrefabFilter = null; internal static ConfigEntry<string> ZdoTraceZdoIdFilter = null; internal static ConfigEntry<float> ZdoTraceSampleRate = null; internal static ConfigEntry<int> 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<string>("BotApi", "LinkApiUrl", "", "Compatible bot Valheim link endpoint. Prefer the PRAETORISCLIENT_LINK_API_URL environment variable on dedicated servers."); BotApiKey = ((BaseUnityPlugin)this).Config.Bind<string>("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<string>("Linking", "LinkCommand", "!link", "In-game chat command consumed before it is sent as chat."); ValheimEventsTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ValheimEvents", "Enabled", true, SyncedDescription("Sends client-observed telemetry to the server-side ValheimEvents mod.")); CombatTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ValheimEvents", "CombatTelemetry", true, SyncedDescription("Sends client-observed combat and death telemetry.")); ExplorationTelemetryEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ValheimEvents", "ExplorationTelemetry", true, SyncedDescription("Sends client-observed minimap exploration telemetry.")); ExplorationFlushSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("ValheimEvents", "ExplorationFlushSeconds", 2f, SyncedDescription("How long newly explored minimap cells are batched before sending.")); RpcTraceEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("RpcTrace", "Enabled", true, SyncedDescription("Sends client-observed routed RPC trace rows to the server-side ValheimTracer receiver.")); RpcTraceCaptureSendReceive = ((BaseUnityPlugin)this).Config.Bind<bool>("RpcTrace", "CaptureSendReceive", true, SyncedDescription("Captures raw routed RPC send and receive points in addition to handled RPC points.")); RpcTraceNameDenyList = ((BaseUnityPlugin)this).Config.Bind<string>("RpcTrace", "RpcNameDenyList", "", SyncedDescription("Comma-separated routed RPC names to exclude from client trace capture.")); RpcTraceHttpUploadPreferred = ((BaseUnityPlugin)this).Config.Bind<bool>("RpcTrace", "HttpUploadPreferred", true, SyncedDescription("Uses ValheimTracer-issued HTTP upload tokens for trace batches when the server supports it.")); ZdoTraceEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("ZdoTrace", "Enabled", true, "Enables ZDOData package and selected ZDO revision tracing."); ZdoTracePrefabFilter = ((BaseUnityPlugin)this).Config.Bind<string>("ZdoTrace", "PrefabFilter", "", "Comma-separated prefab names or prefab hashes to trace. Empty means no prefab filter."); ZdoTraceZdoIdFilter = ((BaseUnityPlugin)this).Config.Bind<string>("ZdoTrace", "ZdoIdFilter", "", "Comma-separated ZDO ids to trace in user:id format. Empty means no ZDO id filter."); ZdoTraceSampleRate = ((BaseUnityPlugin)this).Config.Bind<float>("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<int>("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<ZPackage>("DiscordTools_LinkRequest", (Action<long, ZPackage>)LinkRpc.OnRequest); ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_LinkResult", (Action<long, ZPackage>)LinkRpc.OnResult); ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_CreativeInventoryRequest", (Action<long, ZPackage>)CreativeInventoryRpc.OnRequest); ZRoutedRpc.instance.Register<ZPackage>("DiscordTools_CreativeBiomeOverride", (Action<long, ZPackage>)CreativeBiomeOverride.OnOverride); ZRoutedRpc.instance.Register<ZPackage>("PraetorisClient_CreativeCommandZoneState", (Action<long, ZPackage>)CreativeCommandZoneState.OnState); ZRoutedRpc.instance.Register<ZPackage>("PraetorisClient_RpcTraceClockResponse", (Action<long, ZPackage>)RpcTraceTelemetry.OnClockResponse); ZRoutedRpc.instance.Register<ZPackage>("PraetorisClient_RpcTraceUploadTokenResponse", (Action<long, ZPackage>)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<string, string> BuildHeaders(string token, string batchId, string runtimeId, string fileId, int batchIndex, bool finalBatch, string flushReason, string modVersion) { return new Dictionary<string, string> { { "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<byte>(), 0, finalBatch: true, endOfFile: true, skippedOversizedRow: false, 0.0); } internal static PreparedUploadBatch SkipOversizedRow() { return new PreparedUploadBatch(Array.Empty<byte>(), 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<string> PendingFiles = new Queue<string>(); 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<PreparedUploadBatch> 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<string, string> headers = RpcTraceHttpUploadContract.BuildHeaders(RpcTraceUploadTokenClient.Token, batchId, RpcTraceTelemetry.RuntimeId, fileId, batchIndex, batch.FinalBatch, flushReason, PraetorisClientPlugin.TraceModVersion); Task<UploadResult> 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<string, string> 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<string, string> 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<string, string> 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<string> 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<string> 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;