using 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 HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppTMPro; using Kp0hyc.Labyrinthine.Shared; using MelonLoader; using MelonLoader.Preferences; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(DeathBodyOutline), "Death Body Outline", "0.2.0", "kp0hyc", null)] [assembly: MelonGame("Valko Game Studios", "Labyrinthine")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("DeathBodyOutline")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("DeathBodyOutline")] [assembly: AssemblyTitle("DeathBodyOutline")] [assembly: AssemblyVersion("1.0.0.0")] public class DeathBodyOutline : MelonMod { private sealed class BodyCloneResult { public GameObject Body { get; init; } public GameObject Nameplate { get; init; } public string Source { get; init; } public string LocalHeadCompletion { get; init; } } private readonly struct RendererMaterialCopyResult { public readonly int SourceRenderers; public readonly int TargetRenderers; public readonly int RenderersCopied; public readonly int MaterialsCopied; public RendererMaterialCopyResult(int sourceRenderers, int targetRenderers, int renderersCopied, int materialsCopied) { SourceRenderers = sourceRenderers; TargetRenderers = targetRenderers; RenderersCopied = renderersCopied; MaterialsCopied = materialsCopied; } public override string ToString() { return "sourceRenderers=" + SourceRenderers.ToString(CultureInfo.InvariantCulture) + " targetRenderers=" + TargetRenderers.ToString(CultureInfo.InvariantCulture) + " renderersCopied=" + RenderersCopied.ToString(CultureInfo.InvariantCulture) + " materialsCopied=" + MaterialsCopied.ToString(CultureInfo.InvariantCulture); } } private const string HarmonyId = "kp0hyc.deathbodyoutline"; private const float DuplicateGuardSeconds = 2f; private const int VisualModeDisabled = 0; private const int VisualModeFinalDeathOnly = 1; private const int VisualModeEveryDeath = 2; private const string DeadNameSuffix = " ☠"; private const float NameplateHeightAboveBodyRoot = 1.08f; private const float NameplateHeightAboveHead = 0.42f; private const float MaxNameplateBoundsHorizontalOffset = 1.35f; private static readonly Color DeadNameColor = new Color(1f, 0.18f, 0.14f, 1f); private static readonly Dictionary s_lastOutlineTimeByKey = new Dictionary(StringComparer.Ordinal); private static readonly List s_outlines = new List(); private static MelonPreferences_Entry s_verboseLogs; private static MelonPreferences_Entry s_visualMode; private static Harmony s_harmony; private static GameObject s_root; private static void CreateBodyOutline(string label, object player, GameObject sourceBody, string sourceContext) { BodyCloneResult bodyCloneResult = CreateFrozenBodyClone(label, player, sourceBody); if (!((Object)(object)bodyCloneResult.Body == (Object)null)) { if ((Object)(object)bodyCloneResult.Nameplate != (Object)null) { s_outlines.Add(bodyCloneResult.Nameplate); } s_outlines.Add(bodyCloneResult.Body); LogVerbose("body clone spawned label=" + label + " sourceContext=" + sourceContext + " source=" + bodyCloneResult.Source + " body=" + DescribeGameObject(bodyCloneResult.Body) + " nameplate=" + DescribeGameObject(bodyCloneResult.Nameplate)); } } private static BodyCloneResult CreateFrozenBodyClone(string label, object player, GameObject sourceBody) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) GameObject root = GetRoot(); GameObject val = Object.Instantiate(sourceBody, sourceBody.transform.position, sourceBody.transform.rotation); ((Object)val).name = "DeathBodyOutline_Body_" + SanitizeName(label) + "_" + s_outlines.Count.ToString(CultureInfo.InvariantCulture); val.transform.SetParent(root.transform, true); val.SetActive(true); int num = FreezeBodyClone(val); string text = TryCompleteLocalHeadlessBody(label, player, sourceBody, val); GameObject val2 = TryCloneNameplate(player, label, val); LogVerbose("body clone created label=" + label + " stripped=" + num.ToString(CultureInfo.InvariantCulture) + " localHeadCompletion=" + text + " source=" + DescribeGameObject(sourceBody) + " clone=" + DescribeGameObject(val) + " nameplate=" + DescribeGameObject(val2)); return new BodyCloneResult { Body = val, Nameplate = val2, Source = DescribeGameObject(sourceBody), LocalHeadCompletion = text }; } private static void SetLayerRecursive(GameObject gameObject, int layer) { if ((Object)(object)gameObject == (Object)null) { return; } foreach (Transform componentsInChild in gameObject.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null) { ((Component)componentsInChild).gameObject.layer = layer; } } } private static int FreezeBodyClone(GameObject clone) { int num = 0; foreach (Animator componentsInChild in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { componentsInChild.keepAnimatorStateOnDisable = true; componentsInChild.speed = 0f; ((Behaviour)componentsInChild).enabled = false; num++; } } foreach (Renderer componentsInChild2 in clone.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild2 != (Object)null) { componentsInChild2.enabled = true; } } foreach (Collider componentsInChild3 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild3 == (Object)null)) { componentsInChild3.enabled = false; num++; } } foreach (Rigidbody componentsInChild4 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild4 == (Object)null)) { componentsInChild4.isKinematic = true; componentsInChild4.detectCollisions = false; num++; } } foreach (Light componentsInChild5 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild5 == (Object)null)) { ((Behaviour)componentsInChild5).enabled = false; num++; } } foreach (Behaviour componentsInChild6 in clone.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild6 == (Object)null || componentsInChild6 is Animator || componentsInChild6 is Light) { continue; } string text = ((object)componentsInChild6).GetType().FullName ?? ((object)componentsInChild6).GetType().Name; if (text.IndexOf("Renderer", StringComparison.OrdinalIgnoreCase) < 0) { try { componentsInChild6.enabled = false; num++; } catch (Exception ex) { LogVerbose("body behaviour disable failed type=" + text + " error=" + ex.GetType().Name); } } } return num; } private static GameObject ResolveCurrentVisualBodyObject(object player, string context) { object value = GetFieldOrProp(player, "CurrentBody") ?? GetFieldOrProp(player, "_CurrentBody_k__BackingField"); bool? boolValue = GetBoolValue(GetFieldOrProp(player, "IsCurrentBodyHeadless") ?? GetFieldOrProp(player, "_IsCurrentBodyHeadless_k__BackingField")); object obj = GetFieldOrProp(player, "AnimHandler") ?? GetFieldOrProp(player, "animHandler"); object fieldOrProp = GetFieldOrProp(obj, "VisualObj"); GameObject val = (GameObject)(((fieldOrProp is GameObject) ? fieldOrProp : null) ?? ((object)/*isinst with value type is only supported in some contexts*/)); object value2 = GetFieldOrProp(obj, "CurrentState") ?? GetFieldOrProp(obj, "_currentState"); bool flag = HasRenderer(val); LogVerbose("body source context=" + context + " source=AnimationHandler.VisualObj currentBody=" + FormatObjectValue(value) + " isCurrentHeadless=" + FormatObjectValue(boolValue) + " animState=" + FormatObjectValue(value2) + " hasRenderer=" + flag.ToString(CultureInfo.InvariantCulture) + " visual=" + DescribeGameObject(val)); if (!flag) { return null; } return val; } private static bool LooksHeadless(GameObject gameObject) { if ((Object)(object)gameObject == (Object)null) { return false; } Transform val = gameObject.transform; while ((Object)(object)val != (Object)null) { if (((Object)val).name.IndexOf("Headless", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } val = val.parent; } return false; } private static Vector3 ResolveHeadCenter(GameObject gameObject) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gameObject == (Object)null) { return Vector3.zero; } foreach (Transform componentsInChild in gameObject.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { string text = ((Object)componentsInChild).name.ToLowerInvariant(); if (!text.Contains("headless") && (text == "head" || text.EndsWith(":head", StringComparison.Ordinal) || text.EndsWith("_head", StringComparison.Ordinal))) { return componentsInChild.position; } } } if (!TryComputeRendererBounds(gameObject, out var bounds)) { return gameObject.transform.position + Vector3.up * 1.6f; } return new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y, ((Bounds)(ref bounds)).center.z); } public override void OnInitializeMelon() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown MelonPreferences_Category obj = MelonPreferences.CreateCategory("DeathBodyOutline"); s_visualMode = obj.CreateEntry("VisualMode", 1, "Visual Mode", (string)null, false, false, (ValueValidator)null, (string)null); s_verboseLogs = obj.CreateEntry("VerboseLogs", false, "Verbose Logs", (string)null, false, false, (ValueValidator)null, (string)null); s_harmony = new Harmony("kp0hyc.deathbodyoutline"); PatchFinalDeathRpc(); ((MelonBase)this).LoggerInstance.Msg("DeathBodyOutline initialized."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { ClearOutlines("scene loaded " + sceneName); } private void PatchFinalDeathRpc() { //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown Type type = FindType("Death") ?? FindType("ValkoGames.Labyrinthine.Players.Death"); if (type == null) { ((MelonBase)this).LoggerInstance.Warning("Death type was not found; outlines will not spawn."); return; } MethodInfo methodInfo = AccessTools.Method(type, "UserCode_RpcOnBleedOutTimeout__SByte__Boolean", new Type[2] { typeof(sbyte), typeof(bool) }, (Type[])null); if (methodInfo == null) { ((MelonBase)this).LoggerInstance.Warning("Final-death RPC method was not found; outlines will not spawn."); return; } MethodInfo methodInfo2 = AccessTools.Method(typeof(DeathBodyOutline), "OnFinalDeathRpcPrefix", (Type[])null, (Type[])null); s_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); LogVerbose("patched " + type.FullName + "." + methodInfo.Name); } private static void OnFinalDeathRpcPrefix(object __instance, sbyte livesLeft, bool respawn) { try { SpawnOutlineForDeath(__instance, livesLeft, respawn); } catch (Exception ex) { MelonLogger.Warning("[Death Body Outline] final death outline failed: " + ex.GetType().Name + " " + ex.Message); } } private static void SpawnOutlineForDeath(object deathInstance, sbyte livesLeft, bool respawn) { int visualMode = GetVisualMode(); object fieldOrProp = GetFieldOrProp(deathInstance, "playerNetworkSync"); if (fieldOrProp == null) { LogVerbose("final death ignored: playerNetworkSync missing"); return; } string playerKey = GetPlayerKey(fieldOrProp); switch (visualMode) { case 0: LogVerbose("final death ignored: visual mode disabled key=" + playerKey + " livesLeft=" + livesLeft + " respawn=" + respawn); return; case 1: if (!IsOutOfLivesDeath(livesLeft, respawn)) { LogVerbose("final death ignored by visual mode key=" + playerKey + " mode=" + visualMode.ToString(CultureInfo.InvariantCulture) + " livesLeft=" + livesLeft + " respawn=" + respawn); return; } break; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (s_lastOutlineTimeByKey.TryGetValue(playerKey, out var value) && realtimeSinceStartup - value < 2f) { LogVerbose("duplicate final death ignored key=" + playerKey + " age=" + (realtimeSinceStartup - value).ToString("0.00", CultureInfo.InvariantCulture)); return; } s_lastOutlineTimeByKey[playerKey] = realtimeSinceStartup; string playerLabel = GetPlayerLabel(fieldOrProp); LogVerbose("final death source decision start key=" + playerKey + " label=" + playerLabel + " mode=" + visualMode.ToString(CultureInfo.InvariantCulture) + " livesLeft=" + livesLeft + " respawn=" + respawn); GameObject val = ResolveCurrentVisualBodyObject(fieldOrProp, "final-time"); if ((Object)(object)val != (Object)null) { LogVerbose("final death source selected key=" + playerKey + " label=" + playerLabel + " sourceMode=final-time body=" + DescribeGameObject(val)); CreateBodyOutline(playerLabel, fieldOrProp, val, "final-time"); LogVerbose("outline spawned key=" + playerKey + " label=" + playerLabel + " livesLeft=" + livesLeft + " respawn=" + respawn + " source=" + DescribeGameObject(val)); } else { LogVerbose("final death source selected key=" + playerKey + " label=" + playerLabel + " sourceMode=none player=" + DescribeObject(fieldOrProp)); } } private static bool IsOutOfLivesDeath(sbyte livesLeft, bool respawn) { if (livesLeft > 0) { return !respawn; } return true; } private static int GetVisualMode() { int num = ((s_visualMode == null) ? 1 : s_visualMode.Value); if (num < 0) { return 0; } if (num > 2) { return 2; } return num; } private static string TryCompleteLocalHeadlessBody(string label, object player, GameObject sourceBody, GameObject clone) { if (!LooksHeadless(sourceBody)) { return "source-headed"; } if (!IsLocalPlayer(player)) { LogVerbose("local head completion skipped: non-local selected source is headless label=" + label + " player=" + DescribeObject(player) + " source=" + DescribeGameObject(sourceBody) + " clone=" + DescribeGameObject(clone)); return "source-headless local=False"; } GameObject val = ResolveLocalHeadSource(player, sourceBody); if ((Object)(object)val == (Object)null) { LogVerbose("local head completion skipped: explicit head source missing label=" + label + " player=" + DescribeObject(player) + " source=" + DescribeGameObject(sourceBody) + " clone=" + DescribeGameObject(clone)); return "source-headless local=True headSource=none"; } int num = CompleteHeadlessBodyRendererFromSource(val, clone); int num2 = CopyLocalHairChildrenFromSource(val, clone); return "source-headless local=True headSource=" + ((Object)val).name + " completedBody=" + num.ToString(CultureInfo.InvariantCulture) + " copiedHair=" + num2.ToString(CultureInfo.InvariantCulture) + " label=" + label; } private static bool IsLocalPlayer(object player) { if (TryConvertBool(GetFieldOrProp(player, "isOwned") ?? GetFieldOrProp(player, "IsOwned") ?? GetFieldOrProp(player, "_isOwned_k__BackingField") ?? GetFieldOrProp(player, "_IsOwned_k__BackingField"), out var result)) { return result; } Type type = FindType("PlayerNetworkSync"); object obj = GetStaticFieldOrProp(type, "instance") ?? GetStaticFieldOrProp(type, "Instance"); object fieldOrProp = GetFieldOrProp(player, "gameObject"); GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); object fieldOrProp2 = GetFieldOrProp(obj, "gameObject"); GameObject val2 = (GameObject)((fieldOrProp2 is GameObject) ? fieldOrProp2 : null); bool result2 = obj == player || ((Object)(object)val != (Object)null && val == val2); LogVerbose("local player ownership field missing; instance match=" + result2.ToString(CultureInfo.InvariantCulture) + " player=" + DescribeObject(player) + " instance=" + DescribeObject(obj)); return result2; } private static GameObject ResolveLocalHeadSource(object player, GameObject sourceBody) { object value = GetFieldOrProp(player, "CurrentBody") ?? GetFieldOrProp(player, "_CurrentBody_k__BackingField"); if (!TryConvertLong(value, out var result) || result < 0 || result > int.MaxValue) { LogVerbose("local head source missing: currentBody invalid value=" + FormatObjectValue(value)); return null; } object indexedObject = GetIndexedObject(GetFieldOrProp(player, "playerBodies"), (int)result); object fieldOrProp = GetFieldOrProp(indexedObject, "remote"); GameObject val = (GameObject)((fieldOrProp is GameObject) ? fieldOrProp : null); LogVerbose("local head source lookup currentBody=" + result.ToString(CultureInfo.InvariantCulture) + " container=" + DescribeObject(indexedObject) + " remote=" + DescribeGameObject(val)); if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)sourceBody || LooksHeadless(val) || !HasRenderer(val)) { return null; } return val; } private static object GetIndexedObject(object values, int index) { if (values == null || index < 0) { return null; } if (values is Array array && index < array.Length) { return array.GetValue(index); } if (!TryConvertLong(GetFieldOrProp(values, "Count"), out var result)) { TryConvertLong(GetFieldOrProp(values, "Length"), out result); } if (index >= result) { return null; } MethodInfo method = values.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return null; } try { return method.Invoke(values, new object[1] { index }); } catch { return null; } } private static int CompleteHeadlessBodyRendererFromSource(GameObject headSource, GameObject clone) { //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) int num = 0; foreach (Renderer item in ((IEnumerable)headSource.GetComponentsInChildren(true)).OrderBy((Renderer renderer) => GetDepth(((Component)renderer).transform))) { if ((Object)(object)item == (Object)null || (Object)(object)((Component)item).transform == (Object)null || (Object)(object)((Component)item).transform == (Object)(object)headSource.transform || IsHairTransform(headSource.transform, ((Component)item).transform)) { continue; } string relativePath = GetRelativePath(headSource.transform, ((Component)item).transform); Transform val = FindChildByPath(clone.transform, relativePath); if ((Object)(object)val == (Object)null || !HasRenderer(((Component)val).gameObject)) { continue; } Transform val2 = val.parent ?? clone.transform; GameObject val3 = Object.Instantiate(((Component)((Component)item).transform).gameObject); ((Object)val3).name = ((Object)((Component)((Component)item).transform).gameObject).name + "_DeathCompletedBody"; val3.transform.SetParent(val2, false); val3.transform.localPosition = ((Component)item).transform.localPosition; val3.transform.localRotation = ((Component)item).transform.localRotation; val3.transform.localScale = ((Component)item).transform.localScale; SetLayerRecursive(val3, clone.layer); string text = RemapSkinnedMeshBones(val3, headSource.transform, clone.transform); RendererMaterialCopyResult rendererMaterialCopyResult = CopyRendererMaterialsFromClone(((Component)val).gameObject, val3); if (rendererMaterialCopyResult.MaterialsCopied <= 0) { Object.Destroy((Object)(object)val3); LogVerbose("local body completion skipped path=" + relativePath + " reason=no-live-materials source=" + DescribeGameObject(headSource) + " cloneTarget=" + DescribeGameObject(((Component)val).gameObject)); continue; } foreach (Renderer componentsInChild in val3.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null) { componentsInChild.enabled = true; } } foreach (Renderer componentsInChild2 in ((Component)val).gameObject.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild2 != (Object)null) { componentsInChild2.enabled = false; } } num++; LogVerbose("local body completion copied path=" + relativePath + " disabledClone=" + GetRelativePath(clone.transform, val) + " parent=" + GetRelativePath(clone.transform, val2) + " materials=" + rendererMaterialCopyResult.ToString() + " " + text); } LogVerbose("local body completion summary source=" + DescribeGameObject(headSource) + " clone=" + DescribeGameObject(clone) + " completed=" + num.ToString(CultureInfo.InvariantCulture)); return num; } private static int CopyLocalHairChildrenFromSource(GameObject headSource, GameObject clone) { //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (Renderer item in ((IEnumerable)headSource.GetComponentsInChildren(true)).OrderBy((Renderer renderer) => GetDepth(((Component)renderer).transform))) { Transform transform = (((Object)(object)item == (Object)null) ? null : ((Component)item).transform); if (!((Object)(object)transform == (Object)null) && !((Object)(object)transform == (Object)(object)headSource.transform) && IsHairTransform(headSource.transform, transform) && !list.Any((Transform existing) => IsChildOf(transform, existing))) { list.Add(transform); } } int num = 0; foreach (Transform item2 in list) { Transform val = FindMatchingCloneParent(headSource.transform, clone.transform, item2.parent) ?? FindHeadAttachParent(clone.transform); if ((Object)(object)val == (Object)null) { LogVerbose("local hair copy skipped: target parent missing source=" + GetRelativePath(headSource.transform, item2)); continue; } GameObject obj = Object.Instantiate(((Component)item2).gameObject); ((Object)obj).name = ((Object)((Component)item2).gameObject).name + "_DeathHair"; obj.transform.SetParent(val, false); obj.transform.localPosition = item2.localPosition; obj.transform.localRotation = item2.localRotation; obj.transform.localScale = item2.localScale; SetLayerRecursive(obj, clone.layer); string text = RemapSkinnedMeshBones(obj, headSource.transform, clone.transform); foreach (Renderer componentsInChild in obj.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null) { componentsInChild.enabled = true; } } num++; LogVerbose("local hair copied path=" + GetRelativePath(headSource.transform, item2) + " parent=" + GetRelativePath(clone.transform, val) + " " + text); } LogVerbose("local hair copy summary source=" + DescribeGameObject(headSource) + " clone=" + DescribeGameObject(clone) + " selected=" + list.Count.ToString(CultureInfo.InvariantCulture) + " copied=" + num.ToString(CultureInfo.InvariantCulture)); return num; } private static RendererMaterialCopyResult CopyRendererMaterialsFromClone(GameObject source, GameObject target) { Il2CppArrayBase componentsInChildren = source.GetComponentsInChildren(true); Il2CppArrayBase componentsInChildren2 = target.GetComponentsInChildren(true); int num = 0; int num2 = 0; int num3 = Math.Min(componentsInChildren.Length, componentsInChildren2.Length); for (int i = 0; i < num3; i++) { Renderer val = componentsInChildren[i]; Renderer val2 = componentsInChildren2[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val2 == (Object)null)) { Il2CppReferenceArray sharedMaterials = val.sharedMaterials; if (sharedMaterials != null && ((Il2CppArrayBase)(object)sharedMaterials).Length != 0) { val2.sharedMaterials = sharedMaterials; num++; num2 += ((Il2CppArrayBase)(object)sharedMaterials).Length; } } } return new RendererMaterialCopyResult(componentsInChildren.Length, componentsInChildren2.Length, num, num2); } private static bool IsHairTransform(Transform root, Transform transform) { if ((Object)(object)transform == (Object)null || IsUnderLimbTransform(transform)) { return false; } if (!GetRelativePath(root, transform).ToLowerInvariant().Contains("hair")) { return false; } return true; } private static bool IsUnderLimbTransform(Transform transform) { Transform val = transform; while ((Object)(object)val != (Object)null) { string text = ((Object)val).name.ToLowerInvariant(); if (text.Contains("hand") || text.Contains("forearm") || text.Contains("leftarm") || text.Contains("rightarm")) { return true; } val = val.parent; } return false; } private static GameObject TryCloneNameplate(object player, string label, GameObject bodyClone) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) object fieldOrProp = GetFieldOrProp(player, "nameplateCanvas"); object canvas = ((fieldOrProp is Canvas) ? fieldOrProp : null); object fieldOrProp2 = GetFieldOrProp(player, "namePlate"); Component namePlate = (Component)((fieldOrProp2 is Component) ? fieldOrProp2 : null); GameObject val = ResolveNameplateSource((Canvas)canvas, namePlate); if ((Object)(object)val == (Object)null) { LogVerbose("nameplate clone skipped: source missing"); return null; } GameObject val2 = Object.Instantiate(val, val.transform.position, val.transform.rotation); ((Object)val2).name = "DeathBodyOutline_Nameplate_" + SanitizeName(label); val2.transform.SetParent(bodyClone.transform, true); val2.SetActive(true); int num = PrepareNameplateClone(val2, label); RepositionNameplateClone(val2, bodyClone); LogVerbose("nameplate cloned changed=" + num.ToString(CultureInfo.InvariantCulture) + " source=" + DescribeGameObject(val) + " clone=" + DescribeGameObject(val2)); return val2; } private static GameObject ResolveNameplateSource(Canvas canvas, Component namePlate) { if ((Object)(object)namePlate != (Object)null) { Canvas componentInParent = namePlate.GetComponentInParent(true); if ((Object)(object)componentInParent != (Object)null && HasTmpText(((Component)componentInParent).gameObject)) { return ((Component)componentInParent).gameObject; } if (HasTmpText(namePlate.gameObject)) { return namePlate.gameObject; } } if ((Object)(object)canvas != (Object)null && HasTmpText(((Component)canvas).gameObject)) { return ((Component)canvas).gameObject; } if (!((Object)(object)namePlate != (Object)null)) { if (!((Object)(object)canvas != (Object)null)) { return null; } return ((Component)canvas).gameObject; } return namePlate.gameObject; } private static void RepositionNameplateClone(GameObject nameplate, GameObject bodyClone) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)nameplate == (Object)null || (Object)(object)bodyClone == (Object)null) { return; } Vector3 position = bodyClone.transform.position; Vector3 val = ResolveHeadCenter(bodyClone); bool flag = IsSaneNameplateHeadAnchor(bodyClone, val); bool flag2 = false; string text = "{none}"; Bounds bounds; if (flag) { ((Vector3)(ref position))..ctor(val.x, position.y, val.z); } else if (TryComputeRendererBounds(bodyClone, out bounds)) { float num = Vector2.Distance(new Vector2(position.x, position.z), new Vector2(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).center.z)); text = "center=" + FormatVector(((Bounds)(ref bounds)).center) + " size=" + FormatVector(((Bounds)(ref bounds)).size) + " horizontalOffset=" + num.ToString("0.00", CultureInfo.InvariantCulture); if (num <= 1.35f) { ((Vector3)(ref position))..ctor(((Bounds)(ref bounds)).center.x, position.y, ((Bounds)(ref bounds)).center.z); flag2 = true; } } float num2 = bodyClone.transform.position.y + 1.08f; float val2 = (flag ? (val.y + 0.42f) : num2); Vector3 val3 = default(Vector3); ((Vector3)(ref val3))..ctor(position.x, Math.Max(num2, val2), position.z); Vector3 position2 = nameplate.transform.position; nameplate.transform.position = val3; LogVerbose("nameplate reposition before=" + FormatVector(position2) + " after=" + FormatVector(val3) + " root=" + FormatVector(bodyClone.transform.position) + " head=" + FormatVector(val) + " usedHeadCenter=" + flag + " usedBoundsCenter=" + flag2 + " heightAboveRoot=" + 1.08f.ToString("0.00", CultureInfo.InvariantCulture) + " heightAboveHead=" + 0.42f.ToString("0.00", CultureInfo.InvariantCulture) + " bounds=" + text); } private static bool IsSaneNameplateHeadAnchor(GameObject bodyClone, Vector3 headCenter) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0038: 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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)bodyClone == (Object)null || headCenter == Vector3.zero) { return false; } return Vector2.Distance(new Vector2(bodyClone.transform.position.x, bodyClone.transform.position.z), new Vector2(headCenter.x, headCenter.z)) <= 1.35f; } private static int PrepareNameplateClone(GameObject clone, string label) { int num = 0; foreach (Transform componentsInChild in clone.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null) { ((Component)componentsInChild).gameObject.SetActive(true); } } foreach (Canvas componentsInChild2 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild2 == (Object)null)) { ((Behaviour)componentsInChild2).enabled = true; num++; } } foreach (CanvasGroup componentsInChild3 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild3 == (Object)null)) { componentsInChild3.alpha = 1f; componentsInChild3.interactable = false; componentsInChild3.blocksRaycasts = false; num++; } } foreach (TMP_Text componentsInChild4 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild4 == (Object)null)) { try { ApplyDeadNameText(componentsInChild4, label); ((Component)componentsInChild4).gameObject.SetActive(true); componentsInChild4.ForceMeshUpdate(false, false); num++; } catch (Exception ex) { LogVerbose("nameplate TMP update failed type=" + ((object)componentsInChild4).GetType().FullName + " error=" + ex.GetType().Name); } } } int num2 = 0; foreach (Graphic componentsInChild5 in clone.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild5 == (Object)null) && !((Object)(object)((Component)componentsInChild5).GetComponent() != (Object)null)) { ((Behaviour)componentsInChild5).enabled = false; ((Component)componentsInChild5).gameObject.SetActive(false); num2++; } } LogVerbose("nameplate prepare label=" + label + " canvases=" + clone.GetComponentsInChildren(true).Length.ToString(CultureInfo.InvariantCulture) + " canvasGroups=" + clone.GetComponentsInChildren(true).Length.ToString(CultureInfo.InvariantCulture) + " tmpTexts=" + clone.GetComponentsInChildren(true).Length.ToString(CultureInfo.InvariantCulture) + " disabledGraphics=" + num2.ToString(CultureInfo.InvariantCulture) + " runtimeBehavioursKept=True"); return num; } private static void UpdateNameplateText(GameObject nameplate, string label) { if ((Object)(object)nameplate == (Object)null) { return; } foreach (TMP_Text componentsInChild in nameplate.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { try { ApplyDeadNameText(componentsInChild, label); ((Component)componentsInChild).gameObject.SetActive(true); componentsInChild.ForceMeshUpdate(false, false); } catch (Exception ex) { LogVerbose("nameplate text reveal update failed type=" + ((object)componentsInChild).GetType().FullName + " error=" + ex.GetType().Name); } } } } private static bool HasTmpText(GameObject gameObject) { if ((Object)(object)gameObject != (Object)null) { return ((IEnumerable)gameObject.GetComponentsInChildren(true)).Any((TMP_Text text) => (Object)(object)text != (Object)null); } return false; } private static void ApplyDeadNameText(TMP_Text text, string label) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) text.text = label + " ☠"; ((Graphic)text).color = DeadNameColor; } private static void EnableVisibleRenderers(GameObject gameObject, bool enabled) { if ((Object)(object)gameObject == (Object)null) { return; } foreach (Renderer componentsInChild in gameObject.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null) { componentsInChild.enabled = enabled; } } } private static bool HasRenderer(GameObject gameObject) { if ((Object)(object)gameObject != (Object)null) { return ((IEnumerable)gameObject.GetComponentsInChildren(true)).Any((Renderer renderer) => (Object)(object)renderer != (Object)null); } return false; } private static bool TryComputeRendererBounds(GameObject gameObject, out Bounds bounds) { return TryComputeRendererBounds(gameObject, out bounds, includeDeathAppliedAssets: false); } private static bool TryComputeRendererBounds(GameObject gameObject, out Bounds bounds, bool includeDeathAppliedAssets) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) bounds = default(Bounds); if ((Object)(object)gameObject == (Object)null) { return false; } bool flag = false; foreach (Renderer componentsInChild in gameObject.GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null) && (includeDeathAppliedAssets || !IsDeathAppliedAssetRenderer(componentsInChild))) { if (!flag) { bounds = componentsInChild.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(componentsInChild.bounds); } } } return flag; } private static bool IsDeathAppliedAssetRenderer(Renderer renderer) { Transform val = (((Object)(object)renderer == (Object)null) ? null : ((Component)renderer).transform); while ((Object)(object)val != (Object)null) { if (!string.IsNullOrWhiteSpace(((Object)val).name) && ((Object)val).name.IndexOf("_DeathAppliedAsset", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } val = val.parent; } return false; } private static GameObject GetRoot() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if ((Object)(object)s_root != (Object)null) { return s_root; } s_root = new GameObject("DeathBodyOutline_Root"); return s_root; } private static void ClearOutlines(string reason) { foreach (GameObject s_outline in s_outlines) { if ((Object)(object)s_outline != (Object)null) { Object.Destroy((Object)(object)s_outline); } } s_outlines.Clear(); s_lastOutlineTimeByKey.Clear(); if ((Object)(object)s_root != (Object)null) { Object.Destroy((Object)(object)s_root); s_root = null; } LogVerbose("outlines cleared: " + reason); } private static Type FindType(string fullName) { return Il2CppReflection.FindType(fullName); } private static string GetPlayerKey(object player) { if (TryConvertLong(GetFieldOrProp(player, "netId"), out var result) && result > 0) { return "net:" + result.ToString(CultureInfo.InvariantCulture); } object clientData = GetClientData(player); if (TryConvertLong(GetFieldOrProp(clientData, "SteamID") ?? GetFieldOrProp(clientData, "SteamId") ?? GetFieldOrProp(clientData, "steamID") ?? GetFieldOrProp(clientData, "ID"), out var result2) && result2 > 0) { return "steam:" + result2.ToString(CultureInfo.InvariantCulture); } return "obj:" + RuntimeHelpersHash(player).ToString(CultureInfo.InvariantCulture); } private static string GetPlayerLabel(object player) { object clientData = GetClientData(player); string text = GetStringValue(GetFieldOrProp(clientData, "Name")) ?? GetStringValue(GetFieldOrProp(clientData, "_Name_k__BackingField")) ?? GetStringValue(GetFieldOrProp(GetFieldOrProp(player, "namePlate"), "text")) ?? GetStringValue(GetFieldOrProp(player, "name")) ?? GetStringValue(GetFieldOrProp(GetFieldOrProp(player, "gameObject"), "name")); if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return "player"; } private static object GetClientData(object player) { return GetFieldOrProp(player, "ClientData") ?? GetFieldOrProp(player, "NetworkclientData") ?? GetFieldOrProp(player, "clientData") ?? GetFieldOrProp(player, "_ClientData_k__BackingField") ?? GetFieldOrProp(player, "_NetworkclientData_k__BackingField"); } private static object GetFieldOrProp(object obj, string name) { return Il2CppReflection.GetFieldOrProp(obj, name); } private static object GetStaticFieldOrProp(Type type, string name) { return Il2CppReflection.GetStaticFieldOrProp(type, name); } private static object FindFirstObjectOfType(Type type) { return Il2CppReflection.FindFirstObjectOfType(type); } private static string GetStringValue(object value) { return ValueConversion.GetStringValue(value); } private static bool? GetBoolValue(object value) { return ValueConversion.GetBoolValue(value); } private static bool TryConvertBool(object value, out bool result) { return ValueConversion.TryConvertBool(value, out result); } private static string FormatObjectValue(object value) { if (value == null) { return "{none}"; } if (value is bool) { if (!(bool)value) { return "False"; } return "True"; } return value.ToString(); } private static bool TryConvertLong(object value, out long result) { return ValueConversion.TryConvertLong(value, out result); } private static int RuntimeHelpersHash(object obj) { if (obj != null) { return RuntimeHelpers.GetHashCode(obj); } return 0; } private static string SanitizeName(string value) { if (string.IsNullOrWhiteSpace(value)) { return "player"; } return new string(value.Select((char ch) => (!char.IsLetterOrDigit(ch)) ? '_' : ch).ToArray()).Trim('_'); } private static string FormatVector(Vector3 vector) { return vector.x.ToString("0.00", CultureInfo.InvariantCulture) + "," + vector.y.ToString("0.00", CultureInfo.InvariantCulture) + "," + vector.z.ToString("0.00", CultureInfo.InvariantCulture); } private static string DescribeObject(object obj) { if (obj == null) { return "{none}"; } GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val != null) { return DescribeGameObject(val); } Component val2 = (Component)((obj is Component) ? obj : null); if (val2 != null) { return ((object)val2).GetType().FullName + " go=" + DescribeGameObject(val2.gameObject); } return obj.GetType().FullName + "#" + RuntimeHelpersHash(obj).ToString(CultureInfo.InvariantCulture); } private static string DescribeGameObject(GameObject gameObject) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gameObject == (Object)null) { return "{none}"; } return ((Object)gameObject).name + " activeSelf=" + gameObject.activeSelf + " activeHierarchy=" + gameObject.activeInHierarchy + " pos=" + FormatVector(gameObject.transform.position) + " renderers=" + gameObject.GetComponentsInChildren(true).Length.ToString(CultureInfo.InvariantCulture); } private static void LogVerbose(string message) { if (s_verboseLogs != null && s_verboseLogs.Value) { MelonLogger.Msg("[Death Body Outline] [Debug] " + message); } } private static string RemapSkinnedMeshBones(GameObject copy, Transform donorRoot, Transform cloneRoot) { int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; foreach (SkinnedMeshRenderer componentsInChild in copy.GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild == (Object)null) { continue; } num++; Il2CppReferenceArray bones = componentsInChild.bones; if (bones != null && ((Il2CppArrayBase)(object)bones).Length > 0) { Transform[] array = (Transform[])(object)new Transform[((Il2CppArrayBase)(object)bones).Length]; for (int i = 0; i < ((Il2CppArrayBase)(object)bones).Length; i++) { Transform val = ((Il2CppArrayBase)(object)bones)[i]; if ((Object)(object)val == (Object)null) { array[i] = copy.transform; num4++; continue; } if (IsChildOfOrSelf(val, copy.transform)) { array[i] = val; num3++; continue; } Transform val2 = MapDonorTransformToClone(donorRoot, cloneRoot, val); if ((Object)(object)val2 != (Object)null) { array[i] = val2; num2++; } else { array[i] = copy.transform; num4++; } } componentsInChild.bones = Il2CppReferenceArray.op_Implicit(array); } Transform rootBone = componentsInChild.rootBone; if (!((Object)(object)rootBone == (Object)null) && !IsChildOfOrSelf(rootBone, copy.transform)) { Transform val3 = MapDonorTransformToClone(donorRoot, cloneRoot, rootBone); if ((Object)(object)val3 != (Object)null) { componentsInChild.rootBone = val3; num5++; } else if (IsChildOfOrSelf(rootBone, donorRoot)) { componentsInChild.rootBone = cloneRoot; num6++; } } } return "skinnedRenderers=" + num.ToString(CultureInfo.InvariantCulture) + " mappedBones=" + num2.ToString(CultureInfo.InvariantCulture) + " keptLocalBones=" + num3.ToString(CultureInfo.InvariantCulture) + " detachedBones=" + num4.ToString(CultureInfo.InvariantCulture) + " mappedRootBones=" + num5.ToString(CultureInfo.InvariantCulture) + " detachedRootBones=" + num6.ToString(CultureInfo.InvariantCulture); } private static int GetDepth(Transform transform) { int num = 0; Transform val = transform; while ((Object)(object)val != (Object)null) { num++; val = val.parent; } return num; } private static bool IsChildOf(Transform child, Transform possibleParent) { Transform val = (((Object)(object)child == (Object)null) ? null : child.parent); while ((Object)(object)val != (Object)null) { if ((Object)(object)val == (Object)(object)possibleParent) { return true; } val = val.parent; } return false; } private static bool IsChildOfOrSelf(Transform child, Transform possibleParent) { Transform val = child; while ((Object)(object)val != (Object)null) { if ((Object)(object)val == (Object)(object)possibleParent) { return true; } val = val.parent; } return false; } private static Transform MapDonorTransformToClone(Transform donorRoot, Transform cloneRoot, Transform donorTransform) { if ((Object)(object)donorRoot == (Object)null || (Object)(object)cloneRoot == (Object)null || (Object)(object)donorTransform == (Object)null) { return null; } if ((Object)(object)donorTransform == (Object)(object)donorRoot) { return cloneRoot; } if (!IsChildOfOrSelf(donorTransform, donorRoot)) { return null; } Transform val = FindChildByPath(cloneRoot, GetRelativePath(donorRoot, donorTransform)); if ((Object)(object)val != (Object)null) { return val; } return FindDescendantByName(cloneRoot, ((Object)donorTransform).name); } private static Transform FindMatchingCloneParent(Transform donorRoot, Transform cloneRoot, Transform donorParent) { if ((Object)(object)donorRoot == (Object)null || (Object)(object)cloneRoot == (Object)null || (Object)(object)donorParent == (Object)null || (Object)(object)donorParent == (Object)(object)donorRoot) { return cloneRoot; } Transform val = FindChildByPath(cloneRoot, GetRelativePath(donorRoot, donorParent)); if ((Object)(object)val != (Object)null) { return val; } return FindDescendantByName(cloneRoot, ((Object)donorParent).name); } private static Transform FindHeadAttachParent(Transform cloneRoot) { if ((Object)(object)cloneRoot == (Object)null) { return null; } foreach (Transform componentsInChild in ((Component)cloneRoot).GetComponentsInChildren(true)) { if (!((Object)(object)componentsInChild == (Object)null)) { string text = ((Object)componentsInChild).name.ToLowerInvariant(); if (text == "head" || text.EndsWith(":head", StringComparison.Ordinal) || text.EndsWith("_head", StringComparison.Ordinal)) { return componentsInChild; } } } return cloneRoot; } private static Transform FindChildByPath(Transform root, string path) { if ((Object)(object)root == (Object)null) { return null; } if (string.IsNullOrWhiteSpace(path)) { return root; } Transform val = root; string[] array = path.Split('/'); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text)) { val = FindDirectChildByName(val, text); if ((Object)(object)val == (Object)null) { return null; } } } return val; } private static Transform FindDirectChildByName(Transform root, string name) { if ((Object)(object)root == (Object)null) { return null; } for (int i = 0; i < root.childCount; i++) { Transform child = root.GetChild(i); if ((Object)(object)child != (Object)null && string.Equals(((Object)child).name, name, StringComparison.Ordinal)) { return child; } } return null; } private static Transform FindDescendantByName(Transform root, string name) { if ((Object)(object)root == (Object)null) { return null; } foreach (Transform componentsInChild in ((Component)root).GetComponentsInChildren(true)) { if ((Object)(object)componentsInChild != (Object)null && string.Equals(((Object)componentsInChild).name, name, StringComparison.Ordinal)) { return componentsInChild; } } return null; } private static string GetRelativePath(Transform root, Transform target) { if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)null) { return "{none}"; } if ((Object)(object)root == (Object)(object)target) { return string.Empty; } Stack stack = new Stack(); Transform val = target; while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root) { stack.Push(((Object)val).name); val = val.parent; } return string.Join("/", stack); } } namespace Kp0hyc.Labyrinthine.Shared; internal static class Il2CppReflection { public static Type FindType(string fullName) { if (string.IsNullOrWhiteSpace(fullName)) { return null; } string il2cppName = (fullName.StartsWith("Il2Cpp.", StringComparison.Ordinal) ? fullName : ("Il2Cpp." + fullName)); string il2cppNamespaceName = (fullName.StartsWith("Il2Cpp", StringComparison.Ordinal) ? fullName : ("Il2Cpp" + fullName)); string shortName = (fullName.Contains(".") ? fullName.Substring(fullName.LastIndexOf('.') + 1) : fullName); Type type = Type.GetType(fullName) ?? Type.GetType(il2cppName) ?? Type.GetType(il2cppNamespaceName); if (type != null) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type2 = assembly.GetType(fullName, throwOnError: false) ?? assembly.GetType(il2cppName, throwOnError: false) ?? assembly.GetType(il2cppNamespaceName, throwOnError: false); if (type2 != null) { return type2; } } assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { Type[] source; try { source = assembly2.GetTypes(); } catch (ReflectionTypeLoadException ex) { source = ex.Types.Where((Type t) => t != null).ToArray(); } catch { continue; } Type type3 = source.FirstOrDefault((Type t) => t.Name == shortName || t.FullName == fullName || t.FullName == il2cppName || t.FullName == il2cppNamespaceName); if (type3 != null) { return type3; } } return null; } public static object GetFieldOrProp(object obj, string name) { if (obj == null || string.IsNullOrWhiteSpace(name)) { return null; } Type type = obj.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { try { return property.GetValue(obj); } catch { } } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(obj); } catch { } } type = type.BaseType; } return null; } public static object GetStaticFieldOrProp(Type type, string name) { if (type == null || string.IsNullOrWhiteSpace(name)) { return null; } Type type2 = type; while (type2 != null) { PropertyInfo property = type2.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0) { try { return property.GetValue(null); } catch { } } FieldInfo field = type2.GetField(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { return field.GetValue(null); } catch { } } type2 = type2.BaseType; } return null; } public static bool SetFieldOrProp(object obj, string name, object value) { if (obj == null || string.IsNullOrWhiteSpace(name)) { return false; } Type type = obj.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.GetIndexParameters().Length == 0 && property.CanWrite) { try { property.SetValue(obj, value); return true; } catch { } } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { try { field.SetValue(obj, value); return true; } catch { } } type = type.BaseType; } return false; } public static object FindFirstObjectOfType(Type type) { if (type == null) { return null; } Array array = InvokeFindObjectsOfType(typeof(Object), "FindObjectsOfType", type) ?? InvokeFindObjectsOfType(typeof(Resources), "FindObjectsOfTypeAll", type); if (array == null || array.Length <= 0) { return null; } return array.GetValue(0); } public static void ForEachListItem(object list, Action action) { if (list == null || action == null) { return; } if (list is Array array) { { foreach (object item in array) { action(item); } return; } } if (list is IEnumerable enumerable) { { foreach (object item2 in enumerable) { action(item2); } return; } } TryGetCollectionCount(list, out var count); if (count <= 0) { return; } MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { return; } for (int i = 0; i < count; i++) { try { action(method.Invoke(list, new object[1] { i })); } catch { } } } public static int GetCollectionCount(object list) { if (list == null) { return 0; } if (list is Array array) { return array.Length; } if (TryGetCollectionCount(list, out var count)) { return Math.Max(0, count); } int num = 0; if (list is IEnumerable enumerable) { foreach (object item in enumerable) { _ = item; num++; } } return num; } public static bool TryGetCollectionCount(object list, out int count) { count = 0; if (list == null) { return false; } if (list is Array array) { count = array.Length; return true; } Type type = list.GetType(); if (ValueConversion.TryConvertInt(GetFieldOrProp(list, "Count"), out count)) { return true; } if (ValueConversion.TryConvertInt(GetFieldOrProp(list, "Length"), out count)) { return true; } MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "get_Count" && m.GetParameters().Length == 0); if (methodInfo != null) { try { if (ValueConversion.TryConvertInt(methodInfo.Invoke(list, Array.Empty()), out count)) { return true; } } catch { } } MethodInfo methodInfo2 = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo m) => m.Name == "get_Length" && m.GetParameters().Length == 0); if (methodInfo2 != null) { try { return ValueConversion.TryConvertInt(methodInfo2.Invoke(list, Array.Empty()), out count); } catch { } } return false; } public static string DescribeCollectionAccessForLog(object list, bool enabled) { if (!enabled) { return "disabled"; } if (list == null) { return "null"; } try { Type type = list.GetType(); object fieldOrProp = GetFieldOrProp(list, "Count"); object fieldOrProp2 = GetFieldOrProp(list, "Length"); MethodInfo method = type.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); bool flag = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any((MethodInfo m) => m.Name == "get_Count" && m.GetParameters().Length == 0); string empty = string.Empty; try { empty = string.Join(",", (from i in type.GetInterfaces() select i.FullName ?? i.Name into n where n.IndexOf("IEnumerable", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("IReadOnlyList", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("IList", StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf("ICollection", StringComparison.OrdinalIgnoreCase) >= 0 select n).Take(6)); } catch { empty = "unavailable"; } return "type=" + SafeLogToken(type.FullName ?? type.Name) + " array=" + (list is Array) + " enumerable=" + (list is IEnumerable) + " count=" + FormatObjectForLog(fieldOrProp) + " length=" + FormatObjectForLog(fieldOrProp2) + " getCount=" + flag + " getItem=" + (method != null) + " helperCount=" + GetCollectionCount(list) + " interfaces=[" + empty + "]"; } catch (Exception ex) { return "describe-failed:" + ex.GetType().Name + ":" + SafeLogToken(ex.Message); } } public static bool TryGetIndexedItem(object list, object index, out object item) { item = null; if (list == null) { return false; } if (list is Array array && index is int num) { if (num < 0 || num >= array.Length) { return false; } item = array.GetValue(num); return true; } try { MethodInfo method = list.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { item = method.Invoke(list, new object[1] { index }); return true; } } catch { } if (index is int num2) { int num3 = 0; if (list is IEnumerable enumerable) { foreach (object item2 in enumerable) { if (num3 == num2) { item = item2; return true; } num3++; } } } return false; } private static Array InvokeFindObjectsOfType(Type ownerType, string methodName, Type searchedType) { try { return ownerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo candidate) { ParameterInfo[] parameters = candidate.GetParameters(); return candidate.Name == methodName && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type); })?.Invoke(null, new object[1] { searchedType }) as Array; } catch { return null; } } private static string FormatObjectForLog(object value) { if (value == null) { return "null"; } try { return SafeLogToken(value.ToString()); } catch { return "toString-failed"; } } private static string SafeLogToken(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Replace("|", "/").Replace("\r", " ").Replace("\n", " ") .Trim(); } return string.Empty; } } internal static class LabelUtilities { public static bool TryReadVectorXZ(object vector, out float x, out float z) { x = 0f; z = 0f; if (vector != null && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out x)) { return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "z"), out z); } return false; } public static bool TryReadVectorXY(object vector, out float x, out float y) { x = 0f; y = 0f; if (vector != null && ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "x"), out x)) { return ValueConversion.TryConvertFloat(Il2CppReflection.GetFieldOrProp(vector, "y"), out y); } return false; } public static bool IsActiveInHierarchy(object obj) { bool result; return !ValueConversion.TryConvertBool(Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject") ?? obj, "activeInHierarchy"), out result) || result; } public static string GetObjectName(object obj) { return (Il2CppReflection.GetFieldOrProp(obj, "name") as string) ?? (Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(obj, "gameObject"), "name") as string); } public static string CleanObjectLabel(string name) { if (string.IsNullOrWhiteSpace(name)) { return name; } return name.Replace("(Clone)", string.Empty).Trim(); } public static object GetPlayerClientData(object player) { string source; return GetPlayerClientData(player, out source); } public static object GetPlayerClientData(object player, out string source) { source = "none"; if (player == null) { return null; } object fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "ClientData"); if (fieldOrProp != null) { source = "ClientData"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "NetworkclientData"); if (fieldOrProp != null) { source = "NetworkclientData"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "clientData"); if (fieldOrProp != null) { source = "clientData"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "_ClientData_k__BackingField"); if (fieldOrProp != null) { source = "_ClientData_k__BackingField"; return fieldOrProp; } fieldOrProp = Il2CppReflection.GetFieldOrProp(player, "_NetworkclientData_k__BackingField"); if (fieldOrProp != null) { source = "_NetworkclientData_k__BackingField"; return fieldOrProp; } return null; } public static string GetClientDataName(object clientData) { string source; return GetClientDataName(clientData, out source); } public static string GetClientDataName(object clientData, out string source) { source = "none"; if (clientData == null) { return null; } string text = Il2CppReflection.GetFieldOrProp(clientData, "Name") as string; if (!string.IsNullOrWhiteSpace(text)) { source = "Name"; return text; } text = Il2CppReflection.GetFieldOrProp(clientData, "_Name_k__BackingField") as string; if (!string.IsNullOrWhiteSpace(text)) { source = "_Name_k__BackingField"; return text; } return null; } public static string GetEndMapPlayerName(object ui, int index) { if (ui == null || index < 0) { return null; } if (!Il2CppReflection.TryGetIndexedItem(Il2CppReflection.GetFieldOrProp(ui, "playerNameUIs"), index, out var item) || item == null) { return null; } return GetTextValue(Il2CppReflection.GetFieldOrProp(item, "playerNameText")); } public static string GetTextValue(object textObject) { string text = Il2CppReflection.GetFieldOrProp(textObject, "text") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } text = Il2CppReflection.GetFieldOrProp(textObject, "Text") as string; if (!string.IsNullOrWhiteSpace(text)) { return text; } return null; } public static bool IsUsefulPlayerLabel(string label) { if (string.IsNullOrWhiteSpace(label)) { return false; } string text = label.Trim(); if (!string.Equals(text, "Player", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "Player(Clone)", StringComparison.OrdinalIgnoreCase)) { return !text.StartsWith("PlayerNetworkSync", StringComparison.OrdinalIgnoreCase); } return false; } public static string DescribePlayerNameSources(object player, object ui, int index) { string source; string source2; string clientDataName = GetClientDataName(GetPlayerClientData(player, out source), out source2); string textValue = GetTextValue(Il2CppReflection.GetFieldOrProp(player, "namePlate")); string endMapPlayerName = GetEndMapPlayerName(ui, index); string text = Il2CppReflection.GetFieldOrProp(Il2CppReflection.GetFieldOrProp(player, "gameObject") ?? player, "name") as string; return "player=" + DescribePlayerObject(player) + " clientData=" + source + " clientNameSource=" + source2 + " clientName='" + clientDataName + "' namePlate='" + textValue + "' endMap='" + endMapPlayerName + "' gameObject='" + text + "'"; } public static string DescribePlayerObject(object player) { if (player == null) { return "null"; } string text = player.GetType().FullName ?? player.GetType().Name; if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "netId"), out var result) && result > 0) { return text + "#net:" + result; } if (ValueConversion.TryConvertLong(Il2CppReflection.GetFieldOrProp(player, "ID"), out var result2) && result2 > 0) { return text + "#id:" + result2; } return text + "#obj:" + RuntimeHelpers.GetHashCode(player); } } internal static class ValueConversion { public static string GetStringValue(object value) { return value as string; } public static bool? GetBoolValue(object value) { if (value == null) { return null; } if (value is bool) { return (bool)value; } if (!bool.TryParse(value.ToString(), out var result)) { return null; } return result; } public static bool TryConvertBool(object value, out bool result) { result = false; if (value == null) { return false; } if (value is bool flag) { result = flag; return true; } if (value is string text) { return bool.TryParse(text.Trim(), out result); } try { result = Convert.ToBoolean(value, CultureInfo.InvariantCulture); return true; } catch { return bool.TryParse(value.ToString(), out result); } } public static bool TryConvertInt(object value, out int result) { result = 0; if (value == null) { return false; } if (value is Enum value2) { result = Convert.ToInt32(value2, CultureInfo.InvariantCulture); return true; } try { result = Convert.ToInt32(value, CultureInfo.InvariantCulture); return true; } catch { return int.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } } public static bool TryConvertLong(object value, out long result) { result = 0L; if (value == null) { return false; } if (value is Enum value2) { result = Convert.ToInt64(value2, CultureInfo.InvariantCulture); return true; } if (!(value is long num)) { if (!(value is int num2)) { if (!(value is uint num3)) { if (!(value is ulong num4)) { if (value is short num5) { result = num5; return true; } if (value is ushort num6) { result = num6; return true; } if (value is byte b) { result = b; return true; } if (value is sbyte b2) { result = b2; return true; } if (value is string text) { return long.TryParse(text.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } } else if (num4 <= long.MaxValue) { result = (long)num4; return true; } try { result = Convert.ToInt64(value, CultureInfo.InvariantCulture); return true; } catch { } string[] array = new string[3] { "m_value", "value", "Value" }; foreach (string name in array) { object fieldOrProp = Il2CppReflection.GetFieldOrProp(value, name); if (fieldOrProp != null && fieldOrProp != value && TryConvertLong(fieldOrProp, out result)) { return true; } } return long.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } result = num3; return true; } result = num2; return true; } result = num; return true; } public static bool TryConvertFloat(object value, out float result) { result = 0f; if (value == null) { return false; } try { result = Convert.ToSingle(value, CultureInfo.InvariantCulture); return true; } catch { return float.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out result); } } public static bool TryConvertByte(object value, out byte result) { result = 0; if (value == null) { return false; } try { result = Convert.ToByte(value, CultureInfo.InvariantCulture); return true; } catch { return byte.TryParse(value.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } } public static int FloorToInt(float value) { return (int)Math.Floor(value); } public static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } public static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value <= max) { return value; } return max; } public static float Clamp01(float value) { return Clamp(value, 0f, 1f); } }