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 Valheim ServerGuard Client v1.4.0
Valheim-ServerGuard-Client.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; using ValheimServerGuard.Shared; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; [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("yesu0725")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Valheim ServerGuard Client - companion plugin that attests the client's mod list to a ServerGuard-protected server")] [assembly: AssemblyFileVersion("1.3.0.0")] [assembly: AssemblyInformationalVersion("1.3.0+aec5ff4e1c5c57d0d11bef9eaeebb37e9dabfb4d")] [assembly: AssemblyProduct("Valheim-ServerGuard-Client")] [assembly: AssemblyTitle("Valheim-ServerGuard-Client")] [assembly: AssemblyVersion("1.3.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 ValheimServerGuard.Shared { public static class ModsetFingerprint { public static string ComputeStrict(IEnumerable<KeyValuePair<string, string>> entries) { return Sha256Hex(Canonicalize(entries, withHash: true)); } public static string ComputeLoose(IEnumerable<KeyValuePair<string, string>> entries) { return Sha256Hex(Canonicalize(entries, withHash: false)); } public static string Short(string fullHex) { if (!string.IsNullOrEmpty(fullHex) && fullHex.Length >= 8) { return fullHex.Substring(0, 8); } return fullHex ?? ""; } private static string Canonicalize(IEnumerable<KeyValuePair<string, string>> entries, bool withHash) { if (entries == null) { return ""; } List<string> list = (from e in entries where !string.IsNullOrWhiteSpace(e.Key) select (!withHash) ? (e.Key ?? "").ToLowerInvariant() : ((e.Key ?? "").ToLowerInvariant() + "|" + (e.Value ?? "").ToLowerInvariant())).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string s) => s, StringComparer.Ordinal).ToList(); if (list.Count != 0) { return string.Join("\n", list); } return ""; } private static string Sha256Hex(string input) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(input ?? ""))).Replace("-", "").ToLowerInvariant(); } } [Serializable] public class ModManifestEntry { public string Guid; public string Name; public string Version; public string Sha256; } [Serializable] public class ModManifest { public string SchemaVersion = "1"; public string Challenge; public long TimestampUtc; public List<ModManifestEntry> Mods = new List<ModManifestEntry>(); public string Hmac; public string CanonicalForHmac() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(SchemaVersion ?? "").Append('|'); stringBuilder.Append(Challenge ?? "").Append('|'); stringBuilder.Append(TimestampUtc).Append('|'); List<ModManifestEntry> list = new List<ModManifestEntry>(Mods ?? new List<ModManifestEntry>()); list.Sort(delegate(ModManifestEntry a, ModManifestEntry b) { string strA = ((!string.IsNullOrEmpty(a?.Guid)) ? a.Guid : (a?.Name ?? "")); string strB = ((!string.IsNullOrEmpty(b?.Guid)) ? b.Guid : (b?.Name ?? "")); return string.CompareOrdinal(strA, strB); }); foreach (ModManifestEntry item in list) { stringBuilder.Append(item?.Guid ?? "").Append(':'); stringBuilder.Append(item?.Name ?? "").Append(':'); stringBuilder.Append(item?.Version ?? "").Append(':'); stringBuilder.Append(item?.Sha256 ?? "").Append(';'); } return stringBuilder.ToString(); } public static string ComputeHmac(string canonical, string secret) { if (string.IsNullOrEmpty(secret)) { return ""; } using HMACSHA256 hMACSHA = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); return Convert.ToBase64String(hMACSHA.ComputeHash(Encoding.UTF8.GetBytes(canonical ?? ""))); } public static bool ConstantTimeEquals(string a, string b) { if (a == null || b == null) { return false; } if (a.Length != b.Length) { return false; } int num = 0; for (int i = 0; i < a.Length; i++) { num |= a[i] ^ b[i]; } return num == 0; } } } namespace ValheimServerGuardClient { [BepInPlugin("com.taeguk.valheim.serverguard.client", "Valheim ServerGuard Client", "1.4.0")] public class ClientPlugin : BaseUnityPlugin { private class ClientSettings { public string SharedSecret { get; set; } = ""; } [HarmonyPatch(typeof(Player), "PlacePiece")] public static class Patch_PlacePiece_Report { public static void Postfix(Player __instance, Piece piece, Vector3 pos) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) try { if (IsActiveMultiplayerClient() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)piece == (Object)null)) { GameObject gameObject = ((Component)piece).gameObject; string text = ((gameObject != null) ? ((Object)gameObject).name : null) ?? "unknown"; int num = text.IndexOf("(Clone)", StringComparison.Ordinal); if (num > 0) { text = text.Substring(0, num).Trim(); } Instance?.SendBuildPlace(text, pos); } } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] PlacePiece hook error: " + ex.Message)); } } } } private sealed class LastHitInfo { public Character Attacker; public DateTime At; } [HarmonyPatch(typeof(WearNTear), "Damage")] public static class Patch_WearNTear_Damage_TrackClient { public static void Prefix(WearNTear __instance, HitData hit) { try { if (IsActiveMultiplayerClient() && !((Object)(object)__instance == (Object)null) && hit != null) { Character attacker = null; try { attacker = hit.GetAttacker(); } catch { } LastHitInfo value = new LastHitInfo { Attacker = attacker, At = DateTime.UtcNow }; _clientLastHitOnPiece.Remove(__instance); _clientLastHitOnPiece.Add(__instance, value); } } catch { } } } [HarmonyPatch(typeof(WearNTear), "Destroy")] public static class Patch_WearNTear_Destroy_ClientReport { public static void Prefix(WearNTear __instance) { //IL_0188: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) try { if (!IsActiveMultiplayerClient() || (Object)(object)__instance == (Object)null) { return; } GameObject gameObject = ((Component)__instance).gameObject; string text = ((gameObject != null) ? ((Object)gameObject).name : null) ?? "unknown"; int num = text.IndexOf("(Clone)", StringComparison.Ordinal); if (num > 0) { text = text.Substring(0, num).Trim(); } Vector3 position; try { position = ((Component)__instance).transform.position; } catch { return; } string text2 = "unknown"; string text3 = ""; if (_clientLastHitOnPiece.TryGetValue(__instance, out var value) && value != null) { _clientLastHitOnPiece.Remove(__instance); Character attacker = value.Attacker; if ((Object)(object)attacker != (Object)null) { Player val = (Player)(object)((attacker is Player) ? attacker : null); if (val != null) { if ((Object)(object)val == (Object)(object)Player.m_localPlayer) { text2 = "self"; text3 = ""; } else { text2 = "player"; try { text3 = val.GetPlayerName() ?? ""; } catch { text3 = ""; } } } else { text2 = "creature"; try { text3 = attacker.GetHoverName() ?? ((Object)attacker).name ?? ""; } catch { text3 = ((Object)attacker).name ?? ""; } int num2 = text3.IndexOf("(Clone)", StringComparison.Ordinal); if (num2 > 0) { text3 = text3.Substring(0, num2).Trim(); } } } } if (text2 == "unknown") { text2 = "self"; text3 = ""; } Instance?.SendBuildDestroy(text, position, text2, text3); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] WearNTear.Destroy hook error: " + ex.Message)); } } } } [HarmonyPatch(typeof(Player), "OnDeath")] public static class Patch_Player_OnDeath_Report { public static void Prefix(Player __instance) { try { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && IsActiveMultiplayerClient()) { Instance?.SendDeathReport(__instance); } } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Death hook error: " + ex.Message)); } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] public static class Patch_RegisterClientHandler { public static void Postfix(ZNetPeer peer) { try { if (peer == null || peer.m_rpc == null || ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())) { return; } peer.m_rpc.Register<string>("ServerGuard_RequestManifest", (Action<ZRpc, string>)delegate(ZRpc rpc, string challenge) { try { string text2 = Instance.BuildManifestJson(challenge); rpc.Invoke("ServerGuard_Manifest", new object[1] { text2 }); LogS.LogInfo((object)$"[ServerGuard.Client] Sent manifest ({text2.Length} bytes, {Instance._cachedManifest?.Count ?? 0} mods)."); } catch (Exception ex3) { LogS.LogError((object)("[ServerGuard.Client] Manifest send failed: " + ex3.Message)); } }); if ((Object)(object)Instance != (Object)null) { Instance._serverRpc = peer.m_rpc; } peer.m_rpc.Register<string>("ServerGuard_AdminCommandReply", (Action<ZRpc, string>)delegate(ZRpc rpc, string text) { try { Instance?.DisplayAdminReply(text); } catch (Exception ex2) { ManualLogSource logS2 = LogS; if (logS2 != null) { logS2.LogWarning((object)("[ServerGuard.Client] Admin reply display failed: " + ex2.Message)); } } }); LogS.LogInfo((object)"[ServerGuard.Client] Registered manifest request handler on server peer."); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogError((object)("[ServerGuard.Client] Register handler failed: " + ex.Message)); } } } } [HarmonyPatch(typeof(Player), "StartEmote")] public static class Patch_Player_StartEmote_BlockDuringAttack { public static bool Prefix(Player __instance) { try { if (ShouldBlockAnimationCancel(__instance)) { Instance?.ReportAnimationCancel("emote"); return false; } } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Emote gate error: " + ex.Message)); } } return true; } } [HarmonyPatch(typeof(Humanoid), "HideHandItems")] public static class Patch_Humanoid_HideHandItems_BlockDuringAttack { public static bool Prefix(Humanoid __instance) { try { Player val = (Player)(object)((__instance is Player) ? __instance : null); if ((Object)(object)val == (Object)null) { return true; } if (ShouldBlockAnimationCancel(val)) { Instance?.ReportAnimationCancel("sheathe"); return false; } } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Sheathe gate error: " + ex.Message)); } } return true; } } [HarmonyPatch(typeof(Terminal), "TryRunCommand")] public static class Patch_TryRunCommand { public static bool Prefix(string text) { try { if (string.IsNullOrWhiteSpace(text)) { return true; } string text2 = text.TrimStart(Array.Empty<char>()); int num = text2.IndexOf(' '); string text3 = ((num >= 0) ? text2.Substring(0, num) : text2); string text4 = (text3.StartsWith("/", StringComparison.Ordinal) ? text3.Substring(1) : text3); if (string.Equals(text4, "sg", StringComparison.OrdinalIgnoreCase)) { string command = ((num >= 0) ? text2.Substring(num + 1).TrimStart(Array.Empty<char>()) : ""); Instance?.SendAdminCommand(command); return false; } if (!IsActiveMultiplayerClient()) { return true; } bool num2 = BlockedCommands.Contains(text4); bool flag = !num2 && IsRegisteredCheatCommand(text4); if (num2 || flag) { Instance?.ReportDevcommand(text4); return false; } } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Devcommand gate error: " + ex.Message)); } } return true; } } [CompilerGenerated] private sealed class <DeferredInit>d__15 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public ClientPlugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DeferredInit>d__15(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown int num = <>1__state; ClientPlugin clientPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = (object)new WaitForSeconds(2f); <>1__state = 2; return true; case 2: { <>1__state = -1; clientPlugin.BuildManifestCache(); clientPlugin.ExportAllowedModsSnippet(); ((MonoBehaviour)clientPlugin).StartCoroutine(clientPlugin.SkillReportLoop()); string text = ""; string text2 = ""; try { List<KeyValuePair<string, string>> entries = (clientPlugin._cachedManifest ?? new List<ModManifestEntry>()).Select((ModManifestEntry m) => new KeyValuePair<string, string>((!string.IsNullOrEmpty(m.Guid)) ? m.Guid : (m.Name ?? ""), m.Sha256 ?? "")).ToList(); text = ModsetFingerprint.Short(ModsetFingerprint.ComputeLoose(entries)); text2 = ModsetFingerprint.Short(ModsetFingerprint.ComputeStrict(entries)); } catch (Exception ex) { LogS.LogWarning((object)("[ServerGuard.Client] Fingerprint compute failed: " + ex.Message)); } LogS.LogInfo((object)string.Format("[ServerGuard.Client] Loaded v{0}. Manifest entries: {1}. HMAC: {2}", "1.4.0", clientPlugin._cachedManifest?.Count ?? 0, string.IsNullOrEmpty(clientPlugin._sharedSecret) ? "OFF (no shared_secret configured)" : "ON")); LogS.LogInfo((object)("[ServerGuard.Client] Modset fingerprint loose=" + text + " strict=" + text2)); return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <EnumerateSkills>d__47 : IEnumerable<KeyValuePair<string, float>>, IEnumerable, IEnumerator<KeyValuePair<string, float>>, IDisposable, IEnumerator { private int <>1__state; private KeyValuePair<string, float> <>2__current; private int <>l__initialThreadId; private Skills skills; public Skills <>3__skills; private IDictionaryEnumerator <>7__wrap1; KeyValuePair<string, float> IEnumerator<KeyValuePair<string, float>>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <EnumerateSkills>d__47(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = null; <>1__state = -2; } private bool MoveNext() { bool result; try { switch (<>1__state) { default: result = false; goto end_IL_0000; case 0: <>1__state = -1; if ((Object)(object)skills == (Object)null) { result = false; } else { if (!(_skillsDataField == null)) { goto IL_00a0; } FieldInfo[] fields = typeof(Skills).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (typeof(IDictionary).IsAssignableFrom(fieldInfo.FieldType)) { _skillsDataField = fieldInfo; break; } } if (!(_skillsDataField == null)) { goto IL_00a0; } result = false; } goto end_IL_0000; case 1: { <>1__state = -3; break; } IL_00a0: if (_skillsDataField.GetValue(skills) is IDictionary dictionary) { <>7__wrap1 = dictionary.GetEnumerator(); <>1__state = -3; break; } result = false; goto end_IL_0000; } while (true) { if (<>7__wrap1.MoveNext()) { DictionaryEntry dictionaryEntry = (DictionaryEntry)<>7__wrap1.Current; string text = dictionaryEntry.Key?.ToString(); if (string.IsNullOrEmpty(text)) { continue; } object value = dictionaryEntry.Value; if (value == null) { continue; } if (_skillLevelField == null) { FieldInfo[] fields = value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields) { if (fieldInfo2.FieldType == typeof(float) && fieldInfo2.Name.IndexOf("level", StringComparison.OrdinalIgnoreCase) >= 0) { _skillLevelField = fieldInfo2; break; } } if (_skillLevelField == null) { result = false; <>m__Finally1(); break; } } float num = 0f; try { num = (float)_skillLevelField.GetValue(value); } catch { continue; } <>2__current = new KeyValuePair<string, float>(text, num); <>1__state = 1; result = true; break; } <>m__Finally1(); <>7__wrap1 = null; result = false; break; } end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>7__wrap1 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<KeyValuePair<string, float>> IEnumerable<KeyValuePair<string, float>>.GetEnumerator() { <EnumerateSkills>d__47 <EnumerateSkills>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <EnumerateSkills>d__ = this; } else { <EnumerateSkills>d__ = new <EnumerateSkills>d__47(0); } <EnumerateSkills>d__.skills = <>3__skills; return <EnumerateSkills>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<KeyValuePair<string, float>>)this).GetEnumerator(); } } [CompilerGenerated] private sealed class <SkillReportLoop>d__42 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public ClientPlugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SkillReportLoop>d__42(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown int num = <>1__state; ClientPlugin clientPlugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(15f); <>1__state = 1; return true; case 1: <>1__state = -1; break; case 2: <>1__state = -1; try { clientPlugin.SendSkillReportNow(); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Skill report tick error: " + ex.Message)); } } break; } <>2__current = (object)new WaitForSeconds(60f); <>1__state = 2; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string GUID = "com.taeguk.valheim.serverguard.client"; public const string NAME = "Valheim ServerGuard Client"; public const string VERSION = "1.4.0"; internal static ClientPlugin Instance; internal static ManualLogSource LogS; private Harmony _harmony; private string _sharedSecret = ""; private List<ModManifestEntry> _cachedManifest; internal ZRpc _serverRpc; private static readonly HashSet<string> BlockedCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "devcommands", "debugmode", "imacheater", "god", "ghost", "fly", "nocost", "noplacementcost", "spawn", "pos", "goto", "tame", "killall", "event", "stopevent", "tod", "skiptime", "sleep", "raiseskill", "resetcharacter", "heal", "puke", "damage", "setkey", "resetkeys", "removedrops", "freefly" }; private static readonly string ConfDir = Path.Combine(Paths.ConfigPath, "ServerGuard"); private static readonly string ClientYaml = Path.Combine(ConfDir, "client.yaml"); private static readonly string ExportYaml = Path.Combine(ConfDir, "mods_for_allowed_mods.yaml"); private static MethodInfo _consoleWriteMethod; private static int _consoleWriteArity; private static object _consoleInstance; private static readonly ConditionalWeakTable<WearNTear, LastHitInfo> _clientLastHitOnPiece = new ConditionalWeakTable<WearNTear, LastHitInfo>(); private static FieldInfo _playerLastHitField; private static MethodInfo _hitGetAttackerMethod; private static FieldInfo _hitDamageField; private const float SkillReportIntervalSeconds = 60f; private static FieldInfo _playerSkillsField; private static FieldInfo _skillsDataField; private static FieldInfo _skillLevelField; private void Awake() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown Instance = this; LogS = ((BaseUnityPlugin)this).Logger; EnsureConfig(); _harmony = new Harmony("com.taeguk.valheim.serverguard.client"); _harmony.PatchAll(); ((MonoBehaviour)this).StartCoroutine(DeferredInit()); } [IteratorStateMachine(typeof(<DeferredInit>d__15))] private IEnumerator DeferredInit() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DeferredInit>d__15(0) { <>4__this = this }; } private void ExportAllowedModsSnippet() { try { if (File.Exists(ExportYaml)) { LogS.LogInfo((object)("[ServerGuard.Client] Allowed-mods export already present at " + ExportYaml + ". Delete the file to regenerate.")); return; } List<ModManifestEntry> list = _cachedManifest ?? new List<ModManifestEntry>(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# ServerGuard - allowed_mods snippet generated by ServerGuard.Client v1.4.0"); stringBuilder.AppendLine($"# Generated: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}Z Mods on this client: {list.Count}"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# How to use:"); stringBuilder.AppendLine("# 1. Open <server>/BepInEx/config/ServerGuard/conf/allowed_mods.yaml"); stringBuilder.AppendLine("# 2. Replace the `allowed_mods:` block with the one below"); stringBuilder.AppendLine("# (or merge if you already have entries you want to keep)."); stringBuilder.AppendLine("# 3. Save. The server hot-reloads within ~1 second."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# Each entry is `<GUID>|<sha256>` (GUID-keyed, hash-pinned)."); stringBuilder.AppendLine("# To loosen, drop the `|<sha256>` suffix - the entry will then accept any hash."); stringBuilder.AppendLine("# To tighten further, leave it as-is - the server will require an exact DLL match."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# The companion plugin (this DLL) is intentionally listed under required_mods,"); stringBuilder.AppendLine("# NOT allowed_mods - the server demands its presence."); stringBuilder.AppendLine(); ModManifestEntry modManifestEntry = list.FirstOrDefault((ModManifestEntry m) => string.Equals(m.Guid, "com.taeguk.valheim.serverguard.client", StringComparison.OrdinalIgnoreCase)); stringBuilder.AppendLine("required_mods:"); if (modManifestEntry != null && !string.IsNullOrEmpty(modManifestEntry.Sha256)) { stringBuilder.AppendLine(" - " + modManifestEntry.Guid + "|" + modManifestEntry.Sha256 + " # " + modManifestEntry.Name + " v" + modManifestEntry.Version); } else { stringBuilder.AppendLine(" - com.taeguk.valheim.serverguard.client # Valheim ServerGuard Client v1.4.0"); } stringBuilder.AppendLine(); stringBuilder.AppendLine("allowed_mods:"); List<ModManifestEntry> list2 = list.Where((ModManifestEntry m) => !string.Equals(m.Guid, "com.taeguk.valheim.serverguard.client", StringComparison.OrdinalIgnoreCase)).OrderBy<ModManifestEntry, string>((ModManifestEntry m) => m.Name ?? "", StringComparer.OrdinalIgnoreCase).ToList(); if (list2.Count == 0) { stringBuilder.AppendLine(" []"); } else { int num = 0; foreach (ModManifestEntry item in list2) { int num2 = (((!string.IsNullOrEmpty(item.Guid)) ? item.Guid : item.Name) ?? "").Length + ((!string.IsNullOrEmpty(item.Sha256)) ? (1 + item.Sha256.Length) : 0); if (num2 > num) { num = num2; } } foreach (ModManifestEntry item2 in list2) { string text = ((!string.IsNullOrEmpty(item2.Guid)) ? item2.Guid : (item2.Name ?? "")); string text2 = (string.IsNullOrEmpty(item2.Sha256) ? text : (text + "|" + item2.Sha256)); string text3 = new string(' ', Math.Max(1, num - text2.Length + 2)); string text4 = (string.IsNullOrEmpty(item2.Name) ? "" : (item2.Name + " v" + item2.Version)); if (string.IsNullOrEmpty(item2.Guid)) { stringBuilder.AppendLine(" - " + text2 + text3 + "# " + text4 + " (no GUID; consider replacing the key with the mod's BepInPlugin GUID)"); } else { stringBuilder.AppendLine(" - " + text2 + text3 + "# " + text4); } } } stringBuilder.AppendLine(); stringBuilder.AppendLine("banned_mods: []"); stringBuilder.AppendLine(); Directory.CreateDirectory(ConfDir); File.WriteAllText(ExportYaml, stringBuilder.ToString()); LogS.LogWarning((object)"[ServerGuard.Client] First-run mod export written:"); LogS.LogWarning((object)("[ServerGuard.Client] " + ExportYaml)); LogS.LogWarning((object)$"[ServerGuard.Client] ({list.Count} plugins). Paste its contents into the server's allowed_mods.yaml."); } catch (Exception ex) { LogS.LogError((object)("[ServerGuard.Client] ExportAllowedModsSnippet failed: " + ex.Message)); } } private void OnDestroy() { try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } } private void EnsureConfig() { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown try { Directory.CreateDirectory(ConfDir); if (!File.Exists(ClientYaml)) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Valheim ServerGuard - Client config"); stringBuilder.AppendLine("# sharedSecret MUST match the server's settings.yaml `sharedSecret` value"); stringBuilder.AppendLine("# verbatim. The server will reject manifests whose HMAC does not match."); stringBuilder.AppendLine("# Leave empty only if the server has `requireHmac: false` (insecure)."); stringBuilder.AppendLine("sharedSecret: \"\""); File.WriteAllText(ClientYaml, stringBuilder.ToString()); } ClientSettings clientSettings = ((BuilderSkeleton<DeserializerBuilder>)new DeserializerBuilder()).WithNamingConvention(CamelCaseNamingConvention.Instance).IgnoreUnmatchedProperties().Build() .Deserialize<ClientSettings>(File.ReadAllText(ClientYaml)) ?? new ClientSettings(); _sharedSecret = clientSettings.SharedSecret ?? ""; } catch (Exception ex) { LogS.LogWarning((object)("[ServerGuard.Client] EnsureConfig failed: " + ex.Message)); } } private void BuildManifestCache() { _cachedManifest = new List<ModManifestEntry>(); try { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { PluginInfo value = pluginInfo.Value; BepInPlugin val = ((value != null) ? value.Metadata : null); string sha = ""; try { string text = ((value != null) ? value.Location : null); if (!string.IsNullOrEmpty(text) && File.Exists(text)) { using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(text); sha = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant(); } } catch { } _cachedManifest.Add(new ModManifestEntry { Guid = (((val != null) ? val.GUID : null) ?? ""), Name = (((val != null) ? val.Name : null) ?? ""), Version = (((val == null) ? null : val.Version?.ToString()) ?? ""), Sha256 = sha }); } } catch (Exception ex) { LogS.LogError((object)("[ServerGuard.Client] BuildManifestCache failed: " + ex.Message)); } } public string BuildManifestJson(string challenge) { BuildManifestCache(); ModManifest obj = new ModManifest { SchemaVersion = "1", Challenge = (challenge ?? ""), TimestampUtc = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), Mods = (_cachedManifest ?? new List<ModManifestEntry>()) }; obj.Hmac = ModManifest.ComputeHmac(obj.CanonicalForHmac(), _sharedSecret); return JsonConvert.SerializeObject((object)obj); } internal void SendAdminCommand(string command) { if (!IsActiveMultiplayerClient()) { DisplayAdminReply("[ServerGuard] sg commands only work while connected to a multiplayer server."); return; } if (_serverRpc == null) { DisplayAdminReply("[ServerGuard] Not connected to a server peer yet."); return; } try { _serverRpc.Invoke("ServerGuard_AdminCommand", new object[1] { command ?? "" }); ManualLogSource logS = LogS; if (logS != null) { logS.LogInfo((object)("[ServerGuard.Client] Sent admin command: " + command)); } } catch (Exception ex) { ManualLogSource logS2 = LogS; if (logS2 != null) { logS2.LogWarning((object)("[ServerGuard.Client] Admin command send failed: " + ex.Message)); } DisplayAdminReply("[ServerGuard] Send failed: " + ex.Message); } } internal void DisplayAdminReply(string text) { if (string.IsNullOrEmpty(text)) { return; } string[] array = text.Split(new char[1] { '\n' }); try { object obj = ResolveConsoleInstance(); string[] array2; if (obj == null || _consoleWriteMethod == null) { array2 = array; foreach (string text2 in array2) { ManualLogSource logS = LogS; if (logS != null) { logS.LogInfo((object)("[ServerGuard] " + text2)); } } return; } array2 = array; foreach (string text3 in array2) { if (string.IsNullOrWhiteSpace(text3)) { continue; } try { object[] parameters = ((_consoleWriteArity != 1) ? new object[2] { text3, false } : new object[1] { text3 }); _consoleWriteMethod.Invoke(obj, parameters); } catch { ManualLogSource logS2 = LogS; if (logS2 != null) { logS2.LogInfo((object)("[ServerGuard] " + text3)); } } } } catch (Exception ex) { ManualLogSource logS3 = LogS; if (logS3 != null) { logS3.LogWarning((object)("[ServerGuard.Client] DisplayAdminReply error: " + ex.Message)); } } } private object ResolveConsoleInstance() { if (_consoleInstance != null && _consoleWriteMethod != null) { return _consoleInstance; } Type type = typeof(Terminal).Assembly.GetType("Console"); object obj = null; if (type != null) { try { PropertyInfo property = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { obj = property.GetValue(null); } } catch { } if (obj == null) { try { FieldInfo field = type.GetField("m_instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { obj = field.GetValue(null); } } catch { } } } if (obj == null) { try { obj = Object.FindObjectOfType<Terminal>(); } catch { } } if (obj == null) { return null; } MethodInfo methodInfo = null; int num = 0; string[] array = new string[2] { "Print", "AddString" }; foreach (string text in array) { MethodInfo[] methods = obj.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name != text)) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(string)) { methodInfo = methodInfo2; num = 1; break; } if (parameters.Length == 2 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(bool) && methodInfo == null) { methodInfo = methodInfo2; num = 2; } } } if (methodInfo != null && num == 1) { break; } } if (methodInfo == null) { return null; } _consoleInstance = obj; _consoleWriteMethod = methodInfo; _consoleWriteArity = num; return _consoleInstance; } internal void SendBuildPlace(string pieceName, Vector3 pos) { //IL_0029: 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_0045: Unknown result type (might be due to invalid IL or missing references) if (_serverRpc == null) { return; } try { string text = SanitiseShort(pieceName, 64); string text2 = string.Format(CultureInfo.InvariantCulture, "{0}|{1:F1}|{2:F1}|{3:F1}", text, pos.x, pos.y, pos.z); _serverRpc.Invoke("ServerGuard_BuildPlace", new object[1] { text2 }); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] BuildPlace RPC failed: " + ex.Message)); } } } internal void SendBuildDestroy(string pieceName, Vector3 pos, string attackerKind, string attackerLabel) { //IL_004e: 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) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (_serverRpc == null) { return; } try { string text = SanitiseShort(pieceName, 64); string text2 = SanitiseShort(attackerKind ?? "unknown", 16); string text3 = SanitiseShort(attackerLabel ?? "", 48); string text4 = string.Format(CultureInfo.InvariantCulture, "{0}|{1:F1}|{2:F1}|{3:F1}|{4}|{5}", text, pos.x, pos.y, pos.z, text2, text3); _serverRpc.Invoke("ServerGuard_BuildDestroy", new object[1] { text4 }); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] BuildDestroy RPC failed: " + ex.Message)); } } } private static string SanitiseShort(string s, int max) { string text = (s ?? "").Replace('|', ' ').Replace('\n', ' ').Trim(); if (text.Length > max) { text = text.Substring(0, max); } return text; } internal void SendDeathReport(Player p) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) if (_serverRpc == null) { return; } try { Vector3 position = ((Component)p).transform.position; string text = "environment"; string text2 = ""; string text3 = ""; if (_playerLastHitField == null) { FieldInfo[] fields = typeof(Player).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(HitData)) { _playerLastHitField = fieldInfo; break; } } } object? obj = _playerLastHitField?.GetValue(p); HitData val = (HitData)((obj is HitData) ? obj : null); if (val != null && val != null) { text3 = DominantDamageType(val); if (_hitGetAttackerMethod == null) { _hitGetAttackerMethod = typeof(HitData).GetMethod("GetAttacker", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } Character val2 = null; if (_hitGetAttackerMethod != null) { try { object? obj2 = _hitGetAttackerMethod.Invoke(val, null); val2 = (Character)((obj2 is Character) ? obj2 : null); } catch { } } if ((Object)(object)val2 != (Object)null) { Player val3 = (Player)(object)((val2 is Player) ? val2 : null); if (val3 != null) { if ((Object)(object)val3 == (Object)(object)p) { text = "self"; text2 = ""; } else { text = "player"; text2 = val3.GetPlayerName() ?? ""; } } else { text = "creature"; try { text2 = val2.GetHoverName() ?? ((Object)val2).name ?? ""; } catch { text2 = ((Object)val2).name ?? ""; } } } } text2 = (text2 ?? "").Replace('|', ' ').Replace('\n', ' ').Trim(); text3 = (text3 ?? "").Replace('|', ' ').Replace('\n', ' ').Trim(); string text4 = string.Format(CultureInfo.InvariantCulture, "{0:F1}|{1:F1}|{2:F1}|{3}|{4}|{5}", position.x, position.y, position.z, text, text2, text3); try { _serverRpc.Invoke("ServerGuard_PlayerDeath", new object[1] { text4 }); LogS.LogInfo((object)("[ServerGuard.Client] Death report sent (" + text + " / " + text2 + " / " + text3 + ").")); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Death report RPC failed: " + ex.Message)); } } } catch (Exception ex2) { ManualLogSource logS2 = LogS; if (logS2 != null) { logS2.LogWarning((object)("[ServerGuard.Client] SendDeathReport error: " + ex2.Message)); } } } private static string DominantDamageType(HitData hit) { if (hit == null) { return ""; } FieldInfo[] fields; if (_hitDamageField == null) { fields = typeof(HitData).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.Name.Equals("m_damage", StringComparison.OrdinalIgnoreCase)) { _hitDamageField = fieldInfo; break; } } } if (_hitDamageField == null) { return ""; } object value; try { value = _hitDamageField.GetValue(hit); } catch { return ""; } if (value == null) { return ""; } string text = ""; float num = 0f; fields = value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields) { if (!(fieldInfo2.FieldType != typeof(float))) { float num2; try { num2 = (float)fieldInfo2.GetValue(value); } catch { continue; } if (num2 > num) { num = num2; text = fieldInfo2.Name; } } } if (text.StartsWith("m_", StringComparison.Ordinal)) { text = text.Substring(2); } if (text.Length > 0) { text = char.ToUpperInvariant(text[0]) + text.Substring(1); } return text; } [IteratorStateMachine(typeof(<SkillReportLoop>d__42))] private IEnumerator SkillReportLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SkillReportLoop>d__42(0) { <>4__this = this }; } private static Skills GetPlayerSkills(Player p) { if ((Object)(object)p == (Object)null) { return null; } if (_playerSkillsField == null) { FieldInfo[] fields = typeof(Player).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(Skills)) { _playerSkillsField = fieldInfo; break; } } if (_playerSkillsField == null) { return null; } } object? value = _playerSkillsField.GetValue(p); return (Skills)((value is Skills) ? value : null); } [IteratorStateMachine(typeof(<EnumerateSkills>d__47))] private static IEnumerable<KeyValuePair<string, float>> EnumerateSkills(Skills skills) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <EnumerateSkills>d__47(-2) { <>3__skills = skills }; } private void SendSkillReportNow() { if (_serverRpc == null) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } Skills playerSkills = GetPlayerSkills(localPlayer); if ((Object)(object)playerSkills == (Object)null) { return; } StringBuilder stringBuilder = new StringBuilder(); bool flag = true; foreach (KeyValuePair<string, float> item in EnumerateSkills(playerSkills)) { string key = item.Key; float value = item.Value; if (!string.IsNullOrEmpty(key) && key.IndexOf(':') < 0 && key.IndexOf('|') < 0 && key.Length <= 32) { if (!flag) { stringBuilder.Append('|'); } stringBuilder.Append(key); stringBuilder.Append(':'); stringBuilder.Append(value.ToString("F1", CultureInfo.InvariantCulture)); flag = false; } } if (stringBuilder.Length == 0) { return; } try { _serverRpc.Invoke("ServerGuard_SkillReport", new object[1] { stringBuilder.ToString() }); } catch (Exception ex) { ManualLogSource logS = LogS; if (logS != null) { logS.LogWarning((object)("[ServerGuard.Client] Skill report send failed: " + ex.Message)); } } } private static bool IsActiveMultiplayerClient() { try { if ((Object)(object)ZNet.instance == (Object)null) { return false; } if (ZNet.instance.IsServer()) { return false; } return true; } catch { return false; } } internal void ReportDevcommand(string command) { try { LogS.LogWarning((object)("[ServerGuard.Client] Blocked cheat attempt: `" + command + "` (multiplayer client)")); if (_serverRpc != null) { try { _serverRpc.Invoke("ServerGuard_DevcommandAttempt", new object[1] { command ?? "" }); return; } catch (Exception ex) { LogS.LogWarning((object)("[ServerGuard.Client] Could not report devcommand to server: " + ex.Message)); return; } } } catch { } } internal void ReportAnimationCancel(string source) { try { LogS.LogInfo((object)("[ServerGuard.Client] Blocked animation cancel via " + source + " (mid-attack).")); if (_serverRpc != null) { try { _serverRpc.Invoke("ServerGuard_AnimationCancelAttempt", new object[1] { source ?? "" }); return; } catch (Exception ex) { LogS.LogWarning((object)("[ServerGuard.Client] Could not report animation-cancel: " + ex.Message)); return; } } } catch { } } private static bool IsRegisteredCheatCommand(string cmd) { if (string.IsNullOrEmpty(cmd)) { return false; } try { Type typeFromHandle = typeof(Terminal); IDictionary dictionary = null; FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (typeof(IDictionary).IsAssignableFrom(fieldInfo.FieldType) && fieldInfo.GetValue(null) is IDictionary dictionary2) { dictionary = dictionary2; break; } } if (dictionary == null) { return false; } object obj = null; foreach (DictionaryEntry item in dictionary) { if (item.Key is string a && string.Equals(a, cmd, StringComparison.OrdinalIgnoreCase)) { obj = item.Value; break; } } if (obj == null) { return false; } Type type = obj.GetType(); string[] array = new string[5] { "IsCheat", "isCheat", "m_isCheat", "Cheat", "cheat" }; bool flag = default(bool); bool flag2 = default(bool); foreach (string name in array) { FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && field.FieldType == typeof(bool)) { object value = field.GetValue(obj); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(bool)) { object value = property.GetValue(obj); int num2; if (value is bool) { flag2 = (bool)value; num2 = 1; } else { num2 = 0; } return (byte)((uint)num2 & (flag2 ? 1u : 0u)) != 0; } } } catch { } return false; } private static bool ShouldBlockAnimationCancel(Player p) { try { if (!IsActiveMultiplayerClient()) { return false; } if ((Object)(object)p == (Object)null) { return false; } if ((Object)(object)p != (Object)(object)Player.m_localPlayer) { return false; } return ((Character)p).InAttack(); } catch { return false; } } } }