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 EilifCompanionClient v0.2.0
EilifCompanionClient.dll
Decompiled 7 hours agousing System; using System.Diagnostics; using System.Globalization; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")] [assembly: AssemblyCompany("EilifCompanionClient")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+5109d837318fa85e77890bf184d65a8f6bb60fec")] [assembly: AssemblyProduct("EilifCompanionClient")] [assembly: AssemblyTitle("EilifCompanionClient")] [assembly: AssemblyVersion("0.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EilifCompanionClient { internal static class EilifDeathReporter { private static readonly FieldInfo LastHitField = AccessTools.Field(typeof(Character), "m_lastHit"); internal static void Report(Player who) { //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_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)who == (Object)null || (Object)(object)who != (Object)(object)Player.m_localPlayer || !EilifMapTrackerPlugin.IsOnServer()) { return; } if (LastHitField == null) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)"[EilifDeath] Character.m_lastHit not found — the game's field layout changed; no death reported."); } return; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } object? value = LastHitField.GetValue(who); HitData val = (HitData)((value is HitData) ? value : null); if (val == null) { ManualLogSource log2 = EilifMapTrackerPlugin.Log; if (log2 != null) { log2.LogWarning((object)"[EilifDeath] no m_lastHit at death — nothing to report."); } return; } string text = ((object)Unsafe.As<HitType, HitType>(ref val.m_hitType)/*cast due to .constrained prefix*/).ToString(); string text2 = ResolveAttacker(val); string text3 = ResolveBiome(who); string playerName = who.GetPlayerName(); string text4 = instance.GetWorldName() ?? ""; if (!string.IsNullOrEmpty(playerName)) { Vector3 position = ((Component)who).transform.position; string s = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture); string json = "{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"eilif-death\",\"world\":" + EilifMapTrackerPlugin.JsonStr(text4) + ",\"player\":" + EilifMapTrackerPlugin.JsonStr(playerName) + ",\"tsUtc\":" + EilifMapTrackerPlugin.JsonStr(s) + ",\"hitType\":" + EilifMapTrackerPlugin.JsonStr(text) + ",\"attacker\":" + ((text2 == null) ? "null" : EilifMapTrackerPlugin.JsonStr(text2)) + ",\"biome\":" + EilifMapTrackerPlugin.JsonStr(text3) + ",\"pos\":{\"x\":" + Fmt(position.x) + ",\"z\":" + Fmt(position.z) + "}}"; ManualLogSource log3 = EilifMapTrackerPlugin.Log; if (log3 != null) { log3.LogInfo((object)("[EilifDeath] " + playerName + " died in " + text4 + ": hitType=" + text + ", attacker=" + (text2 ?? "(none)") + ", biome=" + text3)); } EilifMapTrackerPlugin.PostJson(json, "[EilifDeath]", "[EilifDeath] reported " + text + ((text2 == null) ? "" : (" by " + text2)) + " for " + playerName); } } private static string ResolveAttacker(HitData hit) { try { Character attacker = hit.GetAttacker(); if ((Object)(object)attacker == (Object)null) { return null; } Player val = (Player)(object)((attacker is Player) ? attacker : null); if ((Object)(object)val != (Object)null) { string playerName = val.GetPlayerName(); return string.IsNullOrEmpty(playerName) ? null : playerName; } string name = attacker.m_name; if (!string.IsNullOrEmpty(name)) { return name; } return PrefabName(((Component)attacker).gameObject); } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifDeath] attacker lookup failed: " + ex.Message)); } return null; } } private unsafe static string ResolveBiome(Player who) { //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_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) try { Vector3 position = ((Component)who).transform.position; Biome val = Heightmap.FindBiome(position); if ((int)val == 0 && WorldGenerator.instance != null) { val = WorldGenerator.instance.GetBiome(position.x, position.z, 0.02f, false); } return ((object)(*(Biome*)(&val))/*cast due to .constrained prefix*/).ToString(); } catch { return "None"; } } private static string PrefabName(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } string name = ((Object)go).name; if (string.IsNullOrEmpty(name)) { return null; } int num = name.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num <= 0) { return name; } return name.Substring(0, num); } private static string Fmt(float v) { return ((float)Math.Round(v, 1)).ToString("0.#", CultureInfo.InvariantCulture); } } [HarmonyPatch(typeof(Player), "OnDeath")] internal static class Patch_PlayerOnDeath { private static void Postfix(Player __instance) { try { EilifDeathReporter.Report(__instance); } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifDeath] death hook failed: " + ex.Message)); } } } } [BepInPlugin("net.eilif.companionclient", "Eilif Companion Client", "0.2.0")] public class EilifMapTrackerPlugin : BaseUnityPlugin { public const string PluginGuid = "net.eilif.companionclient"; public const string PluginName = "Eilif Companion Client"; public const string PluginVersion = "0.2.0"; internal static ManualLogSource Log; internal static EilifMapTrackerPlugin Instance; internal static string IngestUrl = ""; internal static string IngestToken = ""; private ConfigEntry<string> _url; private ConfigEntry<string> _token; private ConfigEntry<int> _intervalSeconds; private static readonly HttpClient Http = new HttpClient { Timeout = TimeSpan.FromSeconds(20.0) }; private int _postInFlight; private float _postTimer; private bool _wasConnected; private float _lastPct = -1f; private static readonly FieldInfo ExploredField = AccessTools.Field(typeof(Minimap), "m_explored"); private static readonly FieldInfo TextureSizeField = AccessTools.Field(typeof(Minimap), "m_textureSize"); private string _lastCachedPlayer = ""; private string _lastCachedWorld = ""; private void Awake() { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00d0: 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) Log = ((BaseUnityPlugin)this).Logger; Instance = this; _url = ((BaseUnityPlugin)this).Config.Bind<string>("Map", "Url", "https://valheim-dashboard.vercel.app/api/gs-ingest", "Dashboard ingest endpoint. The explored-map % is POSTed here as source:'client-map'."); _token = ((BaseUnityPlugin)this).Config.Bind<string>("Map", "Token", "", "Optional Bearer token. If set it is sent as 'Authorization: Bearer <token>'. Blank is fine (pilot)."); _intervalSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Map", "IntervalSeconds", 300, new ConfigDescription("Seconds between map-% posts while connected to a server.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(60, 3600), Array.Empty<object>())); IngestUrl = _url.Value; IngestToken = _token.Value; try { ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; } catch { } Harmony val = new Harmony("net.eilif.companionclient"); val.PatchAll(typeof(Patch_GameLogout)); val.PatchAll(typeof(Patch_PlayerOnDeath)); Log.LogInfo((object)string.Format("[EilifMap] {0} v{1} loaded. Posting explored-map % to {2} every {3}s while on a server.", "Eilif Companion Client", "0.2.0", _url.Value, _intervalSeconds.Value)); Log.LogInfo((object)("[EilifDeath] death-cause reporter armed (posts source:'eilif-death' to " + _url.Value + " when the local player dies on a server).")); } private void Update() { bool flag = IsOnServer(); if (flag) { _postTimer += Time.unscaledDeltaTime; if (_postTimer >= (float)_intervalSeconds.Value) { _postTimer = 0f; ComputeAndPost("interval"); } } else if (_wasConnected) { _postTimer = 0f; if (_lastPct >= 0f) { Post(_lastCachedPlayer, _lastCachedWorld, _lastPct, "disconnect(cached)"); } _lastPct = -1f; } _wasConnected = flag; } internal static bool IsOnServer() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { return false; } if (instance.GetServerPeer() == null) { return false; } if ((Object)(object)Minimap.instance != (Object)null) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } internal void ComputeAndPost(string reason) { try { Minimap instance = Minimap.instance; ZNet instance2 = ZNet.instance; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && !((Object)(object)localPlayer == (Object)null)) { float? num = ComputeExploredPct(instance); if (num.HasValue) { string playerName = localPlayer.GetPlayerName(); string text = instance2.GetWorldName() ?? ""; _lastCachedPlayer = playerName; _lastCachedWorld = text; _lastPct = num.Value; Post(playerName, text, num.Value, reason); } } } catch (Exception ex) { Log.LogWarning((object)("[EilifMap] compute/post failed: " + ex.Message)); } } private static float? ComputeExploredPct(Minimap mm) { if (ExploredField == null || TextureSizeField == null) { return null; } if (!(ExploredField.GetValue(mm) is bool[] array) || array.Length == 0) { return null; } int num = (int)TextureSizeField.GetValue(mm); if (num <= 0 || array.Length < num * num) { return null; } float num2 = (float)num / 2f; float num3 = num2 * num2; long num4 = 0L; long num5 = 0L; for (int i = 0; i < num; i++) { float num6 = (float)i - num2; float num7 = num3 - num6 * num6; if (num7 < 0f) { continue; } float num8 = (float)Math.Sqrt(num7); int num9 = Math.Max(0, (int)Math.Ceiling(num2 - num8)); int num10 = Math.Min(num - 1, (int)Math.Floor(num2 + num8)); int num11 = i * num; for (int j = num9; j <= num10; j++) { num5++; if (array[num11 + j]) { num4++; } } } if (num5 == 0L) { return null; } float val = (float)((double)num4 / (double)num5 * 100.0); return Math.Min(100f, val); } private void Post(string playerName, string world, float pct, string reason) { if (!string.IsNullOrEmpty(playerName) && Interlocked.CompareExchange(ref _postInFlight, 1, 0) == 0) { float num = (float)Math.Round(pct, 2); string json = "{\"schemaVersion\":1,\"game\":\"valheim\",\"source\":\"client-map\",\"playerName\":" + JsonStr(playerName) + ",\"world\":" + JsonStr(world) + ",\"exploredPct\":" + num.ToString("0.##", CultureInfo.InvariantCulture) + "}"; string url = _url.Value; string token = _token.Value; string pctText = num.ToString("0.##", CultureInfo.InvariantCulture); Task.Run(() => PostAsync(url, token, json, playerName, world, pctText, reason)); } } private async Task PostAsync(string url, string token, string json, string playerName, string world, string pctText, string reason) { try { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url); try { req.Content = (HttpContent)new StringContent(json, Encoding.UTF8, "application/json"); if (!string.IsNullOrEmpty(token)) { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Authorization", "Bearer " + token); } HttpResponseMessage val = await Http.SendAsync(req).ConfigureAwait(continueOnCapturedContext: false); try { if (val.IsSuccessStatusCode) { Log.LogInfo((object)("[EilifMap] posted " + pctText + "% for " + playerName + " (" + world + ") [" + reason + "]")); } else { Log.LogWarning((object)$"[EilifMap] post HTTP {(int)val.StatusCode} {val.ReasonPhrase} for {playerName}"); } } finally { ((IDisposable)val)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } catch (Exception ex) { Log.LogWarning((object)("[EilifMap] post failed: " + ex.Message)); } finally { Interlocked.Exchange(ref _postInFlight, 0); } } internal static void PostJson(string json, string tag, string successLine) { string url = IngestUrl; if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(json)) { return; } string token = IngestToken; try { Task.Run(() => SendAsync(url, token, json, tag, successLine)); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)(tag + " could not queue post: " + ex.Message)); } } } private static async Task SendAsync(string url, string token, string json, string tag, string successLine) { try { HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url); try { req.Content = (HttpContent)new StringContent(json, Encoding.UTF8, "application/json"); if (!string.IsNullOrEmpty(token)) { ((HttpHeaders)req.Headers).TryAddWithoutValidation("Authorization", "Bearer " + token); } HttpResponseMessage val = await Http.SendAsync(req).ConfigureAwait(continueOnCapturedContext: false); try { if (val.IsSuccessStatusCode) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)successLine); } } else { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)$"{tag} post HTTP {(int)val.StatusCode} {val.ReasonPhrase}"); } } } finally { ((IDisposable)val)?.Dispose(); } } finally { ((IDisposable)req)?.Dispose(); } } catch (Exception ex) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)(tag + " post failed: " + ex.Message)); } } } internal static string JsonStr(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } [HarmonyPatch(typeof(Game), "Logout")] internal static class Patch_GameLogout { private static void Prefix() { try { EilifMapTrackerPlugin.Instance?.ComputeAndPost("logout"); } catch (Exception ex) { ManualLogSource log = EilifMapTrackerPlugin.Log; if (log != null) { log.LogWarning((object)("[EilifMap] logout hook failed: " + ex.Message)); } } } } }