Disassemble

Hello !
I have a question more about CIL than C# . I created a game where a cube moves around a scene and jumps to evade obstacles . Pretty simple project to test disassembling .
Lets focus more on the camera script for now . There is a piece of code that goes along as
’ ’ ’
public Transform player; //Taking reference to the player
//Function to move the camera based on the position of the player

’ ’ ’
Okay so far so good . We have the camera script and in the update method we follow the player . Great !
Now lets disassemble it !
This leaves us with some CIL codes that I will not post here because it will get a very long thread . But what we can see in the CIL code is that we have a field that is an instance . Obviously we take a ref to the player , we need to know based on what position we should calculate our camera’s pos .
My question is how can I see this reference when I disassemble the project ? I am using dotPeek by jetBrains and I just see that this player filed is an instance (obviously we know that) How can I actually see what it refers too ? In other words I would like to know how to see the ref tables and kinda be able to find the product im referencing to !
Thank you for your answers in advance !
Greetings from BG

This sounds like more of a toolchain question for the dotpeek or jetbrains support forums.

In 8+ years of using Unity I have never cared about the IL so I’m not sure what your exact goal is here, except perhaps curious investigation.

I’m not 100% sure what you’re asking.

What would be the purpose of that?
Why would you want to know that?

As long as you don’t run the code, there won’t be any instances around, because that’s just code (or IL in that case) describing what that specific part of your source code does.
This also applies to instances that you set up in the editor. These won’t be available as “instances” anywhere in your code when you only inspect the code, but instead their serialized state is saved in data blobs in Unity’s proprietary data format.
The “references” to other serialized UnityEngine.Objects that you linked in the editor are available as unique IDs which kind of refer to a specific data blob for the corresponding instance that will be injected when the actual instances are created from the deserialized data at runtime. But these shouldn’t appear anywhere in your code, nor in the IL.

Still, you have all the information in the project if you create the game. So the question remains:
What’s the real purpose of that? Are you trying to reverse-engineer a project?

1 Like

Thanks for the answers so far . I wouldnt say that I have a particular goal . I started creating an OS for my Engineering project and i stumbled upon assembler . It is recommended to write your interrupts in assembler and I started taking a deeper look at assembly. Assem gets pretty messy tho and its difficult to understand some things . I also do work in a VR company (making unity games is kinda like a hobby) outside the University project . One day I was building a chemistry project and got null references . The code was pretty huge and I finally saw my error .Since I took a look before in the assem I really wanted to dive a bit deeper and out of curiosity just wanted to know how things work under the hood .
So as I said I created a simple game ,where the camera takes a ref to the player pos and moves with it accordingly to follow through some obstacles . Again low level things are pretty interesting to me so I really wanted to know how the PC knows that we need to follow the :player: not the :obstacle: , obviously it needs to store somewhere the addr.
Suddoha , You are saying that references are actually build dynamically ? But when for example take the player and drag it to the camera script is this not creating a ref ? Or it does just when the code needs it ?
Also how can we see those references ? Do we need to use some other program rather than dotPeek in order to actually play the code and see the ref addresses created when they are needed ? I am new in low level memory management so sorry if my question is dumb or badly structured !
Thank you for the answers !

References in Unity get weird, so it’ll probably look weird when you’re looking at stuff at a low level.

The first thing to know is that Unity is a two-part engine. There’s the underlying engine, and there’s user script. The underlying engine is a c++ program, while the user-script part is in C#.
Everything that inherits from UnityEngine.Object (like your behaviours) exists both in the c++ and the C# part of the engine.

The instance you’re talking about is probabbly the m_instanceID field defined in UnityEngine.Object, which is the ID on the c++ side of the same object.

There are reasons for this, but I’ll skip those as it’s not directly relevant to your question. Does this explain things?

Oh I see . Going low would be a bit messy I suppose ? Is there any good tool to follow / (look through) the ids of the references ? And see what really happens with them ?

In the editor you’ll have those instances, as the editor itself executes like a normal program. But when you drag&drop an instance into another field in the inspector, Unity does a lot of additional things. First of all, of course it knows which instance you dropped in there, and it’ll actually also set that instance as one would expect. So yes, there’s actually an assignment of a reference.

However, there’s more happening in the background. Unity serializes all the changes that are done in the editor, unless you bypass that mechanism. So besides setting the reference to that particular instance, it also saves the new state of the component that you’ve just changed. With regards to the instance that you just linked to your component, it’ll save a GUID that’s associated with the instance when it was initially created, as well as with its data on disk.
This applies to all UnityEngine.Objects (nowadays you can also serialize reference to normal types, but that’s done in a similar fashion, but with some limitations).

Anyway, what ends up on disk is a huge data blob, like a data repository with every distinct instance in it… every occurrence of a “reference” to such an instance will be expressed by its associated GUID, which itself is just data at that moment.

When neither the editor nor Unity runs, there’s nothing like an actual instance available. You only have the source code, which sort of describes the infrastructure of your program and the data, which is separated from the source code. That data is just not a part of the source code. Your source code doesn’t know about the existence of any concrete instance, because it simply doesn’t run.

In fact, you can tell Unity to serialize everything as plain text (which has many benefits btw) during development.
Here’s a part of a newly created scene, values may differ on your PC:

--- !u!1 &128627299
GameObject:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  serializedVersion: 6
  m_Component:
  - component: {fileID: 128627302}
  - component: {fileID: 128627301}
  - component: {fileID: 128627300}
  m_Layer: 0
  m_Name: Main Camera
  m_TagString: MainCamera
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
--- !u!81 &128627300
AudioListener:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 128627299}
  m_Enabled: 1
--- !u!20 &128627301
Camera:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 128627299}
  m_Enabled: 1
  serializedVersion: 2
  m_ClearFlags: 1
  m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
  m_projectionMatrixMode: 1
  m_SensorSize: {x: 36, y: 24}
  m_LensShift: {x: 0, y: 0}
  m_GateFitMode: 2
  m_FocalLength: 50
  m_NormalizedViewPortRect:
    serializedVersion: 2
    x: 0
    y: 0
    width: 1
    height: 1
  near clip plane: 0.3
  far clip plane: 1000
  field of view: 60
  orthographic: 0
  orthographic size: 5
  m_Depth: -1
  m_CullingMask:
    serializedVersion: 2
    m_Bits: 4294967295
  m_RenderingPath: -1
  m_TargetTexture: {fileID: 0}
  m_TargetDisplay: 0
  m_TargetEye: 3
  m_HDR: 1
  m_AllowMSAA: 1
  m_AllowDynamicResolution: 0
  m_ForceIntoRT: 0
  m_OcclusionCulling: 1
  m_StereoConvergence: 10
  m_StereoSeparation: 0.022
--- !u!4 &128627302
Transform:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 128627299}
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
  m_LocalPosition: {x: 0, y: 1, z: -10}
  m_LocalScale: {x: 1, y: 1, z: 1}
  m_Children: []
  m_Father: {fileID: 0}
  m_RootOrder: 0
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

When you start Unity, it’ll begin to deserialize the data when it’s needed.
The exact algorithm is not known, but no matter how they pull it off, it’s roughly this idea:

It knows there’s scene A and that scene contains an object called with GUID “128627299”, which is remembered along with many other information for that scene. Now via the ID, the object’s data can be found and it deserializes that data, creates a GameObject.
Each GO happens to have a Transform, which also got a GUID (here: 128627302) when it was created initially.
Now find the data for 128627299, deserialize it, add a Transform component to the GO that was just created, and inject the transform data into the transform component. Voila, your GO was recovered.

But there’s more:

m_Component:
  - component: {fileID: 128627302} // transform
  - component: {fileID: 128627301} // camera
  - component: {fileID: 128627300} // audio listener

So now it repeats the whole process for everything in your scene.
For that particular object, we’ve already said we needed a Transform, but it still has to create the camera and the audio listener. They will also be mapped to their GUIDs, so whenever another component refers to a component with GUID “128627301”, go check if that one has already been deserialized and thus exists. If it does exist, simply inject the reference - no more work needed. If it doesn’t exist, find the data via the GUID, deserialize it, create everything required in order to get it into a healthy state, and inject it.

Long story short: By looking at source code or the IL code, you won’t see any of those instances. In many cases you can see how an instance is acquired. You can follow methods calls, you can see what is done, and how it’s done, you may find hard-coded initializations of instances… but you won’t find runtime data in terms of “actual instances”. Because these do only exist when you run the actual program.

Now it is very clear to me , Suddoha . Thank you very much ! It was very helpful !
How do you know all these stuff btw ? The internet is very tricky to acquire such “lower level” knowledge ?
Greetings from BG I hope you have a wonderful day !