Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Guide to discovery/usage of Unity private/internal classes?

Discussion in 'Scripting' started by StarManta, Sep 23, 2015.

  1. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    I keep coming across situations where I need access to Unity's internal classes, primarily in editor scripts. I'm aware of the problem in doing so (updating will make scripts stop working without notice, etc), but that doesn't change the fact that this is something I need to learn to do. When searching for these situations, I often see posts like this one, where someone swoops in with an answer that seems to come out of nowhere, and they're able to make it work.

    But where do these class and function names come from? Obviously someone's not sitting there typing in random function names hoping for a hit - there has to be some way to find the classes available, decompile the classes, or retrieve a list of the functions that are available to reflection. How?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    Pretty sure there are tools available to introspect any assembly, and then if it isn't obvious from the method name, they probably tinker and try other combinations I'm guessing.

    I will add however, you really don't want to go this way. There be dragons. And madness. And mad dragons. What seems like a Good Idea(tm) today to solve your problem will bit you in the tuckus two days before you ship your game and the package vendor has to update something that breaks your undocumented access.

    I quote here from the page you linked: "Note: This is a pure hack :D you should avoid such things."
     
  3. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    And yet, when there is no method provided to access or change any information about the Game View (for example), I am left with no other options. If Unity supplied proper APIs, I wouldn't have to resort to such things!

    And this is far from the only time a lack of API access to parts of the editor has stopped me in my tracks creating an editor tool. This is something that I need to know so I can do my job.
     
  4. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,987
    System.Reflection

    I put together this script a while ago to spit out the methods on a passed object:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Reflection;
    4. using System;
    5.  
    6. public class RefTest : MonoBehaviour {
    7.  
    8.     void Start () {
    9.         //Transform foo = this.transform;
    10.         //GameOject foo = this.gameObject;
    11.         ParticleSystem foo = gameObject.GetComponent<ParticleSystem>() as ParticleSystem;
    12.         getMethodsOn(foo);
    13.     }
    14.    
    15.     void getMethodsOn(System.Object t)
    16.     {
    17.         Type obj = t.GetType();
    18.         string log = "METHODS FOR : " + obj.Name;
    19.  
    20.         MethodInfo[] method_info = obj.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
    21.         foreach( MethodInfo method in method_info )
    22.         {
    23.             string parameters = "";            
    24.  
    25.             ParameterInfo[] param_info = method.GetParameters();
    26.             if( 0 < param_info.Length )
    27.             {
    28.                 for (int i = 0; i < param_info.Length; i++)
    29.                 {
    30.                     parameters += param_info[i].ParameterType.Name;
    31.                     parameters += (i<(param_info.Length-1)) ? ", " : "";
    32.                 }
    33.             }
    34.             log+="\nFunction :" + method.Name + "(" + parameters + ")" ;
    35.         }
    36.         Debug.Log(log);
    37.     }
    38. }
    39.  
     
    Egad_McDad, UnityLighting and Kiwasi like this.
  5. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Did you read the full answer?

    ILSpy is a useful tool for pulling apart the .Net side of the Unity assemblies and seeing what they are made of. It doesn't help on the C++ side. There are quite a few users on answers who regularly pull things apart to see how they work. It doesn't seem as common on the forums.

    Just be warned, if its not in the API Unity has no contract with users to keep the functionality the same. Stuff is typically private for a reason. And may break or blow up in your face on a whim.

    Good luck.
     
    zombiegorilla likes this.
  6. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,987
    And this is different from the published API how exactly? ;) (sorry, couldn't resist)

    But this is a valid point. While it can sometimes be useful for internal tools and and such, it could be a huge problem if you depend on it in-game. And I hope it goes without saying that if your tools are heading for the Asset store they shouldn't include any unofficially exposed APIs.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    I personally use JetBrains dotPeek. It's free, and is also from the guys who make ReSharper (amazing product, not free).

    Whenever writing editor scripts I almost always have the libraries open and pulled apart to confirm little things, or access huge parts of the underlying structure.

    The entire 'editor' part of my Spacepuppy Framework is built by reverse engineering via this method. Everything that sneeks behind the scenes is in my com.spacepuppyeditor.Internal namespace:
    https://github.com/lordofduct/spacepuppy-unity-framework/tree/master/SpacepuppyBaseEditor/Internal

    Just like ILSpy it only lets you see the .Net side of things in Unity, not the underlying C++ code. So when the method directs to a 'wrapper method', you kind of just gotta take it for what it is.




    I have wondered why you don't see much about it here on the forums. In the same respect, for the first few months, I myself sort of avoided mentioning it, as I wasn't sure if it was against forum rules or not.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    In practice, if you find yourself reaching for this sort of under the hood access, usually one of two things are going on:

    1. you are trying to do things in a sub-optimal way; go back to the drawing board and rethink things.

    2. your vendor really has screwed up and left out critical access that you need, in which case, contact your vendor or change vendors.

    After three+ decades of programming I've found it is 95% the first case, and 5% the second. Further, in about 4.999% of the second cases, the vender won't change his API boundaries to suit you, and in the final 0.001% of cases, it was probably changed by the company purchasing the vendor outright.

    That's my perspective, YMMV. Remember that 80% of all statistics are made up on the spot.
     
    Shaderic and lchaia like this.
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Published API blow-ups have priority when it comes to whining on the forums. :)

    Its not really against any of Unity's rules as far as I am aware. Just not recommended. You really are out on your own if something goes wrong. But if you don't upgrade the engine, things shouldn't mess up to much.

    There was a Unite video a couple of years back that was all about using reflection to call Unity's internal methods. I just can't seem to find it at the moment. I will post a link if it shows up.
     
    zombiegorilla and Kurt-Dekker like this.
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
  11. Korno

    Korno

    Joined:
    Oct 26, 2014
    Posts:
    518
    The times I have had to do this I always make sure I keep the code that touches the internals of Unity locked behind interfaces so if stuff does change I only get errors In a couple of classes not all over the place. Also, you can return different implementations of the interface based on Unity version.
     
    Kiwasi likes this.
  12. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    I've been working on this using @zombiegorilla 's reflection script to probe for methods. Stabbing wildly in the dark (as one is expected to do in this situation), there is a method in the game view named "set_selectedSizeIndex" which appears to be the most promising I've found so far. (I'm trying to forcibly change the chosen resolution of the Game View)

    Unfortunately, my code gives me the following error (the line it points to is the one containing .Invoke). Am I using Reflection wrong here?

    Code (csharp):
    1.  
    2. NullReferenceException: Object reference not set to an instance of an object
    3. ScreenCyclerEditor.OnInspectorGUI () (at Assets/ResolutionSimulator/Editor/ScreenCyclerEditor.cs:62)
    4. UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor editor, Int32 editorIndex, Boolean forceDirty, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect, Boolean eyeDropperDirty) (at /Users/builduser/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1211)
    5. UnityEditor.DockArea:OnGUI()
    6.  
    Code (csharp):
    1.  
    2.            if (foundGameWindow == null) {
    3. EditorGUILayout.HelpBox("Please mouse over the game view", MessageType.Warning);
    4. Repaint();
    5.  
    6. if (EditorWindow.mouseOverWindow != null) {
    7. GUILayout.Label ("Mouseing over "+EditorWindow.mouseOverWindow.GetType().Name);
    8. if (EditorWindow.mouseOverWindow.GetType().Name == "GameView") {
    9. foundGameWindow = EditorWindow.mouseOverWindow;
    10. }
    11. }
    12. }
    13. else {
    14. intnewThisIndex = EditorGUILayout.IntSlider(thisIndex, 0, 4);
    15. if (thisIndex != newThisIndex) {
    16. MethodInfosetResIndexMethod = foundGameWindow.GetType().GetMethod("set_selectedSizeIndex");
    17. setResIndexMethod.Invoke(foundGameWindow, newobject[]{ (System.Int32)newThisIndex} );
    18. thisIndex = newThisIndex;
    19. }
    20. }
    edit: it looks like my GetMethod is the problem - it's returning null. Why would that be?
    For reference, here's the output from the RefTest script:
    Code (csharp):
    1.  
    2. METHODSFOR : GameView
    3. Function :get_maximizeOnPlay()
    4. Function :set_maximizeOnPlay(Boolean)
    5. Function :get_selectedSizeIndex()
    6. Function :set_selectedSizeIndex(Int32)
    7. Function :get_currentGameViewSize()
    8. Function :OnValidate()
    9. Function :OnEnable()
    10. Function :OnDisable()
    11. Function :GameViewAspectWasChanged()
    12. Function :AllowCursorLockAndHide(Boolean)
    13. Function :OnFocus()
    14. Function :OnLostFocus()
    15. Function :DelayedGameViewChanged()
    16. Function :DoDelayedGameViewChanged()
    17. Function :OnResized()
    18. Function :EnsureSelectedSizeAreValid()
    19. Function :IsShowingGizmos()
    20. Function :OnSelectionChange()
    21. Function :ShouldShowMultiDisplayOption()
    22. Function :GetDisplayNames()
    23. Function :get_gameViewRenderRect()
    24. Function :GetConstrainedGameViewRenderRect()
    25. Function :SelectionCallback(Int32, Object)
    26. Function :DoToolbarGUI()
    27. Function :OnGUI()
    28. Function :ShowResolutionWarning(Rect, Boolean, Vector2)
    29. Function :MakeModal(ContainerWindow)
    30. Function :BeginWindows()
    31. Function :EndWindows()
    32. Function :get_wantsMouseMove()
    33. Function :set_wantsMouseMove(Boolean)
    34. Function :CheckForWindowRepaint()
    35. Function :GetLocalizedTitleContent()
    36. Function :ShowNotification(GUIContent)
    37. Function :RemoveNotification()
    38. Function :DrawNotification()
    39. Function :get_dontClearBackground()
    40. Function :set_dontClearBackground(Boolean)
    41. Function :get_autoRepaintOnSceneChange()
    42. Function :set_autoRepaintOnSceneChange(Boolean)
    43. Function :get_maximized()
    44. Function :set_maximized(Boolean)
    45. Function :get_hasFocus()
    46. Function :get_docked()
    47. Function :GetNumTabs()
    48. Function :ShowNextTabIfPossible()
    49. Function :ShowTab()
    50. Function :Focus()
    51. Function :MakeParentsSettingsMatchMe()
    52. Function :ShowUtility()
    53. Function :ShowPopup()
    54. Function :ShowWithMode(ShowMode)
    55. Function :ShowAsDropDown(Rect, Vector2)
    56. Function :ShowAsDropDown(Rect, Vector2, PopupLocation[])
    57. Function :ShowAsDropDownFitToScreen(Rect, Vector2, PopupLocation[])
    58. Function :Show()
    59. Function :Show(Boolean)
    60. Function :ShowAuxWindow()
    61. Function :ShowModal()
    62. Function :RemoveFromDockArea()
    63. Function :Close()
    64. Function :Repaint()
    65. Function :RepaintImmediately()
    66. Function :get_minSize()
    67. Function :set_minSize(Vector2)
    68. Function :get_maxSize()
    69. Function :set_maxSize(Vector2)
    70. Function :get_title()
    71. Function :set_title(String)
    72. Function :get_titleContent()
    73. Function :set_titleContent(GUIContent)
    74. Function :get_depthBufferBits()
    75. Function :set_depthBufferBits(Int32)
    76. Function :GetCurrentGameViewRect()
    77. Function :SetInternalGameViewRect(Rect)
    78. Function :get_antiAlias()
    79. Function :set_antiAlias(Int32)
    80. Function :get_position()
    81. Function :set_position(Rect)
    82. Function :SendEvent(Event)
    83. Function :AddSceneTab()
    84. Function :AddGameTab()
    85. Function :SetDirty()
    86. Function :get_name()
    87. Function :set_name(String)
    88. Function :get_hideFlags()
    89. Function :set_hideFlags(HideFlags)
    90. Function :ToString()
    91. Function :Equals(Object)
    92. Function :GetHashCode()
    93. Function :GetInstanceID()
    94. Function :Finalize()
    95. Function :GetType()
    96. Function :MemberwiseClone()
    97. Function :obj_address()
    98. UnityEngine.Debug:Log(Object)
    99. ScreenCyclerEditor:OnInspectorGUI() (atAssets/ResolutionSimulator/Editor/ScreenCyclerEditor.cs:44)
    100. UnityEditor.DockArea:OnGUI()
    101.  
     
    Last edited: Sep 28, 2015
  13. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
  14. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    That was it, thanks! ....unfortunately the method appears to do absolutely nothing though :(

    Time to poke around more I suppose.
     
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So, using DotPeek I looked into GameView. I checked out the OnGUI event and in it, it calls the function 'DoToolbarGUI'... this must be where the toolbar (where that dropdown is) is drawn. So I go there and I find this:

    Code (csharp):
    1.  
    2.     private void DoToolbarGUI()
    3.     {
    4.       ScriptableSingleton<GameViewSizes>.instance.RefreshStandaloneAndWebplayerDefaultSizes();
    5.       if (ScriptableSingleton<GameViewSizes>.instance.GetChangeID() != this.m_SizeChangeID)
    6.       {
    7.         this.EnsureSelectedSizeAreValid();
    8.         this.m_SizeChangeID = ScriptableSingleton<GameViewSizes>.instance.GetChangeID();
    9.       }
    10.       GUILayout.BeginHorizontal(EditorStyles.toolbar, new GUILayoutOption[0]);
    11.       int num = (int) GameView.currentSizeGroupType;
    12.       int selectedSizeIndex = this.selectedSizeIndex;
    13.       System.Action<int, object> itemClickedCallback = new System.Action<int, object>(this.SelectionCallback);
    14.       GUIStyle toolbarDropDown = EditorStyles.toolbarDropDown;
    15.       GUILayoutOption[] guiLayoutOptionArray = new GUILayoutOption[1];
    16.       int index = 0;
    17.       GUILayoutOption guiLayoutOption = GUILayout.Width(160f);
    18.       guiLayoutOptionArray[index] = guiLayoutOption;
    19.       EditorGUILayout.GameViewSizePopup((GameViewSizeGroupType) num, selectedSizeIndex, itemClickedCallback, toolbarDropDown, guiLayoutOptionArray);
    20.       GUILayout.FlexibleSpace();
    21.       this.m_MaximizeOnPlay = GUILayout.Toggle(this.m_MaximizeOnPlay, "Maximize on Play", EditorStyles.toolbarButton, new GUILayoutOption[0]);
    22.       this.m_Stats = GUILayout.Toggle(this.m_Stats, "Stats", EditorStyles.toolbarButton, new GUILayoutOption[0]);
    23.       Rect rect = GUILayoutUtility.GetRect(this.gizmosContent, GameView.s_GizmoButtonStyle);
    24.       if (EditorGUI.ButtonMouseDown(new Rect(rect.xMax - (float) GameView.s_GizmoButtonStyle.border.right, rect.y, (float) GameView.s_GizmoButtonStyle.border.right, rect.height), GUIContent.none, FocusType.Passive, GUIStyle.none) && AnnotationWindow.ShowAtPosition(GUILayoutUtility.topLevel.GetLast(), true))
    25.         GUIUtility.ExitGUI();
    26.       this.m_Gizmos = GUI.Toggle(rect, this.m_Gizmos, this.gizmosContent, GameView.s_GizmoButtonStyle);
    27.       GUILayout.EndHorizontal();
    28.     }
    29.  
    Note the line:
    Code (csharp):
    1.  
    2.   EditorGUILayout.GameViewSizePopup((GameViewSizeGroupType) num, selectedSizeIndex, itemClickedCallback, toolbarDropDown, guiLayoutOptionArray);
    3.  
    The callback is 'itemClickedCallback', which is set in the previous line:

    Code (csharp):
    1.  
    2.   System.Action<int, object> itemClickedCallback = new System.Action<int, object>(this.SelectionCallback);
    3.  
    When someone selects a new resolution it calls this method. So lets check there:

    Code (csharp):
    1.  
    2.     private void SelectionCallback(int indexClicked, object objectSelected)
    3.     {
    4.       if (indexClicked == this.selectedSizeIndex)
    5.         return;
    6.       this.selectedSizeIndex = indexClicked;
    7.       this.dontClearBackground = true;
    8.       this.GameViewAspectWasChanged();
    9.     }
    10.  
    Ok, first off, I'd like to point out that the reflection tool you're using unwraps properties into the two functions they actually get compiled into. Functions named the property name preceeded by 'get_' and 'set_' respectively.

    Here we see that it does set a value to 'selectedSizeIndex' (it calls 'set_selectedSizeIndex', just like you do). BUT just setting the property isn't enough... it also must force update the screen. Which it does by calling 'GameViewAspectWasChanged'.

    So try setting the index, and then call this method, and you should probably get the correct result.

    Personally I use a set of classes I wrote for dynamic reflection. One of them is TypeAccessWrapper:
    https://github.com/lordofduct/space...r/SpacepuppyBase/Dynamic/TypeAccessWrapper.cs

    With it, you'd say:

    Code (csharp):
    1.  
    2. var wrapper = new TypeAccessWrapper(foundGameWindow.GetType(), foundGameWindow, true);
    3. wrapper.SetProperty("selectedSizeIndex", (int)newThisIndex);
    4. wrapper.SetProperty("dontClearBackground", true);
    5. wrapper.CallMethod("GameViewAspectWasChanged");
    6.  

    edit - ugh, I finally figured out why my code blocks have been formatting so weird when copying code from my IDE. I changed to firefox. Annoying. Changing back to chrome, it formats correctly when I copy paste.
     
    Last edited: Sep 28, 2015
  16. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    And through a lot of poking around, a horrific hack is born! Thanks for your help, everyone.
     
    Kiwasi likes this.