Enabling NoClip on Unity Games


Games used: Hollow Cocoon.
Tools used: Cheat Engine, ILSpy.

As for every unity game, I first opened the AssemblyCSharp.dll file from the Managed folder inside the unity game directory with ILSpy.
This file contains the compiled bytecode of the C# unity scripts written by the developer.
Since there were no obfuscation, I could explore the decompiled C# game code at ease.

My objective was to find interesting classes in the Assembly file so that I could find and modify their instances inside the game memory.
I decided to use the PlayerControl class, because it had interesting details about the player and, luckily, the developper had left a debug feature amongst its methods (see the end of the post).

In order to find an instance of this class corresponding to my local player in the game memory, I had to analyze each class field that PlayerControl had so that I could scan for them in memory using Cheat Engine value scanner.

An interesting field I found was m_isCrouch, this field was a boolean that was set when my player crouched and reset otherwise.
So, I used Cheat Engine and scanned for 1 after pressing ctrl (crouch key) in game and 0 when I pressed it again and stand up.
I did that in order to find the PlayerControl instance associated to my local player.

After some filtering, I narrowed down the values and found the actual field value that belonged to my local player PlayerControl instance.

I then checked which assembly code modified this address using Cheat Engine's Find out what writes to this address feature.
mov r11,PlayerControl:_IsCanStand
call r11
test eax,eax
je PlayerControl:StateGrounded+177f
movzx eax,byte ptr [rdi+0000028A]
test eax,eax
sete al
movzx rax,al
mov [rdi+0000028A],al
movzx eax,byte ptr [rdi+00000289]
As we can see here, the m_isCrouch field is at offset 0x28A from rdi.
This field is set with al when the call to PlayerControl:_IsCanStand returns true, effectively returning 1 in eax.

Now what is rdi you might ask?
Well, it is the address of our PlayerControl instance!.

Let's repeat the same process on the rdi address with Find out what writes to this address to get an assembly code that exclusively accesses this class instance so that we can get it when we re-launch the game without having to repeat the process.
mov rax,[rsi+38]
mov rcx,rax
cmp dword ptr [rax],00
lea rsp,[rsp+00]
mov r11,PlayerControl:IsGroundedState
call r11
In the provided assembly code from Cheat Engine, rdi is being read by the first mov instruction and is the only address read by this instruction.

This is exactly how the Cheat Engine Script (Get PlayerControl) in the video below fetches this instance address and places it in the global PLAYERCONTROL symbol (PlayerControlPtr will then contain its value in the cheat table).
Additionally, the value m_acceleration on the Cheat Engine table corresponds to the PlayerControl movement acceleration value and is given as a means to verify the validity of the PlayerControl instance.

Ok, phew! That was a lot...
Now that we have our PlayerControl instance, let's no clip!

So as I said in the beginning of this post, the developer actually left some debugging features in the production code, especially inside the PlayerControl class.

Amongst them is the DBG_NOCLIP player state, a no clip input event handlerStateDbgNoclip which handles key presses when the player state is changed to DBG_NOCLIP and finally a SetNoclip method which sets the correct state and the associated state event handler.

This really facilitates our task, since usually to get no clip we could: As you can see, we got pretty lucky since the developper already implemented this behaviour in game.

The only thing left to do, is to invoke this method from the PlayerControl instance we found earlier and enable no clip by passing 1 as the parameter!

This effectively enables no clip in game and allows us to move like a free camera. The StateDbgNoClip event handler controls the movement while in this state and allows us to move, change the fov and rotate the camera.

You can see the last steps demonstrated in the following video