Skip to content

This guide provides illustrative examples demonstrating the usage of Il2cppInspector C++ scaffold.

License

Notifications You must be signed in to change notification settings

jadis0x/il2cpp-reverse-engineering-guide

Repository files navigation

GitHub stars GitHub contributors License

Il2cppInspector: C++ Scaffold Guide

Hello! I have decided to share some useful examples regarding the usage of Il2cppInspector C++ scaffold. In this guide, I will provide examples of how to interact with defined Il2cpp API functions.

Installation

You can get the latest version of Il2CppInspector 2023.1 here.

Requirements

Having knowledge of C++ and C# is essential. You should also be familiar with the basics of the Unity game engine.

Note

I wrote my own helper class. You can access it under the lib folder. Throughout the guide, I will be using the helper functions I created.

Video

Video

Contact

If you have any questions, feel free to reach out to me.

Discord: Jadis0x
Steam: Jadis0x

Examples

Tip

You can find more detailed examples by taking a look at HERE using the Il2cppInspector analysis tool I used to create the cheat.

Note

I am updating the guide. I will try to add detailed explanations as much as I can.

Contents



Get the assemblies

This code snippet demonstrates how to retrieve a list of all assemblies within the active domain using il2cpp API functions.

// Get the active domain
const Il2CppDomain* domain = il2cpp_domain_get();

// Define variables to hold the assembly list
const Il2CppAssembly** assemblies;
size_t size;

// Use the il2cpp_domain_get_assemblies function to retrieve all assemblies
assemblies = il2cpp_domain_get_assemblies(domain, &size);

// Iterate through each assembly in the list
for (size_t i = 0; i < size; ++i) {
    const Il2CppAssembly* assembly = assemblies[i];

    if (assembly) {
        // Get the assembly name using il2cpp_image_get_name function
        const char* assemblyName = il2cpp_image_get_name(assembly->image);
        
        // Print the name of the assembly to the console
        std::cout << assemblyName << "\n";
    }
}

Summary of Get the assemblies

  1. Get the Active Domain:
    • Function: il2cpp_domain_get
    • Description: Retrieves the active domain (Il2CppDomain) where the program is currently running.
  2. Retrieve Assemblies:
    • Function: il2cpp_domain_get_assemblies
    • Description: Retrieves a list of all assemblies (Il2CppAssembly) within the specified domain (Il2CppDomain). The function returns an array of Il2CppAssembly pointers (assemblies) and updates the size variable with the number of assemblies found.
  3. Iterate Through Assemblies:
    • Using a for loop, iterate through each assembly in the assemblies array.
  4. Get Assembly Name:
    • Function: il2cpp_image_get_name
    • Description: Retrieves the name of the assembly associated with the given image (Il2CppImage) within the assembly structure (Il2CppAssembly). It returns a const char* containing the assembly name.
  5. Print Assembly Names:
    • Print each assembly name retrieved from il2cpp_image_get_name to the console (std::cout).

Output:



Getting the type from a class (Il2CppObject* to Type*)

To obtain the type information here, we will use the GetTypeFromClass function inside my Il2cppHelper class.

Il2CppObject* Il2CppHelper::GetTypeFromClass(const Il2CppImage* _image, const char* _namespaze, const char* _name)
{
    // Retrieve the class information using the image, namespace, and class name
    Il2CppClass* _targetClass = il2cpp_class_from_name(_image, _namespaze, _name);

    // If the class is found
    if (_targetClass) {
        // Get the type information from the class
        const Il2CppType* _targetType = il2cpp_class_get_type(_targetClass);

        // If the type information is found
        if (_targetType) {
            // Get the Il2CppObject from the type information
            Il2CppObject* targetObject = il2cpp_type_get_object(_targetType);

            // If the object is successfully retrieved, return it
            if (targetObject) {
                return targetObject;
            }
        }
    }

    // If any step fails, return nullptr
    return nullptr;
}

First, load the CoreModule from Unity, which contains many fundamental classes such as GameObject.

const Il2CppImage* _CoreModule = _helper->GetImage("UnityEngine.CoreModule.dll");

If the module is successfully loaded, use the GetTypeFromClass function to get the type information for the GameObject class.

if (_CoreModule) {
    // Get the type information for the GameObject class in the UnityEngine namespace
    Il2CppObject* _object = _helper->GetTypeFromClass(_CoreModule, "UnityEngine", "GameObject");

    // If the type information is successfully retrieved
    if (_object) {
        // Cast the Il2CppObject to Type
        Type* gameobjectType = reinterpret_cast<Type*>(_object);

        // If the type casting is successful
        if (gameobjectType) {
            // Find all objects of the GameObject type
            Object_1__Array* getAllGameObjects = Object_1_FindObjectsOfType(gameobjectType, nullptr);

            // Print the count of GameObject instances
            std::cout << "Gameobject count: " << getAllGameObjects->max_length << "\n";

            // If any GameObject instances are found
            if (getAllGameObjects) {
                // Iterate through each GameObject instance
                for (int i = 0; i < getAllGameObjects->max_length; i++) {
                    Object_1* currentGameObject = getAllGameObjects->vector[i];

                    // If the GameObject is active in the hierarchy
                    if (GameObject_get_activeInHierarchy(reinterpret_cast<GameObject*>(currentGameObject), nullptr)) {
                        // Print the name of the active GameObject
                        std::cout << "GameObject Name: " << il2cppi_to_string(Object_1_GetName(currentGameObject, nullptr)) << "\n";
                    }
                }
            }
        }
    }
}

In this example, we are passing the type of the object to be found (GameObject) as a parameter to the Object_1_FindObjectsOfType function. This actually allows us to achieve the following:

GameObject[] allGameObjects = FindObjectsOfType<GameObject>();

This function is quite slow. Using this function every frame is not recommended.

Summary of the Steps and il2cpp API Functions Used

  1. Loading the Image:
    • Function: GetImage
    • Description: Loads the CoreModule image from Unity, which contains fundamental classes like GameObject.
  2. Retrieving Class Information:
    • Function: il2cpp_class_from_name
    • Description: Retrieves class information using the image, namespace, and class name.
  3. Getting Type Information from Class:
    • Function: il2cpp_class_get_type
    • Description: Obtains the type information from the class.
  4. Getting Il2CppObject from Type:
    • Function: il2cpp_type_get_object
    • Description: Converts type information into an Il2CppObject.
  5. Casting Il2CppObject to Type:
    • Operation: reinterpret_cast<Type*>
    • Description: Casts the retrieved Il2CppObject to Type.
  6. Finding Objects of a Specific Type:
    • Function: Object_1_FindObjectsOfType
    • Description: Finds all objects of the specified type (GameObject in this case).
  7. Checking if GameObject is Active:
    • Function: GameObject_get_activeInHierarchy
    • Description: Checks if the GameObject is active in the hierarchy.
  8. Getting GameObject Name:
    • Function: Object_1_GetName
    • Description: Retrieves the name of the GameObject.

Output:

Gameobjects



Getting class names and types from a specific assembly

This code snippet demonstrates how to retrieve and print the names and namespaces of classes within a specified assembly using il2cpp API functions.

void Il2CppHelper::GetClassesAndNamesFromAssembly(const Il2CppImage* _image)
{
	if (_image) {
		size_t classCount = il2cpp_image_get_class_count(_image);

		std::cout << "{\n";

		for (size_t i = 0; i < classCount; ++i) {
			const Il2CppClass* _klass = il2cpp_image_get_class(_image, i);

			if (_klass) {
				char* _name = const_cast<char*>(il2cpp_class_get_name(const_cast<Il2CppClass*>(_klass)));
				char* _namespace = const_cast<char*>(il2cpp_class_get_namespace(const_cast<Il2CppClass*>(_klass)));

				std::cout << " [\n";
				std::cout << "\tName: " << _name << "\n";
				std::cout << "\tNamespace: " << _namespace << "\n";

				std::cout << " ],\n";
			}
		}

		std::cout << "\n}\n";
	}
}
const Il2CppImage* _BoltDll = _helper->GetImage("bolt.dll");

if (_BoltDll) {
    _helper->GetClassesAndNamesFromAssembly(_BoltDll);
}

or

const Il2CppImage* _assemblyCSHARP = _helper->GetImage("Assembly-CSharp.dll");

if (_assemblyCSHARP) {
   _helper->GetClassesAndNamesFromAssembly(_assemblyCSHARP);
}

Outputs:



Getting information about any method

  • It allows you to print the name, return type, and parameter information of the target method.
void Il2CppHelper::GetMethodInfo(const Il2CppImage* _image, const char* _funcName, int argLength, const char* _class_name, const char* _class_namespace)
{
	Il2CppClass* _class = il2cpp_class_from_name(_image, _class_namespace, _class_name);

	if (_class == nullptr) return;

	const MethodInfo* methodInfo = il2cpp_class_get_method_from_name(_class, _funcName, argLength);

	if (methodInfo == nullptr) return;

	Il2CppReflectionMethod* reflectionMethod = il2cpp_method_get_object(methodInfo, _class);

	// Check if the reflectionMethod is not null
	if (reflectionMethod == nullptr) return;

	std::cout << "{\n";

	// Get the method's name from the reflectionMethod object
	const char* methodName = il2cpp_method_get_name(methodInfo);
	std::cout << "\tMethod Name: " << methodName << std::endl;

	const Il2CppType* returnType = il2cpp_method_get_return_type(methodInfo);
	std::cout << "\tReturn Type: " << il2cpp_type_get_name(returnType) << std::endl;

	// Get the parameter count of the method using il2cpp_method_get_param_count
	int parameterCount = il2cpp_method_get_param_count(methodInfo);
	std::cout << "\tParameter Count: " << parameterCount << std::endl;

	std::cout << "\t[\n";
	// Get the parameter types of the method
	for (int i = 0; i < parameterCount; i++) {
		// Get the parameter type at index i using il2cpp_method_get_param
		const Il2CppType* parameterType = il2cpp_method_get_param(methodInfo, i);

		// Get the type name of the parameter type using il2cpp_type_get_name
		const char* parameterTypeName = il2cpp_type_get_name(parameterType);

		// Print the parameter type name to the console
		std::cout << "\t\tParameter " << i << " Type: " << parameterTypeName << std::endl;
	}
	std::cout << "\t]\n";

	std::cout << "}\n";
}
const Il2CppImage* _AssemblyCSharp = _helper->GetImage("Assembly-CSharp.dll");
			
_helper->GetMethodInfo(_AssemblyCSharp, "SetFOV", 1, "NolanBehaviour", "");

Output:



Calling a function with "il2cpp_runtime_invoke"

if (GetAsyncKeyState(VK_F1) & 0x8000) {
	const Il2CppImage* _AssemblyCSharp = _helper->GetImage("Assembly-CSharp.dll");
	_helper->GetMethodInfo(_AssemblyCSharp, "SetRank", 1, "NolanRankController", "");
}

if (GetAsyncKeyState(VK_F2) & 0x8000) {
	const Il2CppImage* _csharp = _helper->GetImage("Assembly-CSharp.dll");
	if (_csharp == nullptr) return;

	Il2CppObject* nolanObj = _helper->GetTypeFromClass(_csharp, "", "NolanRankController");

	Type* TNolan = reinterpret_cast<Type*>(nolanObj);
	
	auto isTypeValid = Object_1_FindObjectOfType(TNolan, nullptr);

	if(isTypeValid){
	
		NolanBehaviour* _nb_ = reinterpret_cast<NolanBehaviour*>(isTypeValid);

		if (_nb_) {
			Il2CppClass* _nbClass = il2cpp_class_from_name(_csharp, "", "NolanRankController");
			if (_nbClass == nullptr) return;

			const MethodInfo* methodInfo = il2cpp_class_get_method_from_name(_nbClass, "SetRank", 1);

			int newRankvalue = 666;

			void* params[] = { &newRankvalue };

			std::cout << "call function..\n";
			il2cpp_runtime_invoke(methodInfo, _nb_, params, nullptr);
		}
	}
}

Output:



Getting a List of All Functions in the Target Class

  • This function, serves the purpose of obtaining and displaying a list of all methods within a given class (specified by the klass parameter). It iterates through each method in the class using a loop, retrieving information such as the method name and its return type. Subsequently, it prints out the method name along with its return type, providing a clear representation of the methods contained within the class
void Il2CppHelper::PrintMethods(Il2CppClass* klass) {
	const MethodInfo* methodIter = nullptr;
	void* iter = nullptr;

	// Retrieve all methods of the class
	while ((methodIter = il2cpp_class_get_methods(klass, &iter)) != nullptr) {
		// Get the name of the method
		const char* methodName = il2cpp_method_get_name(methodIter);

		// Get the return type of the method
		const Il2CppType* methodReturnType = il2cpp_method_get_return_type(methodIter);
		char* returnTypeName = il2cpp_type_get_name(methodReturnType);

		// Print the method name and its return type
		std::cout << "Method Name: " << methodName;
		std::cout << " (" << returnTypeName << ")\n------------------------------------\n";

		// Perform necessary memory operations
		il2cpp_free(returnTypeName);
	}
}

Example:

const Il2CppImage* _image = _helper->GetImage("Assembly-CSharp.dll");

if (_image) {
	Il2CppClass* nolanRankControllerClass = il2cpp_class_from_name(_timage, "", "NolanRankController");

	if (nolanRankControllerClass != nullptr) {
		_helper->PrintMethods(nolanRankControllerClass);
	}
}

Output:



Getting Information about Class Fields (FieldInfo)

  • It allows us to get information about the fields of a class
void Il2CppHelper::GetFieldsInformation(Il2CppClass* klass)
{
	void* iter = nullptr;
	FieldInfo* field = nullptr;

	// Iterate through the fields of the class
	while ((field = il2cpp_class_get_fields(klass, &iter)) != nullptr)
	{
		// Get the name of the field
		const char* fieldName = il2cpp_field_get_name(field);

		// Get the type of the field
		const Il2CppType* fieldType = il2cpp_field_get_type(field);
		char* fieldTypeStr = il2cpp_type_get_name(fieldType);

		// Print the information about the field
		std::cout << "Field Name: " << fieldName << std::endl;
		std::cout << "Type: " << fieldTypeStr << std::endl;
		std::cout << "-----------\n";
	}
}
const Il2CppImage* _assemblyCSHARP = _helper->GetImage("Assembly-CSharp.dll");
			
if (_assemblyCSHARP) {
	Il2CppClass* _nolanBehaviourClass = il2cpp_class_from_name(_assemblyCSHARP, "", "NolanBehaviour");

	_helper->GetFieldsInformation(_nolanBehaviourClass);
}

Output:



Modifiying the Value of a Field

if (GetAsyncKeyState(VK_F1) & 0x8000) {
	// Get the Il2CppImage for "Assembly-CSharp.dll"
	const Il2CppImage* _AssemblyCSharp = _helper->GetImage("Assembly-CSharp.dll");

	// Get the object for the "Menu" class within the "Horror" namespace
	Il2CppObject* _horrorMenuClassObject = _helper->GetTypeFromClass(_AssemblyCSharp, "Horror", "Menu");

	// Check if the object exists
	if (_horrorMenuClassObject) {

		// Find the object represented by _horrorMenuClassObject in the app
		auto menuType = app::Object_1_FindObjectOfType_1(reinterpret_cast<Type*>(_horrorMenuClassObject), true, nullptr);

		// Check if the object was found
		if (menuType) {

			// Get the Il2CppClass for the "Menu" class
			Il2CppClass* menuClass = il2cpp_class_from_name(_AssemblyCSharp, "Horror", "Menu");
			if (menuClass == nullptr) return;

			// Get the FieldInfo for the "steamName" field
			FieldInfo* steamNameField = il2cpp_class_get_field_from_name(menuClass, "steamName");

			// Check if the field exists
			if (steamNameField) {
				std::cout << "field is exists!!\n";

				// Define a new value for the field
				const char* newSteamNameValue = "il2cpp-field";

				// Create a new Il2CppString from the new value
				Il2CppString* newSteamNameString = il2cpp_string_new(newSteamNameValue);

				// Set the field's value to the new value
				il2cpp_field_set_value(_horrorMenuClassObject, steamNameField, newSteamNameString);
			}
			else {
				std::cout << "field is not exists!\n";
			}
		}
	}
}

I will continue to contribute as much as I can. For now, bye!