using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using Agents; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using FriendlyFireDetector.Config; using FriendlyFireDetector.Managers; using FriendlyFireDetector.UI; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Microsoft.CodeAnalysis; using Player; using SNetwork; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("FriendlyFireDetector")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("FriendlyFireDetector")] [assembly: AssemblyTitle("FriendlyFireDetector")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 FriendlyFireDetector { [BepInPlugin("com.gtfo.friendlyfiredetector", "FriendlyFireDetector", "1.0.1")] public sealed class Plugin : BasePlugin { public const string PluginGuid = "com.gtfo.friendlyfiredetector"; public const string PluginName = "FriendlyFireDetector"; public const string PluginVersion = "1.0.1"; private Harmony? _harmony; internal static ManualLogSource Logger { get; private set; } public override void Load() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown Logger = ((BasePlugin)this).Log; try { ConfigManager.Bind(new ConfigFile(Path.Combine(Paths.ConfigPath, "FriendlyFireDetector.cfg"), true)); _harmony = new Harmony("com.gtfo.friendlyfiredetector"); _harmony.PatchAll(typeof(Plugin).Assembly); Logger.LogInfo((object)"FriendlyFireDetector loaded."); } catch (Exception ex) { ManualLogSource logger = Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("FriendlyFireDetector failed to load: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); throw; } } } } namespace FriendlyFireDetector.UI { internal static class MessageDisplay { public static string BuildFriendlyFireMessage(string attackerName, string victimName, float damage, float attackerTotalDamage) { string value = (ConfigManager.ShowAttackerName.Value ? attackerName : "Unknown"); string value2 = (ConfigManager.ShowVictimName.Value ? victimName : "Unknown"); if (ConfigManager.ShowDamageAmount.Value) { return $"⚠ 内鬼播报:\n{value}对{value2}造成了{damage:0.#}点伤害\n{value}累计造成{attackerTotalDamage:0.#}点伤害。"; } return $"⚠ 内鬼播报:\n{value}对{value2}造成了伤害\n{value}累计造成友伤。"; } public static void Show(string message) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown try { if (GuiManager.InteractionLayer != null) { GuiManager.InteractionLayer.SetTimedMessage(message, ConfigManager.MessageDuration.Value, (ePUIMessageStyle)3, 10); return; } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(34, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Native UI message display failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } TryShowChatFallback(message); } private static void TryShowChatFallback(string message) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown try { if (!((Object)(object)PlayerChatManager.Current == (Object)null)) { PlayerChatManager.PostChatMessageLocaly(PlayerManager.GetLocalPlayerAgent().Owner, message, (SNet_Player)null); } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Chat fallback display failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } } } } namespace FriendlyFireDetector.Patches { [HarmonyPatch(typeof(Dam_PlayerDamageBase), "ReceiveBulletDamage")] internal static class ReceiveBulletDamagePatch { private static void Prefix(Dam_PlayerDamageBase __instance, out DamageSnapshot __state) { __state = FriendlyFireManager.Capture(__instance); } private static void Postfix(Dam_PlayerDamageBase __instance, pBulletDamageData data, DamageSnapshot __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) FriendlyFireManager.HandleSyncedDamage(__instance, data.source, __state, DamageKind.Bullet); } } [HarmonyPatch(typeof(Dam_PlayerDamageBase), "ReceiveMeleeDamage")] internal static class ReceiveMeleeDamagePatch { private static void Prefix(Dam_PlayerDamageBase __instance, out DamageSnapshot __state) { __state = FriendlyFireManager.Capture(__instance); } private static void Postfix(Dam_PlayerDamageBase __instance, pFullDamageData data, DamageSnapshot __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) FriendlyFireManager.HandleSyncedDamage(__instance, data.source, __state, DamageKind.Melee); } } [HarmonyPatch(typeof(Dam_PlayerDamageBase), "ReceiveFireDamage")] internal static class ReceiveFireDamagePatch { private static void Prefix(Dam_PlayerDamageBase __instance, out DamageSnapshot __state) { __state = FriendlyFireManager.Capture(__instance); } private static void Postfix(Dam_PlayerDamageBase __instance, pSmallDamageData data, DamageSnapshot __state) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) FriendlyFireManager.HandleSyncedDamage(__instance, data.source, __state, DamageKind.Fire); } } [HarmonyPatch(typeof(Dam_PlayerDamageBase), "ReceiveExplosionDamage")] internal static class ReceiveExplosionDamagePatch { private static void Prefix(Dam_PlayerDamageBase __instance, out DamageSnapshot __state) { __state = FriendlyFireManager.Capture(__instance); } private static void Postfix(Dam_PlayerDamageBase __instance, pExplosionDamageData data, DamageSnapshot __state) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown try { FriendlyFireManager.HandleExplosionDamageWithoutSource(__instance, __state); } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(30, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Explosion damage hook failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } } } } namespace FriendlyFireDetector.Managers { internal enum DamageKind { Bullet, Melee, Fire, Explosion } internal readonly struct DamageSnapshot { public float HealthBefore { get; } public DamageSnapshot(float healthBefore) { HealthBefore = healthBefore; } } internal static class FriendlyFireManager { private const float MinimumReportableDamage = 0.01f; private static readonly Dictionary s_friendlyDamageByAttacker = new Dictionary(); public static DamageSnapshot Capture(Dam_PlayerDamageBase damageBase) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown try { return new DamageSnapshot(((Dam_SyncedDamageBase)damageBase).Health); } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(35, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to capture damage snapshot: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); return new DamageSnapshot(0f); } } public static void HandleSyncedDamage(Dam_PlayerDamageBase victimDamageBase, pAgent source, DamageSnapshot snapshot, DamageKind kind) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown if (!ConfigManager.EnableMod.Value) { return; } try { float num = CalculateActualDamage(victimDamageBase, snapshot); Agent agent = default(Agent); if (!(num <= 0.01f) && ((pAgent)(ref source)).TryGet(ref agent)) { PlayerAgent val = PlayerManager.TryAsPlayerAgent(agent); PlayerAgent val2 = PlayerManager.TryAsPlayerAgent(((Dam_SyncedDamageBase)victimDamageBase).GetBaseAgent()); if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null) && !PlayerManager.IsSamePlayer(val, val2)) { ReportFriendlyFire(val, val2, num, kind); } } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val3 = new BepInExErrorLogInterpolatedStringHandler(32, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Friendly fire detection failed: "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(ex); } logger.LogError(val3); } } public static void HandleExplosionDamageWithoutSource(Dam_PlayerDamageBase victimDamageBase, DamageSnapshot snapshot) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown if (!ConfigManager.EnableMod.Value) { return; } float num = CalculateActualDamage(victimDamageBase, snapshot); if (!(num <= 0.01f)) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(91, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Explosion damage received by player, but pExplosionDamageData has no source agent. Damage: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num, "0.#"); } logger.LogInfo(val); } } private static float CalculateActualDamage(Dam_PlayerDamageBase damageBase, DamageSnapshot snapshot) { float health = ((Dam_SyncedDamageBase)damageBase).Health; return Math.Max(0f, snapshot.HealthBefore - health); } private static void ReportFriendlyFire(PlayerAgent attacker, PlayerAgent victim, float damage, DamageKind kind) { string displayName = PlayerManager.GetDisplayName(attacker); string displayName2 = PlayerManager.GetDisplayName(victim); string text = kind.ToString(); float num = AddAttackerDamage(attacker, damage); Plugin.Logger.LogInfo((object)("Friendly damage detected:\nAttacker: " + displayName + "\nVictim: " + displayName2 + "\n" + $"Damage: {damage:0.#}\n" + $"TotalDamageByAttacker: {num:0.#}\n" + "Type: " + text)); if (ConfigManager.EnableBroadcast.Value) { MessageDisplay.Show(MessageDisplay.BuildFriendlyFireMessage(displayName, displayName2, damage, num)); } } private static float AddAttackerDamage(PlayerAgent attacker, float damage) { ulong stablePlayerKey = PlayerManager.GetStablePlayerKey(attacker); s_friendlyDamageByAttacker.TryGetValue(stablePlayerKey, out var value); value += damage; s_friendlyDamageByAttacker[stablePlayerKey] = value; return value; } } internal static class PlayerManager { public static ulong GetStablePlayerKey(PlayerAgent agent) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown try { SNet_Player owner = agent.Owner; if ((Object)(object)owner != (Object)null && owner.Lookup != 0L) { return owner.Lookup; } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve stable player key: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } return ((Agent)agent).GlobalID; } public static PlayerAgent? TryAsPlayerAgent(Agent? agent) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_000a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)agent == (Object)null || (int)agent.Type != 0) { return null; } try { return ((Il2CppObjectBase)agent).Cast(); } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(37, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to cast Agent to PlayerAgent: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); return null; } } public static bool IsSamePlayer(PlayerAgent left, PlayerAgent right) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown try { SNet_Player owner = left.Owner; SNet_Player owner2 = right.Owner; if ((Object)(object)owner != (Object)null && (Object)(object)owner2 != (Object)null) { return owner.Lookup == owner2.Lookup; } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(33, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to compare player owners: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } if (left != right) { return ((Agent)left).GlobalID == ((Agent)right).GlobalID; } return true; } public static string GetDisplayName(PlayerAgent agent) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown try { SNet_Player owner = agent.Owner; if ((Object)(object)owner != (Object)null) { string nickName = owner.NickName; if (!string.IsNullOrWhiteSpace(nickName)) { return nickName; } string name = owner.GetName(); if (!string.IsNullOrWhiteSpace(name)) { return name; } } string playerName = agent.PlayerName; if (!string.IsNullOrWhiteSpace(playerName)) { return playerName; } } catch (Exception ex) { ManualLogSource logger = Plugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(31, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to resolve player name: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logger.LogError(val); } return $"Player_{((Agent)agent).GlobalID}"; } } } namespace FriendlyFireDetector.Config { internal static class ConfigManager { public static ConfigEntry EnableMod { get; private set; } public static ConfigEntry EnableBroadcast { get; private set; } public static ConfigEntry ShowDamageAmount { get; private set; } public static ConfigEntry ShowAttackerName { get; private set; } public static ConfigEntry ShowVictimName { get; private set; } public static ConfigEntry MessageDuration { get; private set; } public static void Bind(ConfigFile config) { EnableMod = config.Bind("General", "EnableMod", true, "Enable or disable FriendlyFireDetector."); EnableBroadcast = config.Bind("General", "EnableBroadcast", true, "Show friendly fire alerts in game UI."); ShowDamageAmount = config.Bind("General", "ShowDamageAmount", true, "Include the actual health deducted in alerts."); ShowAttackerName = config.Bind("General", "ShowAttackerName", true, "Include attacker name in alerts."); ShowVictimName = config.Bind("General", "ShowVictimName", true, "Include victim name in alerts."); MessageDuration = config.Bind("General", "MessageDuration", 5f, "Message display duration in seconds."); } } }