Introduction
Unity3D is a popular game engine used by many games, like Escape from Tarkov, Rust, Fall Guys, Among Us, and many more. The engine uses C# as scripting backend, and by default runs on top of the Mono Runtime. Since C# assemblies get compiled to MSIL, we can easily reverse engineer them using tools like dnSpy.
We are also able to easily inject our dependencies and even reference the games and the engine’s dependencies in our source code. In Unity, this generally is used for writing mods, such as for the game Beat Saber, which depends on the modding community.
In this post I will be using the simple game Adventure Communist, which is for free on steam.
Show screenshot of the game
This game is not protected by Il2Cpp. Games like Fall Guys and Among Us are. However, you can achieve the same results by using tools like Il2CppDumper and Il2CppAssemblyUnhollower.
Decompiling the game’s code
All (non-il2cpp) Unity games have an Assembly-CSharp.dll
located inside of GameRoot\TheNameOfTheGame_Data\Managed\
. This file contains all game scripts. We need to plug this DLL into a .NET decompiler. I recommend dnSpy because it is very powerful.
Finding a starting point
This game is a clicker game and its goal is to gain as many resources as possible. Let’s try to increase the number of potatoes.
Obviously, one could simply edit the save files, however, that would be very boring.
I started by using the search tool in dnSpy to search for Potato
. The search tool can be found in the top bar, right next to the Start
button. The search yielded a single result.
It’s an Enum with all types of resources that can be gathered in the game. Now, if you have some experience with IDA, the next thing you would do here is to look for cross-references. We are going to do the same thing here.
Going down the rabbit hole
Let’s see what references that Enum. We have to find the class that contains all values we need.
We can list cross-references in dnSpy by right-clicking the Enums name and then clicking Analyze
. A list is going to show up listing all types of usages of the Enum. I noticed that the Enum was being used in a class called GeneratorData
.
The game has different types of “Generators” which generate items while you aren’t clicking. Now we do the same trick on the GeneratorData
class: right-click -> Analyze
.
dnSpy will show us, that a GeneretorData
object is being used to initialize an object of type GeneratorModel
inside of the Execute
method in the InitializeGameModelCommand
class.
This method seems to be initializing Generators for all kinds of resources.
An object of type GameModel
is passed into this method which contains a Dictionary called Resources
. This is the Dictionary that stores the number of resources we have.
This means that we have to get this GameModel
instance somehow. However, before that, we are going to take a quick dive into the engine’s principles.
What is a GameObject?
We know that we have to find the GameModel
instance. To do so, we have to understand the engine’s structure first.
Every scene holds GameObjects. GameObjects do nothing by default. However, we can apply components to a GameObject, such as a C# script.
All C# script classes have to inherit from the MonoBehaviour
class.
After doing so, you can place methods like Start()
or Update()
in your code and the engine will call them for you at initialization or on each frame.
This means that we can assume, that the game has a GameObject somewhere which has a script component managing the game. This script class likely holds the GameModel instance we are searching for.
Finding the game manager
We know, that the GameModel
is getting initialized by that Execute
method. This method call can once again be traced using dnSpy’s Analyze
feature.
The method is getting called from GameController.Init()
.
This GameController
class however still does not inherit MonoBehaviour
, which means that it is not a component of a GameObject.
We need to repeat the analysis on the GameController
class to see, where it’s being initialized.
We end up in SceneRootGame.Start()
.
SceneRootGame
does inherit from MonoBehaviour and it also has the Start()
method, which will get called by the engine when the GameObject spawns.
It also holds a reference to the GameController
, which holds the reference to the GameModel
. Finally, the GameModel
has the dictionary with all resource counts.
Developing a hack trainer
The gained knowledge can be used to write a DLL dependency that can be injected into the game. For this, you are going to need to set up a C# class library, reference the game’s DLLs, and set up a loader. If you haven’t done this before I highly recommend you to check out Zat’s Beginner’s Guide To Hacking Unity Games because the entire set up procedure is explained there.
Anyways, the final step is getting the SceneRootGame
instance itself. Since it is a GameObject component, we can use the engine method GameObject.FindObjectOfType<Type>()
to search for the instance.
If we pass in SceneRootGame
as the type, we will get the first SceneRootGame
instance it finds. Once again, this then contains the GameModel
, et cetera.
The following code is going to give us a lot of potatoes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using AdComm; //import game namespace
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine; //import unity engine namespace
public class ItemsCheat
{
public void CheatPotatoes()
{
SceneRootGame sceneRoot = GameObject.FindObjectOfType<SceneRootGame>(); //get the SceneRootGame object that controls everything
GameController gameController = sceneRoot.Game; //get the GameController of the SceneRootGame
GameModel gameModel = gameController.Model; //get the game model
gameModel.Resources[ AdComm.Resources.Resource.Potato ].Value = 10000000; //set the value of the Potato Resource to a high number
}
}
We can iterate over the resources Enum and set all types of resources to a high value as well.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void GetAllItems()
{
SceneRootGame sceneRoot = GameObject.FindObjectOfType<SceneRootGame>(); //get the SceneRootGame object that controls everything
GameController gameController = sceneRoot.Game; //get the GameController of the SceneRootGame
GameModel gameModel = gameController.Model; //get the game model
List<AdComm.Resources.Resource> AllResources = Enum.GetValues(
typeof(AdComm.Resources.Resource))
.Cast<AdComm.Resources.Resource>().ToList(); //get all available Resource Types as list
foreach (AdComm.Resources.Resource resourceType in AllResources) //iterate through the resources
{
gameModel.Resources[resourceType].Value = 1000000000; //set amount of the resource to huge number
}
}
Conclusion
Unity game hacking is a lot of fun. There is a lot that can be done thanks to the direct access to engine methods.
You can easily spawn new GameObjects, modify textures, and do a lot of other things. It gets slightly harder when the game is protected with Il2cpp, but not impossible. Sadly many multiplayer games use advanced anti-cheats like EasyAntiCheat, and due to the nature of Mono injection, it is hard to inject a Mono DLL without getting caught by the anti-cheat. In those cases, it’s probably better to go for a native approach.