Stacklands now has official mod support. Thunderstore is no longer recommended for this game, and most mods can now be found on the Steam Workshop.
Decompiled source of Unity Explorer Tree Snapshooter v0.1.0
UnityExplorerTreeSnapShooter.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using UnityExplorer; using UnityExplorer.Inspectors; using UnityExplorer.UI; using UnityExplorer.UI.Panels; using UnityExplorerTreeSnapShooter.Models; using UnityExplorerTreeSnapShooter.Services; using UnityExplorerTreeSnapShooter.Services.Implementation; using UnityExplorerTreeSnapShooter.UI; using UniverseLib.UI; using UniverseLib.UI.Models; using UniverseLib.UI.Panels; using UniverseLib.UI.Widgets; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("UnityExplorerTreeSnapshooter")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("UnityExplorerTreeSnapshooter")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("c5450fe0-edcf-483f-b9ea-4b1ef9d36da7")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace UnityExplorerTreeSnapShooter { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("gymmed.unity_tree_snap_shooter", "Unity Tree Snap Shooter", "0.1.0")] public class UnityExplorerTreeSnapShooter : BaseUnityPlugin { public const string GUID = "gymmed.unity_tree_snap_shooter"; public const string NAME = "Unity Tree Snap Shooter"; public const string VERSION = "0.1.0"; public static readonly string Prefix = "[Unity-Tree-Snap-Shooter]"; public const string EVENTS_LISTENER_GUID = "gymmed.unity_tree_snap_shooter_*"; internal static ManualLogSource Log; internal void Awake() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; LogMessage("Hello world from Unity Tree Snap Shooter 0.1.0!"); new Harmony("gymmed.unity_tree_snap_shooter").PatchAll(); } internal void Update() { } public static void LogMessage(string message) { Log.LogMessage((object)(Prefix + " " + message)); } public static string GetProjectLocation() { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } } } namespace UnityExplorerTreeSnapShooter.UI { public class SnapShooterPanel : PanelBase { public static readonly string UIBaseGuid = "gymmed.unity_tree_snap_shooter.SnapShooterInspector"; private Text _logText; private ISnapShooterService _snapShooterService; private IPathsManager _pathsManager; private IVisibilityManager _visibilityManager; private InputFieldRef _saveLocation; private InputFieldRef _saveFileName; private string _lastInspectedName; public static SnapShooterPanel Instance { get; private set; } public static UIBase UIBaseRegistry { get; set; } public override string Name => "Tree Snap Shooter"; public override int MinWidth => 420; public override int MinHeight => 350; public override Vector2 DefaultAnchorMin => new Vector2(0.5f, 0.5f); public override Vector2 DefaultAnchorMax => new Vector2(0.5f, 0.5f); public Text SelectionText { get; private set; } public override bool CanDragAndResize => true; public SnapShooterPanel(UIBase owner) : base(owner) { Instance = this; } protected override void ConstructPanelContent() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) if (_snapShooterService == null) { _pathsManager = PathsManager.Instance; _visibilityManager = VisibilityManager.Instance; FileSnapshotSaver saver = new FileSnapshotSaver(); _snapShooterService = new TreeSnapShooterService(saver); } SelectionText = UIFactory.CreateLabel(((PanelBase)this).ContentRoot, "Header", GetSelectionMessage(), (TextAnchor)4, default(Color), true, 14); UIFactory.CreateLabel(((PanelBase)this).ContentRoot, "LocationLabel", "Save Location:", (TextAnchor)3, default(Color), true, 14); _saveLocation = UIFactory.CreateInputField(((PanelBase)this).ContentRoot, "SaveLocationField", "Save to directory..."); GameObject gameObject = ((Component)_saveLocation.Component).gameObject; int? num = 25; int? num2 = 9999; UIFactory.SetLayoutElement(gameObject, (int?)null, num, num2, (int?)null, (int?)null, (int?)null, (bool?)null); _saveLocation.Component.text = _pathsManager.GetSnapshotsDirectory(); UIFactory.CreateLabel(((PanelBase)this).ContentRoot, "FileNameLabel", "Save File Name:", (TextAnchor)3, default(Color), true, 14); _saveFileName = UIFactory.CreateInputField(((PanelBase)this).ContentRoot, "SaveFileNameField", "File name..."); GameObject gameObject2 = ((Component)_saveFileName.Component).gameObject; num2 = 25; num = 9999; UIFactory.SetLayoutElement(gameObject2, (int?)null, num2, num, (int?)null, (int?)null, (int?)null, (bool?)null); _lastInspectedName = GetSelectionName(); _saveFileName.Component.text = _pathsManager.GenerateDefaultFileName(_lastInspectedName); ButtonRef obj = UIFactory.CreateButton(((PanelBase)this).ContentRoot, "SnapshotBtn", "Snapshot Selection", (Color?)new Color(0.2f, 0.2f, 0.35f)); GameObject gameObject3 = ((Component)obj.Component).gameObject; num = 30; num2 = 9999; UIFactory.SetLayoutElement(gameObject3, (int?)null, num, num2, (int?)null, (int?)null, (int?)null, (bool?)null); obj.OnClick = (Action)Delegate.Combine(obj.OnClick, new Action(OnSnapShoot)); UIFactory.CreateLabel(((PanelBase)this).ContentRoot, "LogLabel", "Output:", (TextAnchor)3, default(Color), true, 14); GameObject val = default(GameObject); AutoSliderScrollbar val2 = default(AutoSliderScrollbar); GameObject obj2 = UIFactory.CreateScrollView(((PanelBase)this).ContentRoot, "LogScroll", ref val, ref val2, new Color(0.2f, 0.2f, 0.2f)); num2 = 9999; num = 120; UIFactory.SetLayoutElement(obj2, (int?)null, num, (int?)null, num2, (int?)null, (int?)null, (bool?)null); _logText = val.AddComponent<Text>(); _logText.font = Resources.GetBuiltinResource<Font>("Arial.ttf"); _logText.fontSize = 12; ((Graphic)_logText).color = Color.white; _logText.supportRichText = true; _logText.horizontalOverflow = (HorizontalWrapMode)0; _logText.verticalOverflow = (VerticalWrapMode)1; _logText.text = "Ready..."; InspectorManager.OnInspectedTabsChanged += OnInspectorChanged; } private void OnInspectorChanged() { if (((UIModel)this).Enabled) { _lastInspectedName = GetSelectionName(); if ((Object)(object)SelectionText != (Object)null) { SelectionText.text = GetSelectionMessage(); } InputFieldRef saveFileName = _saveFileName; if ((Object)(object)((saveFileName != null) ? saveFileName.Component : null) != (Object)null) { _saveFileName.Component.text = _pathsManager.GenerateDefaultFileName(_lastInspectedName); } } } public override void SetActive(bool active) { bool enabled = ((UIModel)this).Enabled; ((PanelBase)this).SetActive(active); if (active && !enabled) { _lastInspectedName = GetSelectionName(); if ((Object)(object)SelectionText != (Object)null) { SelectionText.text = GetSelectionMessage(); } InputFieldRef saveFileName = _saveFileName; if ((Object)(object)((saveFileName != null) ? saveFileName.Component : null) != (Object)null) { _saveFileName.Component.text = _pathsManager.GenerateDefaultFileName(_lastInspectedName); } } } public override void Update() { InputFieldRef saveFileName = _saveFileName; if (!((Object)(object)((saveFileName != null) ? saveFileName.Component : null) == (Object)null)) { string selectionName = GetSelectionName(); if (selectionName != _lastInspectedName) { _lastInspectedName = selectionName; OnInspectorChanged(); } } } protected override void OnClosePanelClicked() { _visibilityManager.Hide(); } public string GetSelectionMessage() { return "<b>Selection: " + GetSelectionName() + "</b>"; } public string GetSelectionName() { GameObject inspectedGameObject = GetInspectedGameObject(); if ((Object)(object)inspectedGameObject != (Object)null) { return ((Object)inspectedGameObject).name; } return "None"; } private void OnSnapShoot() { if (_saveLocation == null || (Object)(object)_saveLocation.Component == (Object)null || _saveFileName == null || (Object)(object)_saveFileName.Component == (Object)null) { Log("ERROR: Save fields not initialized!"); return; } string text = _saveLocation.Component.text; string text2 = _saveFileName.Component.text; string fullPath = Path.Combine(text, text2); GameObject inspectedGameObject = GetInspectedGameObject(); if ((Object)(object)inspectedGameObject != (Object)null) { try { string content = _snapShooterService.ExecuteSnapShoot(inspectedGameObject); string text3 = _snapShooterService.SaveSnapshot(content, fullPath); Log("Successfully created snapshot: " + text3); return; } catch (Exception ex) { Log("Snapshot failed: " + ex.Message); return; } } Log("Target is not a GameObject!"); } private void Log(string msg) { if ((Object)(object)_logText != (Object)null) { _logText.text = msg + "\n" + _logText.text; } } private GameObject GetInspectedGameObject() { InspectorBase activeInspector = InspectorManager.ActiveInspector; if (activeInspector != null) { return TryGetGameObjectFromInspector(activeInspector); } if (InspectorManager.Inspectors != null) { for (int i = 0; i < InspectorManager.Inspectors.Count; i++) { InspectorBase inspector = InspectorManager.Inspectors[i]; GameObject val = TryGetGameObjectFromInspector(inspector); if ((Object)(object)val != (Object)null) { return val; } } } return null; } private GameObject TryGetGameObjectFromInspector(InspectorBase inspector) { if (inspector == null) { return null; } object target = inspector.Target; if (target == null || target.ToString() == "null") { return null; } GameObject val = (GameObject)((target is GameObject) ? target : null); if (val != null) { return val; } Component val2 = (Component)((target is Component) ? target : null); if (val2 != null && (Object)(object)val2.gameObject != (Object)null) { return val2.gameObject; } return null; } public override void Destroy() { ((PanelBase)this).Destroy(); InspectorManager.OnInspectedTabsChanged -= OnInspectorChanged; } } } namespace UnityExplorerTreeSnapShooter.Services { public interface IComponentStateReader { List<ComponentSnapshot> ReadComponents(GameObject gameObject, IReferenceTracker tracker); List<GameObjectSnapshot> ReadAllGameObjects(List<SnapshotNode> hierarchyNodes, int selectedInstanceId, IReferenceTracker tracker); } public interface IHierarchyBuilder { void BuildHierarchy(Transform root, IReferenceTracker tracker); List<SnapshotNode> GetHierarchyNodes(); List<SnapshotNode> BuildParentChain(Transform transform, IReferenceTracker tracker); } public interface IPathsManager { string GetSnapshotsDirectory(); string GenerateDefaultFileName(string gameObjectName); string GenerateDefaultFullPath(string gameObjectName); } public interface IReferenceTracker { void AddToSelection(int instanceId); bool IsInSelection(int instanceId); bool IsInSelection(Object obj); void Clear(); } public interface ISnapShooterService { string ExecuteSnapShoot(GameObject root); string SaveSnapshot(string content, string fullPath); } public interface ISnapshotFormatter { string Format(SnapshotResult snapshot); } public interface ISnapshotSaver { string Save(string content, string fullPath); } public interface IVisibilityManager { bool IsVisible { get; } void Initialize(UIBase uiBase); void Toggle(); void Hide(); void OnParentVisibilityChanged(bool parentVisible); void OnMouseInspectStarted(); void OnMouseInspectStopped(); } public class TreeSnapShooterService : ISnapShooterService { private readonly IReferenceTracker _referenceTracker; private readonly IHierarchyBuilder _hierarchyBuilder; private readonly IComponentStateReader _componentReader; private readonly ISnapshotFormatter _formatter; private readonly ISnapshotSaver _saver; public TreeSnapShooterService(ISnapshotSaver saver) { _referenceTracker = new ReferenceTracker(); _hierarchyBuilder = new HierarchyBuilder(); _componentReader = new ComponentStateReader(_referenceTracker); _formatter = new PlainTextSnapshotFormatter(); _saver = saver; } public string ExecuteSnapShoot(GameObject root) { _referenceTracker.Clear(); SnapshotResult snapshotResult = new SnapshotResult { RootName = ((Object)root).name, RootInstanceId = ((Object)root).GetInstanceID(), GeneratedAt = DateTime.Now }; _hierarchyBuilder.BuildHierarchy(root.transform, _referenceTracker); snapshotResult.Hierarchy = _hierarchyBuilder.GetHierarchyNodes(); snapshotResult.ParentChain = _hierarchyBuilder.BuildParentChain(root.transform, _referenceTracker); snapshotResult.Components = _componentReader.ReadComponents(root, _referenceTracker); snapshotResult.GameObjectSnapshots = _componentReader.ReadAllGameObjects(snapshotResult.Hierarchy, ((Object)root).GetInstanceID(), _referenceTracker); return _formatter.Format(snapshotResult); } public string SaveSnapshot(string content, string fullPath) { return _saver.Save(content, fullPath); } } } namespace UnityExplorerTreeSnapShooter.Services.Implementation { public class ComponentStateReader : IComponentStateReader { private readonly IReferenceTracker _tracker; public ComponentStateReader(IReferenceTracker tracker) { _tracker = tracker; } public List<ComponentSnapshot> ReadComponents(GameObject gameObject, IReferenceTracker tracker) { List<ComponentSnapshot> list = new List<ComponentSnapshot>(); Component[] components = gameObject.GetComponents<Component>(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null)) { ComponentSnapshot item = new ComponentSnapshot { TypeName = ((object)val).GetType().Name, InstanceId = ((Object)val).GetInstanceID(), Members = ReadMembers(val, tracker) }; list.Add(item); } } return list; } public List<GameObjectSnapshot> ReadAllGameObjects(List<SnapshotNode> hierarchyNodes, int selectedInstanceId, IReferenceTracker tracker) { List<GameObjectSnapshot> list = new List<GameObjectSnapshot>(); HashSet<int> hashSet = new HashSet<int>(); foreach (SnapshotNode hierarchyNode in hierarchyNodes) { GameObject gameObjectByInstanceId = GetGameObjectByInstanceId(hierarchyNode.InstanceId); if ((Object)(object)gameObjectByInstanceId == (Object)null) { continue; } bool isSelected = hierarchyNode.InstanceId == selectedInstanceId; GameObjectSnapshot gameObjectSnapshot = new GameObjectSnapshot { Name = ((Object)gameObjectByInstanceId).name, InstanceId = hierarchyNode.InstanceId, IsSelected = isSelected, Components = new List<ComponentSnapshot>() }; if (hashSet.Contains(hierarchyNode.InstanceId)) { gameObjectSnapshot.IsDuplicate = true; list.Add(gameObjectSnapshot); continue; } hashSet.Add(hierarchyNode.InstanceId); Component[] components = gameObjectByInstanceId.GetComponents<Component>(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); ComponentSnapshot item = new ComponentSnapshot { TypeName = ((object)val).GetType().Name, InstanceId = instanceID, Members = ReadMembers(val, tracker) }; gameObjectSnapshot.Components.Add(item); } } list.Add(gameObjectSnapshot); } return list; } private GameObject GetGameObjectByInstanceId(int instanceId) { GameObject[] array = Object.FindObjectsOfType<GameObject>(); foreach (GameObject val in array) { if (((Object)val).GetInstanceID() == instanceId) { return val; } } return null; } private List<MemberSnapshot> ReadMembers(Component component, IReferenceTracker tracker) { List<MemberSnapshot> list = new List<MemberSnapshot>(); Type type = ((object)component).GetType(); BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; FieldInfo[] fields = type.GetFields(bindingAttr); foreach (FieldInfo fieldInfo in fields) { try { object value = fieldInfo.GetValue(component); MemberSnapshot item = CreateMemberSnapshot("field", fieldInfo.Name, value, fieldInfo.FieldType, tracker); list.Add(item); } catch { list.Add(new MemberSnapshot { MemberType = "field", MemberName = fieldInfo.Name, FormattedValue = "[Error reading value]" }); } } PropertyInfo[] properties = type.GetProperties(bindingAttr); foreach (PropertyInfo propertyInfo in properties) { try { if (propertyInfo.GetIndexParameters().Length == 0) { object value2 = propertyInfo.GetValue(component, null); MemberSnapshot item2 = CreateMemberSnapshot("property", propertyInfo.Name, value2, propertyInfo.PropertyType, tracker); list.Add(item2); } } catch { list.Add(new MemberSnapshot { MemberType = "property", MemberName = propertyInfo.Name, FormattedValue = "[Error reading value]" }); } } return list; } private MemberSnapshot CreateMemberSnapshot(string memberType, string memberName, object value, Type valueType, IReferenceTracker tracker) { MemberSnapshot memberSnapshot = new MemberSnapshot { MemberType = memberType, MemberName = memberName, ValueType = (valueType?.Name ?? "null") }; if (value == null) { memberSnapshot.FormattedValue = "null"; return memberSnapshot; } Object val = (Object)((value is Object) ? value : null); if (val != null) { if (val == (Object)null) { memberSnapshot.FormattedValue = "null"; return memberSnapshot; } int instanceID = val.GetInstanceID(); memberSnapshot.ReferencedInstanceId = instanceID; if (tracker.IsInSelection(instanceID)) { memberSnapshot.FormattedValue = $"[InstanceID: {instanceID}] {val.name} ({valueType.Name})"; memberSnapshot.IsOutsideReference = false; } else { memberSnapshot.FormattedValue = $"[OUTSIDE_REFERENCE: InstanceID={instanceID}, Type={valueType.Name}] {val.name}"; memberSnapshot.IsOutsideReference = true; } return memberSnapshot; } memberSnapshot.FormattedValue = FormatPrimitiveValue(value); return memberSnapshot; } private string FormatPrimitiveValue(object value) { if (value is string text) { return "\"" + text + "\""; } if (value.GetType().IsPrimitive || value.GetType().IsEnum) { return value.ToString(); } if (value is Array array) { return $"Array[{array.Length}]"; } if (value is IEnumerable enumerable && !(value is string)) { int num = 0; foreach (object item in enumerable) { _ = item; num++; } return $"IEnumerable[{num}]"; } return $"({value.GetType().Name}) {value}"; } } public class FileSnapshotSaver : ISnapshotSaver { public string Save(string content, string fullPath) { string directoryName = Path.GetDirectoryName(fullPath); try { EnsureDirectoryExists(directoryName); File.WriteAllText(fullPath, content); return fullPath; } catch (Exception ex) { UnityExplorerTreeSnapShooter.LogMessage("Error saving snapshot: " + ex.Message); throw; } } private void EnsureDirectoryExists(string path) { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } } } public class HierarchyBuilder : IHierarchyBuilder { private readonly List<SnapshotNode> _nodes = new List<SnapshotNode>(); public void BuildHierarchy(Transform root, IReferenceTracker tracker) { _nodes.Clear(); BuildNode(root, 0, tracker); } private void BuildNode(Transform transform, int depth, IReferenceTracker tracker) { GameObject gameObject = ((Component)transform).gameObject; int instanceID = ((Object)gameObject).GetInstanceID(); tracker.AddToSelection(instanceID); SnapshotNode item = new SnapshotNode { Name = ((Object)gameObject).name, InstanceId = instanceID, TypeName = typeof(GameObject).Name, Depth = depth, IsActive = gameObject.activeInHierarchy }; _nodes.Add(item); for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); BuildNode(child, depth + 1, tracker); } } public List<SnapshotNode> GetHierarchyNodes() { return new List<SnapshotNode>(_nodes); } public List<SnapshotNode> BuildParentChain(Transform transform, IReferenceTracker tracker) { List<SnapshotNode> list = new List<SnapshotNode>(); List<Transform> list2 = new List<Transform>(); Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { list2.Add(parent); parent = parent.parent; } list2.Reverse(); for (int i = 0; i < list2.Count; i++) { GameObject gameObject = ((Component)list2[i]).gameObject; int instanceID = ((Object)gameObject).GetInstanceID(); tracker.AddToSelection(instanceID); SnapshotNode item = new SnapshotNode { Name = ((Object)gameObject).name, InstanceId = instanceID, TypeName = typeof(GameObject).Name, Depth = i, IsActive = gameObject.activeInHierarchy }; list.Add(item); } return list; } } public class PathsManager : IPathsManager { private static PathsManager _instance; private readonly string _snapshotsDirectory; public static PathsManager Instance => _instance ?? (_instance = new PathsManager()); private PathsManager() { string projectLocation = UnityExplorerTreeSnapShooter.GetProjectLocation(); _snapshotsDirectory = Path.Combine(projectLocation, "Snapshots"); EnsureDirectoryExists(_snapshotsDirectory); } private void EnsureDirectoryExists(string path) { try { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); UnityExplorerTreeSnapShooter.LogMessage("Created directory: " + path); } } catch (Exception ex) { UnityExplorerTreeSnapShooter.LogMessage("Error creating directory " + path + ": " + ex.Message); } } public string GetSnapshotsDirectory() { return _snapshotsDirectory; } public string GenerateDefaultFileName(string gameObjectName) { string text = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); return "TreeSnapShooter_" + gameObjectName + "_" + text + ".txt"; } public string GenerateDefaultFullPath(string gameObjectName) { return Path.Combine(_snapshotsDirectory, GenerateDefaultFileName(gameObjectName)); } } public class PlainTextSnapshotFormatter : ISnapshotFormatter { private readonly StringBuilder _output = new StringBuilder(); public string Format(SnapshotResult snapshot) { _output.Clear(); AddHeader(snapshot); AddParentHierarchy(snapshot.ParentChain); AddHierarchy(snapshot.Hierarchy); AddComponents(snapshot.Components); AddGameObjectReflection(snapshot.GameObjectSnapshots); return _output.ToString(); } private void AddHeader(SnapshotResult snapshot) { AppendLine("========================================"); AppendLine("TREE SNAPSHOT - " + snapshot.RootName); AppendLine($"Root Instance ID: {snapshot.RootInstanceId}"); AppendLine($"Generated: {snapshot.GeneratedAt:yyyy-MM-dd HH:mm:ss}"); AppendLine("========================================"); AppendLine(""); } private void AddParentHierarchy(List<SnapshotNode> parentChain) { AppendLine("=== PARENT HIERARCHY ==="); AppendLine(""); if (parentChain.Count == 0) { AppendLine(" (No parents - already at root)"); } else { foreach (SnapshotNode item in parentChain) { string text = new string(' ', item.Depth * 2); string text2 = (item.IsActive ? "" : " [INACTIVE]"); AppendLine($"{text}{item.Name} (InstanceID: {item.InstanceId}){text2}"); } } AppendLine(""); } private void AddHierarchy(List<SnapshotNode> hierarchy) { AppendLine("=== CHILDREN HIERARCHY ==="); AppendLine(""); foreach (SnapshotNode item in hierarchy) { string text = new string(' ', item.Depth * 2); string text2 = (item.IsActive ? "" : " [INACTIVE]"); AppendLine($"{text}[{item.Depth}] {item.Name} (InstanceID: {item.InstanceId}){text2}"); } } private void AddComponents(List<ComponentSnapshot> components) { AppendLine(""); AppendLine("=== COMPONENTS ==="); AppendLine(""); foreach (ComponentSnapshot component in components) { AppendLine($" {component.TypeName} (InstanceID: {component.InstanceId})"); } AddComponentDetails(components); } private void AddComponentDetails(List<ComponentSnapshot> components) { AppendLine(""); AppendLine("=== UNITYEXPLORER DISCOVERED STATE (REFLECTION) ==="); AppendLine(""); foreach (ComponentSnapshot component in components) { AppendLine($"--- {component.TypeName} (InstanceID: {component.InstanceId}) ---"); foreach (MemberSnapshot member in component.Members) { string text = " "; AppendLine(text + member.MemberType + " " + member.MemberName + ": " + member.FormattedValue); } AppendLine(""); } } private void AddGameObjectReflection(List<GameObjectSnapshot> gameObjectSnapshots) { AppendLine(""); AppendLine("=== GAME OBJECT REFLECTION (ALL CHILDREN) ==="); AppendLine(""); foreach (GameObjectSnapshot gameObjectSnapshot in gameObjectSnapshots) { string text = (gameObjectSnapshot.IsSelected ? " [SELECTED]" : ""); string text2 = (gameObjectSnapshot.IsDuplicate ? " [DUPLICATE - skipped]" : ""); AppendLine($"--- {gameObjectSnapshot.Name} (InstanceID: {gameObjectSnapshot.InstanceId}){text}{text2} ---"); if (gameObjectSnapshot.IsDuplicate || gameObjectSnapshot.Components.Count == 0) { AppendLine(""); continue; } foreach (ComponentSnapshot component in gameObjectSnapshot.Components) { AppendLine($" {component.TypeName} (InstanceID: {component.InstanceId}):"); foreach (MemberSnapshot member in component.Members) { string text3 = " "; AppendLine(text3 + member.MemberType + " " + member.MemberName + ": " + member.FormattedValue); } AppendLine(""); } AppendLine(""); } } private void AppendLine(string line) { _output.AppendLine(line); } } public class ReferenceTracker : IReferenceTracker { private readonly HashSet<int> _selectionInstanceIds = new HashSet<int>(); public void AddToSelection(int instanceId) { _selectionInstanceIds.Add(instanceId); } public bool IsInSelection(int instanceId) { return _selectionInstanceIds.Contains(instanceId); } public bool IsInSelection(Object obj) { if (obj == (Object)null) { return false; } return _selectionInstanceIds.Contains(obj.GetInstanceID()); } public void Clear() { _selectionInstanceIds.Clear(); } } public class VisibilityManager : IVisibilityManager { private static VisibilityManager _instance; private UIBase _uiBase; private bool _userRequested; private bool _parentVisible = true; private bool _mouseInspectOverride; public static VisibilityManager Instance => _instance ?? (_instance = new VisibilityManager()); public bool IsVisible { get; private set; } private VisibilityManager() { } public void Initialize(UIBase uiBase) { _uiBase = uiBase; } public void Toggle() { _userRequested = !_userRequested; ApplyVisibility(); } public void Hide() { _userRequested = false; ApplyVisibility(); } public void OnParentVisibilityChanged(bool parentVisible) { _parentVisible = parentVisible; ApplyVisibility(); } public void OnMouseInspectStarted() { _mouseInspectOverride = true; ApplyVisibility(); } public void OnMouseInspectStopped() { _mouseInspectOverride = false; ApplyVisibility(); } private void ApplyVisibility() { bool visible = _userRequested && _parentVisible && !_mouseInspectOverride; SetVisible(visible); } private void SetVisible(bool visible) { if (IsVisible == visible) { return; } IsVisible = visible; if (_uiBase != null) { _uiBase.Enabled = visible; } if (!visible) { EventSystem current = EventSystem.current; if (current != null) { current.SetSelectedGameObject((GameObject)null); } } } } } namespace UnityExplorerTreeSnapShooter.Patches { [HarmonyPatch(typeof(InspectorPanel), "ConstructPanelContent")] public static class Patch_InspectorPanel_ConstructPanelContent { private static SnapShooterPanel _snapShooterPanel; private static void Postfix(InspectorPanel __instance) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (_snapShooterPanel == null) { if (SnapShooterPanel.UIBaseRegistry == null) { SnapShooterPanel.UIBaseRegistry = UniversalUI.RegisterUI(SnapShooterPanel.UIBaseGuid, (Action)null); } SnapShooterPanel.UIBaseRegistry.Enabled = false; VisibilityManager.Instance.Initialize(SnapShooterPanel.UIBaseRegistry); _snapShooterPanel = new SnapShooterPanel(SnapShooterPanel.UIBaseRegistry); } ButtonRef obj = UIFactory.CreateButton(((Component)((PanelBase)__instance).TitleBar.transform.Find("CloseHolder")).gameObject, "SnapshotBtn", "Snapshot", (Color?)new Color(0.2f, 0.2f, 0.35f)); GameObject gameObject = ((Component)obj.Component).gameObject; int? num = 25; UIFactory.SetLayoutElement(gameObject, (int?)80, num, (int?)null, (int?)null, (int?)null, (int?)null, (bool?)null); ((Component)obj.Component).transform.SetSiblingIndex(0); obj.OnClick = (Action)Delegate.Combine(obj.OnClick, (Action)delegate { VisibilityManager.Instance.Toggle(); }); UnityExplorerTreeSnapShooter.LogMessage("[TreeSnapShooter] Snapshoot button added to Inspector panel!"); } } [HarmonyPatch(typeof(MouseInspector), "StartInspect")] public static class Patch_MouseInspector_StartInspect { private static void Postfix() { VisibilityManager.Instance.OnMouseInspectStarted(); } } [HarmonyPatch(typeof(MouseInspector), "StopInspect")] public static class Patch_MouseInspector_StopInspect { private static void Postfix() { VisibilityManager.Instance.OnMouseInspectStopped(); } } [HarmonyPatch(typeof(UIManager), "set_ShowMenu")] public static class Patch_UIManager_ShowMenu { private static void Postfix(bool value) { VisibilityManager.Instance.OnParentVisibilityChanged(value); } } } namespace UnityExplorerTreeSnapShooter.Models { public class ComponentSnapshot { public string TypeName { get; set; } public int InstanceId { get; set; } public List<MemberSnapshot> Members { get; set; } = new List<MemberSnapshot>(); } public class GameObjectSnapshot { public string Name { get; set; } public int InstanceId { get; set; } public bool IsSelected { get; set; } public bool IsDuplicate { get; set; } public List<ComponentSnapshot> Components { get; set; } = new List<ComponentSnapshot>(); } public class MemberSnapshot { public string MemberType { get; set; } public string MemberName { get; set; } public string ValueType { get; set; } public string FormattedValue { get; set; } public int? ReferencedInstanceId { get; set; } public bool IsOutsideReference { get; set; } } public class SnapshotNode { public string Name { get; set; } public int InstanceId { get; set; } public string TypeName { get; set; } public List<SnapshotNode> Children { get; set; } = new List<SnapshotNode>(); public int Depth { get; set; } public bool IsActive { get; set; } public bool IsOutsideReference { get; set; } } public class SnapshotResult { public string RootName { get; set; } public int RootInstanceId { get; set; } public DateTime GeneratedAt { get; set; } public List<SnapshotNode> ParentChain { get; set; } = new List<SnapshotNode>(); public List<SnapshotNode> Hierarchy { get; set; } = new List<SnapshotNode>(); public List<ComponentSnapshot> Components { get; set; } = new List<ComponentSnapshot>(); public List<GameObjectSnapshot> GameObjectSnapshots { get; set; } = new List<GameObjectSnapshot>(); } }