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 PurpleTurtle Companion v1.3.11
BepInEx/plugins/PurpleTurtleCompanion/MagiCorp.PurpleTurtleCompanion.Client.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("MagiCorp.PurpleTurtleCompanion.Client")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+94a0159cc17e2ecee7b16678996a689ed3702a2d")] [assembly: AssemblyProduct("MagiCorp.PurpleTurtleCompanion.Client")] [assembly: AssemblyTitle("MagiCorp.PurpleTurtleCompanion.Client")] [assembly: AssemblyVersion("1.0.0.0")] namespace MagiCorp.PurpleTurtleCompanion; [BepInPlugin("magicorp.purpleturtle.companion.client", "Purple Turtle Companion", "1.3.11")] public sealed class ClientCompanion : BaseUnityPlugin { private sealed class DamageState { public float Before; public float MaxHealth; public string Attacker = ""; public string Victim = ""; public string AttackerPrefab = ""; public string VictimPrefab = ""; public string AttackerId = ""; public string VictimId = ""; public bool AttackerPlayer; public bool VictimPlayer; public bool AttackerBoss; public bool VictimBoss; } private const string PluginVersion = "1.3.11"; private const string EventRpc = "MagiCorp_QolAudit_v1"; private const string HelloRpc = "MagiCorp_QolHello_v1"; private const string AckRpc = "MagiCorp_QolAck_v1"; private const string StatsRpc = "MagiCorp_PlayerStats_v1"; private const string CombatRpc = "MagiCorp_Combat_v1"; private const string FogRpc = "MagiCorp_MapFog_v1"; private const string NetProbeRpc = "MagiCorp_NetProbe_v1"; private const string NetReplyRpc = "MagiCorp_NetReply_v1"; private const string ServerChatRpc = "MagiCorp_ServerChat_v1"; private const string DeathRpc = "MagiCorp_PlayerDeath_v1"; private static ClientCompanion I; private Harmony harmony; private bool combatHooked; private bool serverNativeFog; private bool fogSyncedThisConnection; private ConfigEntry<bool> RepeatFogSync; private ConfigEntry<float> FogSyncIntervalSeconds; private float nextScan; private float nextHello; private float nextStats; private float nextStatsForce; private float nextFogForce; private float nextAdminScan; private float lastDeathSent; private string lastStatsPayload = ""; private string lastFogPayload = ""; private string adminToolVersion = "absent"; private string fogState = "pending"; private int adminHooks; private int adminHookFailures; private int transportSamples; private int transportJitterSamples; private int transportPing; private double transportPingSum; private double transportPingMin = double.MaxValue; private double transportPingMax; private double transportJitterSum; private double transportPrevPing; private float transportLq = -1f; private float transportRq = -1f; private float transportOutBps; private float transportInBps; private ZRpc serverRpc; private bool ack; private readonly Queue<string> pending = new Queue<string>(); private readonly HashSet<MethodBase> patchedAdminMethods = new HashSet<MethodBase>(); private static readonly FieldInfo FogOwnField = AccessTools.Field(typeof(Minimap), "m_explored"); private static readonly FieldInfo FogTextureSizeField = AccessTools.Field(typeof(Minimap), "m_textureSize"); private static readonly FieldInfo FogPixelSizeField = AccessTools.Field(typeof(Minimap), "m_pixelSize"); private string lastAuditKey = ""; private float lastAuditAt; private static readonly MethodInfo InstantLootSpawnMethod = typeof(Ragdoll).GetMethod("SpawnLoot", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(Vector3) }, null); private void Awake() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown I = this; RepeatFogSync = ((BaseUnityPlugin)this).Config.Bind<bool>("Fog", "RepeatSync", false, "Repeat client-side fog scans after the first successful sync. Purple Turtle uses server-native fog, so this should normally stay disabled."); FogSyncIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Fog", "RepeatIntervalSeconds", 30f, "Seconds between legacy client fog scans when RepeatSync is enabled (5-600). Ignored when the server provides native shared fog."); harmony = new Harmony("magicorp.purpleturtle.companion.client"); TryHookCombat(); TryHookPlayerDeath(); TryHookInstantLoot(); TryHookAdminTool(force: true); ((MonoBehaviour)this).InvokeRepeating("FogTick", 5f, Mathf.Clamp(FogSyncIntervalSeconds.Value, 5f, 600f)); ((MonoBehaviour)this).InvokeRepeating("NetTick", 1f, 1f); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Purple Turtle Companion 1.3.11 loaded. Live stats, combat telemetry and AdminTool auditing enabled for Purple Turtle."); } private void OnDestroy() { ((MonoBehaviour)this).CancelInvoke("FogTick"); ((MonoBehaviour)this).CancelInvoke("NetTick"); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } I = null; } private void Update() { if (Time.unscaledTime < nextScan) { return; } nextScan = Time.unscaledTime + 0.25f; try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { ResetConnection(); return; } TryHookAdminTool(); TryHookCombat(); ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer == null || !serverPeer.IsReady()) { ResetConnection(); return; } if (serverRpc != serverPeer.m_rpc) { ResetConnection(); serverRpc = serverPeer.m_rpc; serverRpc.Register<string>("MagiCorp_QolAck_v1", (Action<ZRpc, string>)delegate(ZRpc rpc, string v) { if (rpc == serverRpc) { ack = true; serverNativeFog = !string.IsNullOrEmpty(v) && v.IndexOf("fog=server-native", StringComparison.OrdinalIgnoreCase) >= 0; if (serverNativeFog) { fogState = "server-native"; } } }); serverRpc.Register<string>("MagiCorp_NetProbe_v1", (Action<ZRpc, string>)OnNetProbe); serverRpc.Register<string, string>("MagiCorp_ServerChat_v1", (Action<ZRpc, string, string>)OnServerChat); } if (Time.unscaledTime >= nextHello) { nextHello = Time.unscaledTime + 10f; string text = ((adminToolVersion == "absent") ? "AdminTool=absent" : $"AdminTool={adminToolVersion};hooks={adminHooks};failures={adminHookFailures}"); serverRpc.Invoke("MagiCorp_QolHello_v1", new object[1] { "1.3.11;Purple Turtle Companion;coverage=active;netprobe=1;" + text + ";" + ClientNetworkInfo() + ";fog=" + fogState }); } if ((Object)(object)Player.m_localPlayer != (Object)null && Time.unscaledTime >= nextStats) { nextStats = Time.unscaledTime + 15f; SendStats(); } if (!ack) { return; } for (int num = 0; num < 24; num++) { if (pending.Count <= 0) { break; } serverRpc.Invoke("MagiCorp_QolAudit_v1", new object[1] { pending.Dequeue() }); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)ex.Message); } } private void ResetConnection() { serverRpc = null; ack = false; serverNativeFog = false; fogSyncedThisConnection = false; nextHello = 0f; nextStats = 0f; nextStatsForce = 0f; nextFogForce = 0f; lastStatsPayload = ""; lastFogPayload = ""; pending.Clear(); } private void OnNetProbe(ZRpc rpc, string sequence) { if (serverRpc == null || rpc != serverRpc || string.IsNullOrEmpty(sequence)) { return; } try { serverRpc.Invoke("MagiCorp_NetReply_v1", new object[1] { sequence }); } catch { } } private void OnServerChat(ZRpc rpc, string title, string text) { if (serverRpc == null || rpc != serverRpc || string.IsNullOrWhiteSpace(text)) { return; } try { if ((Object)(object)Chat.instance != (Object)null) { ((Terminal)Chat.instance).AddString(string.IsNullOrWhiteSpace(title) ? "Purple Turtle" : title, text, (Type)1, false); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Server chat display failed: " + ex.Message)); } } private void TryHookPlayerDeath() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ClientCompanion), "PlayerDeathPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Player death hook failed: " + ex.Message)); } } private static void PlayerDeathPostfix(Player __instance) { try { I?.ReportDeath(__instance); } catch { } } private void ReportDeath(Player p) { if ((Object)(object)p == (Object)null || (Object)(object)p != (Object)(object)Player.m_localPlayer || serverRpc == null || !ack || Time.unscaledTime - lastDeathSent < 3f) { return; } lastDeathSent = Time.unscaledTime; try { serverRpc.Invoke("MagiCorp_PlayerDeath_v1", new object[1] { Clean(p.GetPlayerName(), 80) }); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Death report failed: " + ex.Message)); } } private void TryHookInstantLoot() { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(Ragdoll), "Setup", (Type[])null, (Type[])null); if (methodInfo == null || InstantLootSpawnMethod == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Instant loot disabled: required vanilla Ragdoll methods not found."); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ClientCompanion), "InstantLootPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Instant loot enabled for Purple Turtle (safe reflection path)."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Instant loot hook failed: " + ex)); } } private static void InstantLootPostfix(Ragdoll __instance) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)I == (Object)null || !I.ack || (Object)(object)__instance == (Object)null) { return; } try { ZNetView component = ((Component)__instance).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { return; } ZDO zDO = component.GetZDO(); if (zDO == null || zDO.GetInt(ZDOVars.s_drops, 0) <= 0) { return; } Vector3 val = (((Object)(object)__instance.m_lootSpawnJoint != (Object)null) ? __instance.m_lootSpawnJoint.transform.position : __instance.GetAverageBodyPosition()); try { InstantLootSpawnMethod.Invoke(__instance, new object[1] { val }); zDO.Set(ZDOVars.s_drops, 0, false); } catch (TargetInvocationException ex) { Exception? obj = ex.InnerException ?? ex; ((BaseUnityPlugin)I).Logger.LogError((object)("Instant loot vanilla SpawnLoot failed; corpse loot retained for normal fade drop: " + obj)); } } catch (Exception ex2) { ((BaseUnityPlugin)I).Logger.LogError((object)("Instant loot failed safely; vanilla death flow continues: " + ex2)); } } private void Report(string feature, string detail) { if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || (Object)(object)Player.m_localPlayer == (Object)null) { return; } feature = Clean(feature, 100); detail = Clean(detail, 800); string text = feature + "\t" + detail; if (!(text == lastAuditKey) || !(Time.unscaledTime - lastAuditAt < 0.35f)) { lastAuditKey = text; lastAuditAt = Time.unscaledTime; if (pending.Count >= 1024) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Purple Turtle Companion audit queue full; report dropped"); return; } pending.Enqueue(feature + "\t" + detail); ((BaseUnityPlugin)this).Logger.LogWarning((object)("[PurpleTurtleAudit] " + feature + ": " + Clean(detail, 320))); } } private void FogTick() { if (serverNativeFog) { fogState = "server-native"; } else if (fogSyncedThisConnection && !RepeatFogSync.Value) { fogState = "one-shot-complete"; } else { if ((Object)(object)Player.m_localPlayer == (Object)null) { return; } fogState = "attempt"; try { SendFog(); if (fogState.StartsWith("sent-", StringComparison.Ordinal) || fogState.StartsWith("ok-", StringComparison.Ordinal)) { fogSyncedThisConnection = true; } } catch (Exception ex) { fogState = "timer-error-" + Clean(ex.GetType().Name, 32); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Fog timer failed: " + ex.Message)); } } } private void SendFog() { if (!ack || serverRpc == null || (Object)(object)Player.m_localPlayer == (Object)null) { fogState = "waiting"; return; } if ((Object)(object)Minimap.instance == (Object)null) { fogState = "no-minimap"; return; } try { Minimap instance = Minimap.instance; BitArray bitArray = FogOwnField?.GetValue(instance) as BitArray; int num = 0; float num2 = 0f; try { if (FogTextureSizeField?.GetValue(instance) is int num3) { num = num3; } } catch { } try { if (FogPixelSizeField?.GetValue(instance) is float num4) { num2 = num4; } } catch { } if (bitArray == null || bitArray.Length <= 0) { fogState = "no-explored"; return; } int length = bitArray.Length; int num5 = (int)Math.Round(Math.Sqrt(length)); if (num5 < 64 || num5 > 4096 || num5 * num5 != length) { fogState = "bad-mask-" + length.ToString(CultureInfo.InvariantCulture); return; } if (num2 <= 0f || float.IsNaN(num2) || float.IsInfinity(num2)) { fogState = "bad-pixel-size"; return; } byte[] array = new byte[13613]; int num6 = 0; float num7 = (float)num5 / 2f; float num8 = 165f; for (int i = 0; i < 330; i++) { int num9 = Mathf.RoundToInt(((float)i - num8) * 64f / num2 + num7); if (num9 < 0 || num9 >= num5) { continue; } for (int j = 0; j < 330; j++) { int num10 = Mathf.RoundToInt(((float)j - num8) * 64f / num2 + num7); if (num10 >= 0 && num10 < num5) { int index = num9 * num5 + num10; if (bitArray[index]) { int num11 = i * 330 + j; array[num11 >> 3] |= (byte)(1 << (num11 & 7)); num6++; } } } } string text = 330.ToString(CultureInfo.InvariantCulture) + "\t" + Convert.ToBase64String(array); bool flag = Time.unscaledTime >= nextFogForce; if (text != lastFogPayload || flag) { serverRpc.Invoke("MagiCorp_MapFog_v1", new object[1] { text }); lastFogPayload = text; nextFogForce = Time.unscaledTime + 300f; fogState = "sent-" + num6.ToString(CultureInfo.InvariantCulture) + "of" + 108900.ToString(CultureInfo.InvariantCulture); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Fog sync: {num6}/{108900} website cells from own map (source {num5}x{num5}, minimap pixel {num2:0.###}m, texture {num})"); } else { fogState = "ok-" + num6.ToString(CultureInfo.InvariantCulture) + "of" + 108900.ToString(CultureInfo.InvariantCulture); } } catch (Exception ex) { fogState = "error-" + Clean(ex.GetType().Name, 40); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Fog telemetry failed: " + ex.Message)); } } private void NetTick() { try { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null); if (val?.m_socket != null) { float num = default(float); float num2 = default(float); int val2 = default(int); float val3 = default(float); float val4 = default(float); val.m_socket.GetConnectionQuality(ref num, ref num2, ref val2, ref val3, ref val4); double num3 = Math.Max(0, val2); transportPing = val2; transportLq = num; transportRq = num2; transportOutBps = Math.Max(0f, val3); transportInBps = Math.Max(0f, val4); transportSamples++; transportPingSum += num3; transportPingMin = Math.Min(transportPingMin, num3); transportPingMax = Math.Max(transportPingMax, num3); if (transportSamples > 1) { transportJitterSum += Math.Abs(num3 - transportPrevPing); transportJitterSamples++; } transportPrevPing = num3; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Transport sample failed: " + ex.Message)); } } private string ClientNetworkInfo() { try { if (transportSamples <= 0) { return "transport=unavailable"; } double num = transportPingSum / (double)transportSamples; double num2 = ((transportPingMin == double.MaxValue) ? 0.0 : transportPingMin); double num3 = ((transportJitterSamples > 0) ? (transportJitterSum / (double)transportJitterSamples) : 0.0); return "transport=" + transportPing.ToString(CultureInfo.InvariantCulture) + "," + transportLq.ToString("0.###", CultureInfo.InvariantCulture) + "," + transportRq.ToString("0.###", CultureInfo.InvariantCulture) + "," + transportOutBps.ToString("0", CultureInfo.InvariantCulture) + "," + transportInBps.ToString("0", CultureInfo.InvariantCulture) + "," + num.ToString("0.0", CultureInfo.InvariantCulture) + "," + num2.ToString("0.0", CultureInfo.InvariantCulture) + "," + transportPingMax.ToString("0.0", CultureInfo.InvariantCulture) + "," + num3.ToString("0.0", CultureInfo.InvariantCulture) + "," + transportSamples.ToString(CultureInfo.InvariantCulture); } catch (Exception ex) { return "transport=error-" + Clean(ex.GetType().Name, 32); } } private void SendStats() { if (!ack || serverRpc == null || (Object)(object)Game.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } try { string text = BuildStatsJson(); if (!string.IsNullOrEmpty(text)) { bool flag = Time.unscaledTime >= nextStatsForce; if (text != lastStatsPayload || flag) { serverRpc.Invoke("MagiCorp_PlayerStats_v1", new object[1] { text }); lastStatsPayload = text; nextStatsForce = Time.unscaledTime + 300f; } } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogDebug((object)("Stats telemetry failed: " + ex.Message)); } } private string BuildStatsJson() { PlayerProfile playerProfile = Game.instance.GetPlayerProfile(); if (playerProfile == null) { return null; } Array array = (Array)(typeof(PlayerProfile).GetField("m_playerStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(playerProfile)); if (array == null || array.Length == 0) { return null; } object value = array.GetValue(0); if (value == null) { return null; } Type type = value.GetType(); StringBuilder stringBuilder = new StringBuilder(8192); stringBuilder.Append('{').Append("\"schema\":2,\"character\":\"").Append(JsonEscape(playerProfile.GetName())) .Append("\",\"player_id\":") .Append(playerProfile.GetPlayerID()); AppendMap(stringBuilder, "stats", type.GetField("m_stats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value) as IDictionary); object? obj = type.GetField("m_enemyStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value); IDictionary dict = null; if (obj is Array { Length: >0 } array2) { dict = array2.GetValue(0) as IDictionary; } AppendMap(stringBuilder, "enemies", dict); AppendMap(stringBuilder, "food", type.GetField("m_foodEatenStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value) as IDictionary); AppendMap(stringBuilder, "pieces", type.GetField("m_piecesPlacedStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value) as IDictionary); AppendMap(stringBuilder, "crafted", type.GetField("m_itemCraftStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value) as IDictionary); AppendMap(stringBuilder, "picked_up", type.GetField("m_itemPickupStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value) as IDictionary); AppendMap(stringBuilder, "harvested", type.GetField("m_pickableStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value) as IDictionary); stringBuilder.Append('}'); return stringBuilder.ToString(); } private static void AppendMap(StringBuilder sb, string name, IDictionary dict) { sb.Append(",\"").Append(name).Append("\":{"); if (dict != null) { List<KeyValuePair<string, float>> list = new List<KeyValuePair<string, float>>(); foreach (DictionaryEntry item in dict) { float value; try { value = Convert.ToSingle(item.Value, CultureInfo.InvariantCulture); } catch { continue; } if (!(Math.Abs(value) < 0.0001f)) { list.Add(new KeyValuePair<string, float>(Convert.ToString(item.Key, CultureInfo.InvariantCulture), value)); } } list.Sort((KeyValuePair<string, float> a, KeyValuePair<string, float> b) => string.CompareOrdinal(a.Key, b.Key)); for (int num = 0; num < list.Count; num++) { if (num > 0) { sb.Append(','); } sb.Append('"').Append(JsonEscape(list[num].Key)).Append("\":") .Append(list[num].Value.ToString("0.###", CultureInfo.InvariantCulture)); } } sb.Append('}'); } private static string JsonEscape(string s) { return (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\r", " ") .Replace("\n", " "); } private void TryHookCombat() { //IL_0043: 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_0065: Expected O, but got Unknown //IL_0065: Expected O, but got Unknown if (combatHooked) { return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(Character), "ApplyDamage", (Type[])null, (Type[])null); if (!(methodInfo == null)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ClientCompanion), "DamagePrefix", (Type[])null), new HarmonyMethod(typeof(ClientCompanion), "DamagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); combatHooked = true; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Combat telemetry hook failed: " + ex.Message)); } } private static string Prefab(Character c) { try { return ((Object)(object)c == (Object)null) ? "" : Utils.GetPrefabName(((Component)c).gameObject); } catch { return ((c != null) ? ((Object)c).name : null) ?? ""; } } private static string CharId(Character c) { //IL_000a: 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) try { return ((Object)(object)c == (Object)null) ? "" : ((object)c.GetZDOID()/*cast due to .constrained prefix*/).ToString(); } catch { return ""; } } private static void DamagePrefix(Character __instance, HitData hit, out DamageState __state) { __state = null; try { if ((Object)(object)I == (Object)null || (Object)(object)__instance == (Object)null || hit == null) { return; } Character attacker = hit.GetAttacker(); bool flag = (Object)(object)attacker != (Object)null && attacker.IsPlayer(); bool flag2 = __instance.IsPlayer(); bool flag3 = (Object)(object)attacker != (Object)null && attacker.IsBoss(); bool flag4 = __instance.IsBoss(); if (!flag && !flag2 && !flag3 && !flag4) { return; } object obj; if (flag) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { obj = val.GetPlayerName(); goto IL_009d; } } obj = (((Object)(object)attacker != (Object)null) ? attacker.GetHoverName() : "environment"); goto IL_009d; IL_00bd: string text; string s = (string)text; string s2; __state = new DamageState { Before = __instance.GetHealth(), MaxHealth = __instance.GetMaxHealth(), Attacker = Clean(s2, 100), Victim = Clean(s, 100), AttackerPlayer = flag, VictimPlayer = flag2, AttackerBoss = flag3, VictimBoss = flag4, AttackerPrefab = Clean(Prefab(attacker), 100), VictimPrefab = Clean(Prefab(__instance), 100), AttackerId = Clean(CharId(attacker), 100), VictimId = Clean(CharId(__instance), 100) }; return; IL_009d: s2 = (string)obj; if (flag2) { Player val2 = (Player)(object)((__instance is Player) ? __instance : null); if (val2 != null) { text = val2.GetPlayerName(); goto IL_00bd; } } text = __instance.GetHoverName(); goto IL_00bd; } catch { } } private static void DamagePostfix(Character __instance, DamageState __state) { try { if (!((Object)(object)I == (Object)null) && __state != null && !((Object)(object)__instance == (Object)null) && I.ack && I.serverRpc != null) { float health = __instance.GetHealth(); float num = __state.Before - health; if (!(num <= 0.001f)) { bool flag = __state.Before > 0.001f && health <= 0.001f; string text = string.Join("\t", "damage", __state.Attacker, __state.Victim, num.ToString("0.###", CultureInfo.InvariantCulture), __state.AttackerPlayer ? "1" : "0", __state.VictimPlayer ? "1" : "0", __state.AttackerPrefab, __state.VictimPrefab, __state.AttackerBoss ? "1" : "0", __state.VictimBoss ? "1" : "0", flag ? "1" : "0", __state.AttackerId, __state.VictimId, __state.MaxHealth.ToString("0.###", CultureInfo.InvariantCulture)); I.serverRpc.Invoke("MagiCorp_Combat_v1", new object[1] { text }); } } } catch { } } private void TryHookAdminTool(bool force = false) { if (!force && Time.unscaledTime < nextAdminScan) { return; } nextAdminScan = Time.unscaledTime + 5f; try { Type type = AccessTools.TypeByName("ValheimAdminTool.AdminActions"); if (type == null) { adminToolVersion = "absent"; adminHooks = 0; adminHookFailures = 0; return; } Assembly assembly = type.Assembly; adminToolVersion = GetAdminToolVersion(assembly); int added = 0; int failed = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo m in methods) { if (ShouldHookAdminAction(m)) { PatchAudit(m, ref added, ref failed); } } PatchNamed(assembly, "ValheimAdminTool.AdminEsp", "SetCategoryEnabled", ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminSpeeds", "SetPercent", ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminFun", "SetPercent", ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminPickup", "SetRadius", ref added, ref failed); PatchMapTeleport(assembly, ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminVirtualWorkbenches", "Open", ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminToolRpcs", "RequestTerrainReset", ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminToolRpcs", "RequestWorldReset", ref added, ref failed); PatchNamed(assembly, "ValheimAdminTool.AdminToolRpcs", "RequestTool", ref added, ref failed); adminHooks = patchedAdminMethods.Count; adminHookFailures += failed; if (added > 0 || force) { ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Valheim Admin Tool {adminToolVersion} audit coverage: {adminHooks} methods active" + ((adminHookFailures > 0) ? $", {adminHookFailures} failed" : ""))); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("AdminTool audit scan failed: " + ex.Message)); } } private static bool ShouldHookAdminAction(MethodInfo m) { string name = m.Name; if (name.StartsWith("On", StringComparison.Ordinal) && (name.EndsWith("Toggled", StringComparison.Ordinal) || name.EndsWith("Clicked", StringComparison.Ordinal) || name.EndsWith("Changed", StringComparison.Ordinal))) { return true; } if (name.StartsWith("Spawn", StringComparison.Ordinal)) { return true; } if (name == "SetSkill") { return true; } return false; } private string GetAdminToolVersion(Assembly asm) { try { FieldInfo fieldInfo = asm.GetType("ValheimAdminTool.PluginInfo")?.GetField("Version", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null) { return Convert.ToString(fieldInfo.GetRawConstantValue(), CultureInfo.InvariantCulture) ?? "unknown"; } } catch { } return asm.GetName().Version?.ToString() ?? "unknown"; } private void PatchNamed(Assembly asm, string typeName, string methodName, ref int added, ref int failed) { Type type = asm.GetType(typeName); if (type == null) { return; } foreach (MethodInfo item in from x in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where x.Name == methodName select x) { PatchAudit(item, ref added, ref failed); } } private void PatchMapTeleport(Assembly asm, ref int added, ref int failed) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_009c: Expected O, but got Unknown Type type = asm.GetType("ValheimAdminTool.AdminMapTeleport"); if (type == null) { return; } foreach (MethodInfo item in from x in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where x.Name == "HandleClick" select x) { if (!patchedAdminMethods.Contains(item)) { try { harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(ClientCompanion), "MapTeleportPrefix", (Type[])null), new HarmonyMethod(typeof(ClientCompanion), "MapTeleportPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); patchedAdminMethods.Add(item); added++; } catch (Exception ex) { failed++; ((BaseUnityPlugin)this).Logger.LogWarning((object)("AdminTool map teleport hook failed: " + ex.Message)); } } } } private static void MapTeleportPrefix(ref Vector3 __state) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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_0033: Unknown result type (might be due to invalid IL or missing references) try { __state = (Vector3)(((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : new Vector3(float.NaN, float.NaN, float.NaN)); } catch { __state = new Vector3(float.NaN, float.NaN, float.NaN); } } private static void MapTeleportPostfix(Vector3 __state) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: 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) try { if (!((Object)(object)I == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && !float.IsNaN(__state.x)) { Vector3 position = ((Component)Player.m_localPlayer).transform.position; float num = Vector3.Distance(__state, position); if (!(num < 20f)) { I.Report("AdminTool.MapTeleport", string.Format(CultureInfo.InvariantCulture, "teleported {0:0.0}m: ({1:0.0}, {2:0.0}, {3:0.0}) -> ({4:0.0}, {5:0.0}, {6:0.0})", num, __state.x, __state.y, __state.z, position.x, position.y, position.z)); } } } catch (Exception ex) { ClientCompanion i = I; if (i != null) { ((BaseUnityPlugin)i).Logger.LogDebug((object)("AdminTool map teleport audit failed: " + ex.Message)); } } } private void PatchAudit(MethodBase m, ref int added, ref int failed) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (m == null || patchedAdminMethods.Contains(m)) { return; } try { harmony.Patch(m, new HarmonyMethod(typeof(ClientCompanion), "AdminAuditPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); patchedAdminMethods.Add(m); added++; } catch (Exception ex) { failed++; ((BaseUnityPlugin)this).Logger.LogWarning((object)("AdminTool hook failed: " + m.DeclaringType?.Name + "." + m.Name + ": " + ex.Message)); } } private static void AdminAuditPrefix(MethodBase __originalMethod, object[] __args) { if ((Object)(object)I == (Object)null || __originalMethod == null) { return; } try { string owner = __originalMethod.DeclaringType?.Name ?? "AdminTool"; string name = __originalMethod.Name; ParameterInfo[] parameters = __originalMethod.GetParameters(); List<string> list = new List<string>(); for (int i = 0; i < ((__args != null) ? __args.Length : 0); i++) { string text = ((i < parameters.Length) ? parameters[i].Name : ("arg" + i)); list.Add(text + "=" + Describe(__args[i])); } string feature = HumanFeature(owner, name); I.Report(feature, (list.Count == 0) ? "attempt" : ("attempt: " + string.Join(" ", list))); } catch (Exception ex) { ((BaseUnityPlugin)I).Logger.LogDebug((object)("AdminTool audit formatting failed: " + ex.Message)); } } private static string HumanFeature(string owner, string method) { if (method.StartsWith("On") && method.EndsWith("Toggled")) { return "AdminTool." + method.Substring(2, method.Length - 9); } if (method.StartsWith("On") && method.EndsWith("Clicked")) { return "AdminTool." + method.Substring(2, method.Length - 9); } if (method.StartsWith("On") && method.EndsWith("Changed")) { return "AdminTool." + method.Substring(2, method.Length - 9); } switch (owner) { case "AdminEsp": return "AdminTool.ESP"; case "AdminSpeeds": return "AdminTool.Speed"; case "AdminFun": return "AdminTool.Fun"; case "AdminPickup": return "AdminTool.PickupRadius"; case "AdminMapTeleport": return "AdminTool.MapTeleport"; case "AdminVirtualWorkbenches": return "AdminTool.VirtualWorkbench"; default: _ = owner == "AdminToolRpcs"; return "AdminTool." + method; } } private static string Describe(object value) { if (value == null) { return "null"; } Type type = value.GetType(); if (value is string || type.IsPrimitive || type.IsEnum || value is decimal) { return Clean(Convert.ToString(value, CultureInfo.InvariantCulture), 140); } string[] array = new string[4] { "PrefabName", "DisplayName", "Name", "Id" }; foreach (string name in array) { try { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { object value2 = property.GetValue(value, null); if (value2 != null) { return Clean(Convert.ToString(value2, CultureInfo.InvariantCulture), 140); } } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object value3 = field.GetValue(value); if (value3 != null) { return Clean(Convert.ToString(value3, CultureInfo.InvariantCulture), 140); } } } catch { } } return Clean(Convert.ToString(value, CultureInfo.InvariantCulture) ?? type.Name, 140); } private static string Clean(string s, int max) { s = new string((s ?? "").Select((char c) => (!char.IsControl(c)) ? c : ' ').ToArray()); if (s.Length <= max) { return s; } return s.Substring(0, max); } }